【Redis源码系列】Redis6.0 超详细多线程IO源码分析
前言
上篇文章我们研究了Redis6的事件机制, 与之前版本相比, 事件处理机制并没有太大的变化。在6.0版本中最大的变化莫过于增加了多线程IO机制, 有效提升了Redis在处理网络IO方面的处理能力, 官方宣称性能可以提高至一倍, 本人未作具体性能测试, 大家可以参考: Redis6.0多线程性能测试。我们一起从源码探究多线程IO实现的具体流程。
事件机制回顾: Redis6.0事件机制详解
多线程设计
Redis6.0的多线程分为以下几个部分:
- 在
server.c
中设置beforesleep
回调函数, 文件事件回调函数等相关设置, 其中beforesleep
中比较重要的有handleClientsWithPendingReadsUsingThreads
, 下文我们将具体展开讲述 - 启动 IO Thread, 并设置 IO 线程的处理函数
- 执行事件轮询, 将 IO 事件分发给不同的线程处理
整体架构如下图所示:
源码流程分析
设置事件处理函数
调用 initServer
> aeSetBeforeSleepProc
设定 beforesleep
函数, 其中 handleClientsWithPendingReadsUsingThreads
将所有IO线程置为读状态,函数源码如下:
启动多线程
initThreadedIO
在 server.c
文件中的main函数流程, 通过: InitServerLast()
-> initThreadedIO()
, 启动线程, 我们一起来看启动线程的详细流程:
可以看到 redis 6.0 最多允许启动 128 个IO 线程, 启动时初始化每一个 io_threads_list
为双向链表, 用于保存任务信息, 并且为IO线程注册回调函数IOThreadMain
, 这是一个比较重要的处理函数, 我们一起梳理其流程。
IOThreadMain
IOThreadMain 回调的处理流程相对比较简单, 就是根据当前任务标志位 (读|写)
, 遍历任务链表执行任务的处理操作。
在 IOThreadMain
中会遍历当前线程的任务列表, 根据读写标识确定可执行的操作, 从源码可以看出在同一时刻, redis的IO线程只能处理读或者写操作, 通过上篇文章的分析可知道在 readQueryFromClient
函数在创建连接回调的时候一直执行过一次, 如此执行不是死循环吗, 答案就在 readQueryFromClient
函数中:
至此和多线程启动相关的源码执行流程已经分析完毕, 完成服务的启动后就进入时间轮询中。
事件轮询
主线程调用: aeMian
-> aeProcessEvents
进入事件轮询, redis服务启动完成, 开始接受并处理事件。在执行poll的过程中会执行 beforesleep
和 aftersleep
两个钩子函数, 如果我们后续有插件需求也可以基于这两个函数来处理, beforesleep
中有两个比较重要的函数 handleClientsWithPendingWritesUsingThreads
和 handleClientsWithPendingReadsUsingThreads
, 目的是为了进入下一次轮训前, 将 server.clients_pending_read
和 server.clients_pending_write
处理完成, 保证列表中不再有待处理任务。
其中读事件回调:acceptTcpHandler|acceptTLSHandler|acceptUnixHandler|moduleBlockedClientPipeReadable
底层都调用了 acceptCommonHandler
-> createClient
-> connSetReadHandler(conn, readQueryFromClient)
设定连接的读事件处理函数。
至此整条线路梳理完成, 客户端连接读|写
事件到来时, 调用回调函数将事件加入到 server.clients_pending_read
和 server.clients_pending_write
,然后IO线程轮询处理每个客户端的等待事件, 完成后进入下一次轮询。
总结
总结不易, 别忘了素质三连,下期开始研究服务的对象结构和数据结构, 求各位看官给个小小的赞 :)
转载自:https://juejin.cn/post/6992407136261128229