memcached 多线程网络编程的巅峰
,其线程机制的设计十分之精妙。线程池部分主要包含 主线程、工作线程 以及 辅助线程,另外再辅以 状态机、通信管道,就大致组成了整个线程机制
memcached 网络线程模型事件完全基于 libevent 处理机制,利用 基础的 多路复用IO模型 进行事件监控,以下我们就 网络模型
以及 源码解析
两部分进行解读
网络模型
memcached 服务器采用 master-woker 模式进行工作,后再辅以 辅助线程。服务端采用 socket 与客户端通讯,主线程、工作线程 采用 pipe管道进行通讯。主线程采用 libevent 监听 listen、accept 的读事件,事件响应后 将连接信息的数据结构封装起来 根据算法 选择合适的工作线程,将 连接任务携带连接信息 分发出去,相应的线程利用连接描述符 建立与 客户端的socket连接 并进行后续的存取数据操作。主线程和工作线程 处理事件流都采用状态机进行事件转移。
线程模型
线程模型 主要为 一个主线程 和 若干个工作线程,另外包含一些 slab、assoc、logger 等辅助线程,后续会一一介绍。
- 主线程 main 函数在初始化以后会 创建
main_base
,并将 服务端端口的listen、accept
加入至 libevent 的event_base
读事件
监控中 - 主线程 main 函数在初始化以后会 创建
若干个 woker thread 工作线程
,每个工作线程都会有自己的LIBEVENT_THREAD数据结构来存储线程信息(线程基本信息、线程队列、pipi信息)
。同时 每个工作线程都会有属于自己的 pipi 读写端,并加入至 event_base 进行读事件监听,主线程如有需要,会往 pipe写端写入 “c”,工作线程捕获读事件后进行处理,即完成 master-woker 线程通讯 - 主线程监听到 来自 client 的 socket connect 后,创建一个
conn_item(主要包含 建立连接所需要的 描述符等信息)
。主线程根据 线程位数 通过简单的求余操作
选定 合适的 工作线程,并把 conn_item 写入该 工作线程的conn_queue处理队列
中,随后 主线程往该 工作线程的 pipi 中写入信息 通知该线程处理任务 - 工作线程 接到 主线程通知后,获取处理队列中的连接信息,与客户端进行通讯,创建针对客户端的 socket 读事件,获取客户端传递的 读写请求。client 一旦有消息传递过来,工作线程 捕获读事件后 通过
状态机
进行 事件流的处理 - 另外 memcached 还有 功能丰富的 辅助线程,可以根据配置进行按需启动
slab thread
slab 线程主要负责实现一个 slabautomove 的功能,memcahced内存区域分为不同的 slabclass,每个 slabclass 里面包含相同大小的内存块,slab automove 通过在后台运行一个线程, 这个线程会在某些 slabclass 内存不足时, 在 slabclass 之间移动内存来保证其正常工作。slab automove 主要涉及到两个线程 slab_maintenance_thread 和 slab_rebalance_thread,slab_maintenance_thread 决定是否需要进行一定, slab_rebalance_thread 进行实际的移动工作,两个线程通过一个 do_run_slab_rebalance_thread 来交互。具体 slab 机制后续 存储部分加以详细介绍,这里先 挖个坑…logger thread
logger 线程主要负责日志管理assoc thread
assoc线程主要负责 hash表 的管理,负责 hash 的扩展和维护conn timeout thread
conn timeout 线程主要控制允许空闲客户端的连接情况。memcached 对于连接上来的客户端,都会尽量保持,不会主动断开,但有些程序员写的程序都会开启新的连接,却不会关闭老的连接。导致无用的空闲连接仍然占用资源。因此,从1.4.27版本起 memcached 新增了idle_timeout
参数,可以设置 idle 时间,默认为0,不会主动断开exstore thread
exstore 线程主要作用于需要将数据存储至 内存之外 其他缓存介质 的处理,此功能需要编译选项--enable-extstore
,并且在启动的时候配置-o ext_path=/path/to/a/datafile,ext_page_count=100
lru thread
lru 线程,主要用来处理 LRU爬虫淘汰机制,这个后续会作为重点详细介绍,受控于settings.lru_crawler
变量,可以通过参数-o lru_crawler
设置为 true 进行启动
状态机
状态机在 memcached 线程模型中至关重要,主导整个 事件流的处理逻辑,状态机的处理函数为 drive_machine
,下面为简要的状态转移逻辑,具体 命令解析、数据输出 等我们后续会详细讲解(再次 挖坑…^-^)
- 初始状态为
conn_lisenting
,意为 主线程监听 来自客户端的请求 - 分发任务至 工作线程后开始进入
conn_new_cmd
状态,自此以后即为 工作线程的 处理范围。conn_new_cmd 状态下工作线程重置命令句柄,根据 conn_queue队列 pop 出任务,建立 client 端的 读监控事件 - 当 client 产生 send 动作,状态为
conn_read
,通过try_read_network
方法读取客户端报文,如果读取失败则 进入conn_closing
,关闭客户端连接;如果没有读取到任何数据,则进入conn_waiting
,继续通过事件监听 报文的到来;如果读取成功,状态进入conn_parse_cmd
进行命令解析,命令内容存入连接数据结构中的c->rbuf
容器中 conn_parse_cmd
状态的任务就是对 命令进行解析,通过try_read_command
方法对 c->rbuf 中存储的命令按照 “\n” 进行基础分割,如果 c->rbuf 中找不到 “\n” 则状态进入conn_waiting
继续等待后续报文的到来;如果报文完整,将切割出来的 命令交给process_command
进行处理process_command
命令通过 “\0” 来对单条命令进行详细拆分;而后的tokenize_command
这个方法就非常重要了,将命令拆解成多个元素(KEY的最大长度250),比如 get 命令将会跳转到process_get_command
这个方法,而add、set、append 等
命令将会跳转到process_update_command
方法;process_*_command
这代表一系列的命令处理逻辑对应的方法- 当命令按照对应处理方法运算完毕,会根据具体情况 判断是否需要继续读取 客户端报文,比如
set
命令就需要再次读取第二行数据,这样进入conn_nread
状态(继续类似conn_read
状态的循环);如果不需要再次读取报文,则进入conn_write
状态,准备数据写入连接描述符了(这中间还有一个逻辑,如果是 简单输出,类似 “END”、”ERROR”、”STORED” 等将直接进入conn_write
,而类似 get 数据这种复杂输出则进入conn_mwrite
状态);随后利用transmit
方法进行数据输出,同样道理,如果发生异常或者失败 进入conn_closing
- 数据写入客户端成功后,状态机再次进入
conn_new_cmd
,进行监听,循环状态
不过还有两个状态 conn_watch
、conn_swallow
不是那么常用,我自个也没真正明白其中的做法,有待后续补充~~~
如果仅仅是为了了解 memcached 的线程机制,这里就可以结束了,当然 需要知道详细实现细节的请 往下看...
源码解析
由于有些方法里的代码量比较大,我们这里按照 典型的代码片段进行解析,同志们可以根据文章提示的代码位置 和 代码里面的关键词 在源码中搜素,可能数据结构一些元素 看不太懂什么意思,没关系,先混个脸熟,后面看完回头再看过来就明白了
常用数据结构
CQ_ITEM
主要用于存储用户 socket 连接 的基本信息;主线程将用户的 socket 连接信息 封装成 CQ_ITEM,并放入工作线程的处理队列中,工作线程 得到 主线程 的 pipe 通知后,就会将队列中的 ITEM 取出来,创建 libevent 的 socket 读事件
CQ
连接处理队列
LIBEVENT_THREAD
每个工作线程的数据结构,每一个工作线程都有有这么一个自己的数据结构,主要存储线程信息、处理队列、pipe信息等
conn
连接信息数据结构,存储某个 socket 连接的 所有连接信息数据
线程池源码
libevent
基本事件 API
main
方法
主线程的 socket server 主要通过 server_sockets
这个方法创建,而 server_sockets 中主要调用了 server_socket
这个方法
conn_new
方法
然后我们继续看 event_handler
方法,里面主要是 drive_machine
状态机方法,另外下方涉及到的 命令解析、响应数据封装等模块我们后续会专门进行详细解读
dispatch_conn_new
工作线程任务分发方法
本文作者: wettper
本文链接: http://www.web-lovers.com/memcached-source-threads.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!