一文带你彻底掌握Redis为什么这么快?

一、Redis为什么这么快

因为Redis采取了epoll模型,IO多路复用。同步非阻塞。

二、什么是epoll

介绍epoll之前必须要先介绍下select和poll。明白select和poll的缺点后才能知道为什么epoll这么快。

三、什么是select

1、源码

在这里插入图片描述

2、源码解析

2.1、核心代码

// select(xx,读文件描述符集合,写文件描述符集合,异常文件描述符集合,超时时间),我们只需要关注读文件描述符集合,其他NULL不影响,因为有默认值。
select(max+1, &rset, NULL, NULL, NULL)

2.2、&rset是什么?

rset其实就是fds(文件描述符的集合)。而select模型存储fds的方式是采取的bitmap,默认最大1024个。

2.3、执行流程

1.select模型每次都直接将rset(也就是fds)全部拷贝到内核态,因为内核态速度比用户空间态快很多。
2.如果没数据的话,select函数会阻塞,如果有数据的话会执行两步
(1)将有数据的那个fd置位(也就是标记一下,代表这个fd有数据)
(2)select函数不在阻塞,将继续往下执行。也就是整体遍历fds,找到有数据的那个fd读取数据做处理。他的fd不能重用,每一次都需要重新创建新的fds且将用户空间态的fds拷贝到内核态。(这个可以看上面while(1)的代码)
在这里插入图片描述

3、缺点

  • fds最大支持1024个(可以更改,但是意义不大)
  • fd不可重用,每次内核态都给置位了,导致每次都需要重新执行如下四句话创建一个新的rset(也就是fds)
    在这里插入图片描述
  • 用户控件态拷贝rset到内核态也需要时间,虽然内核态执行比用户态快,但是copy也需要开销
  • O(n)再次遍历问题。因为rset里的fd被置位后,select函数并不知道哪个被置位了,需要从头遍历到尾,逐个对比。

四、什么是poll

1.源码

在这里插入图片描述

2、源码解析

2.1、核心代码

// 代表传入了5个pollfd这个结构体,超时时间是5000,poll整体就是围绕他的pollfd这个结构体来展开的
poll(pollfds, 5, 5000)

2.2、结构体

poll的结构体是为了fd重复利用,不需要每次都拷贝到内核态用的。
在这里插入图片描述
revents:置位用的,如果某个fd有数据了,就把这个revents置位为POLLIN

2.3、执行流程

  • 有数据的时候流程如下
  • 将pollfd这个结构体里的revents这个字段置位为POLLIN
  • 然后判断这个结构体的revents字段是不是被置位为POLLIN了,是的话再将其结构体的这个字段恢复默认值0,然后取出数据,处理逻辑。
  • 恢复为默认值0的用途是不需要在像select一样每次都需要重新创建fds,而是直接复用。但还是避免不了每个fd第一次复制一次。好的是不用每次都创建新的了。
    在这里插入图片描述

3、解决了select哪些问题

  • 采取的链表存储,而不是bitmap,解决了1024长度限制问题
  • 采取结构体每次置位结构体内的revents字段,而不破坏fd本身,所以可重用,不需要每次都创建新的fd。

4、缺点

  • 用户控件态拷贝rset到内核态也需要时间,虽然内核态执行比用户态快,但是copy也需要开销
  • O(n)再次遍历问题。因为rset里的fd被置位后,select函数并不知道哪个被置位了,需要从头遍历到尾,逐个对比。

五、再谈epoll

0、概述

epoll的出现就是为了解决poll最后两个缺点的。

1、源码

在这里插入图片描述

2、源码解析

2.1、核心代码

epoll_create 相当于创建白板
epoll_ctl相当于往白板上写字,写的内容是一个fd、一个events结构体,events结构体跟pollfd的结构体很像。
epoll_ctl执行完后就得到了epfd,epfd就是循环了五个fd-events放里面,每个fd都有events事件
epoll_wait

2.2、执行流程

  • epoll将fd放到了红黑树里,且不需要拷贝到内核态,因为他采取了“共享内存”的概念。(其实还是复制,只是复制采取了其他技术可以使开销极其的小)
  • epoll的置位是重排,比如五个fd, 1 2 3 4 5,1 3 5这三个fd有数据了,那么他会重排序,排成如下1 3 5 2 4。(也有的说是单独放到新的数组里)
  • 每一次置位nfds的值都+1。且会回调epoll_wait
  • 所以epoll_wait执行完会返回有几个fd有数据,那么下面的for直接遍历nfds次即可。解决了前面的两种O(n)。变成了O1
    在这里插入图片描述

六、阻塞/非阻塞

用户进程通过调用select/poll/epoll去处理socket请求的时候, select/poll/epoll由于是阻塞的,所以导致用户进程阻塞;而select/poll/epoll处理的socket用的是非阻塞

七、再次分析Redis

比如三个redis-cli,假设2个redis-cli写入命令,
select:那么select模型是轮询这三个redis-cli的fd,看哪个fd有消息,有的话读取处理消息。当他下次再写命令的时候还需要重新创建fd,然后复制到内核态然后再遍历全部。
poll:那么poll模型是轮询这三个redis-cli的fd,看哪个fd有消息,有的话读取处理消息。下次再写入的时候还是遍历全局fd,看哪个fd有消息进行处理。省去了每次都创建新的fd且复制的过程。
epoll:epoll就不轮询了,有消息进来后你通知我,我去处理你的消息,那些没消息的fd我不管。而且复制到内核态的过程我采取牛逼的技术让开销达到最小的极致。

八、总结

  • select、poll、epoll必须懂,redis面试90%问
    -在这里插入图片描述
发布了28 篇原创文章 · 获赞 33 · 访问量 8291

猜你喜欢

转载自blog.csdn.net/ctwctw/article/details/105024324