【redis进阶之路】redis工作原理之epoll(一)

275 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

引言

最近研究redis的相关底层知识,查阅了一些资料、博客,发现对于redis工作原理的说明不够用全面,同时针对“redis为什么这么快”的解答发现了一些问题。故基于这些文章结合自己的理解做了如下整理 【如发现有问题的地方,望指出】

redis工作原理

要聊redis的工作原理,那就要从工作流程出发,逐步分析: 1、首先redis采用了epoll I/O多路复用器:经过网络数据传输,大量的连接请求通过epoll接收并且监控,有数据的连接才放到redis中进行读写处理,所以首先就要聊聊epoll

2、请求数据进入到redis中后,redis进程采用单线程处理,命令完全串行,不用额外维护锁机制,因此单线程处理是redis快的原因之一

3、处理数据时,redis采用的底层数据结构简单,提供的api操作简单,没有实现比较复杂的逻辑,因为redis的设计理念更多的在于查询快上,要针对计算密集型处理,更多的建议在后端代码中实现。所以其次要了解redis各类数据类型采用的数据结构是什么,优点在哪里

4、数据请求处理完成后,为了迎接下一次数据请求,那么还需要实现的是保证内存空间可用,即内存占用满了redis怎么处理

5、同时保存在内存里的数据,如果重启就会消失的,那么redis是怎么实现持久化的呢?redis的持久化机制有哪些亮点?

1、epoll

网络传输时是需要建立连接的,但是建立完连接并不会马上就传输数据,特别是在大量连接请求的情况下,同一时间段有数据传输来的连接是少数。每个连接会建立一个socket,每个socket都有一个它自己的等待队列,当socket阻塞时就会把任务放到等待队列中,当它唤醒时就会调用等待队列中的任务

1、在BIO时期,一个请求如果进程没有反馈数据,则线程会一直阻塞在那里,直到数据传输完

2、在NIO时期,内核做了优化,线程非阻塞了,可以使用一个死循环来遍历所有的连接,看哪个连接有数据,没有就遍历下一个,有数据就唤醒这个连接对应的进程,但是被唤醒的进程是不知道是哪些socket有数据的,因此进程又要遍历一次它关注的那些连接,即和他建立了连接的socket(实际上是遍历的文件描述符,这里为方便理解直接用连接来说明,有兴趣可自查),来找到是哪些连接有数据传输过来,然后把这些连接传给内核,进行后续的读写操作

3、如果有1000个连接的话,就意味着光是知道连接有数据传来,就要遍历1000遍,这样的消耗是比较高的,能不能有什么东西可以直接监控这些连接,通过一次调用这个东西就知道进程关心的这些连接有没有数据传来。这个东西就是IO多路复用器。

4、最初的多路复用器是select,但是它只解决了监控这些连接,不用遍历就知道这些连接有数据传来。但是对于具体是哪些连接有数据,select还是得通过再遍历进程关心的所有连接才知道。所以select的消耗还是比较高的,所以规定了select能监控的连接数不能超过1024个。

5、那么能不能有一个多路复用器即不用再遍历一次,也没有1024的限制呢?也就引入了epoll。下面详细介绍下epoll的工作原理,方便大家理解:

(1)进程会首先调用epoll_create方法来创建一个eventpoll对象,这个对象中包含(不仅有)一个等待队列,一个就绪列表

(2)有请求向进程建立连接时,就会通过epoll_ctl来添加epoll对socket的监控,即将eventpoll的引用添加到socket的等待队列中

(3)socket有数据传来时,网卡会发出中断信号,然后内核会调用中断程序,中断程序就会唤醒socket的等待队列,就会执行eventpoll的监听事件,将socket的引用添加到eventpoll的就绪列表中

(4)同时进程会被唤醒,然后通过就绪列表就能知道哪些socket有数据传来,再把这些socket传入内核,执行后续的读写操作(这里实际传入的是文件描述符,为了方便理解说明为socket,本质一样)

总结:epoll能够接收大量的请求,并且筛选出有效的请求放给redis,且执行效率高消耗资源少