本文是的一篇 Codis 代码阅读笔记,涉及的是 codis-proxy 从接收 client 请求,转发给 redis(codis-server),然后返回给 client 的过程。Codis 代码开始阅读的是重构后的2.0版本,流程比较清晰高效,后来需要了解公司对 Codis 的使用修改情况,而线上系统是基于1.8的,这个版本的请求解析转发流程则有点晦涩,所以把分析结果记录下来,也作为 golang-server 开发的一个参考。
codis-config 在1.8/2.0版本之间接口、代码结构变化很少,zk角色功能变化较多,这部分不在本文范围之内。
这幅图是解析转发请求的顺序图(请不要在意细节):
其中以Chan结尾的代表结构体里的channel或list,其他为结构体里实现的函数,通过goroutine运行,其中:
- redisTunnel & WritingLoop 分别负责一个client连接的读写两端,redisTunnel函数实现在Server类里;
- TaskRunner与一个redis实例一一对应,因此proxy内对同一个redis的请求是串行的;
- Server代表当前运行的Proxy实例。
在这个架构里:
- session类对应client实体,每个client有一对 read-routine & write-routine 处理 client 的读请求和返回请求,客户端请求和命令结果分别对应的结构体是PipelineRequest & PipelineResponse;
- 请求 PipelineRequest 在 Server::reqCh 里串行化了,由一个唯一的 Server::handleTopoEvent-routine 分发(dispatch)给不同的后端 redis(TaskRunner)。
- 每个后端 redis 对应一个 TaskRunner,有两个routine: writeLoop & readLoop。
writeLoop routine 从 chan-in 读取 PipelineRequest 然后通过 socket 写给后端,同时从 chan-out 读取 ParseResp 并封装成 PipelineResponse 发送给 Session::backQ。
readLoop routine 从 socket 读取解析数据封装成 ParseResp 然后插入到 chan-out。 - 这里面 dispatch 分发过程是串行的。
- mget/mset/del 是串行执行的,不直接使用TaskRunner,而是新建一个独立的到本机服务的连接,转换成单key操作。并且是一个短链接,虽然代码里用到了pool object,但只是用来建立连接的。
3.0与1.8的差别在于 session::loopReader (相当于1.8的 read-routine )在自己的 goroutine 里分派 Request object 到对应 Backend (对应1.8的 TaskRunner )的队列中,同时使用了 Future 模式,session::loopWriter 阻塞在对应的 Reqest::WaitGroup 上,Backend 处理完调用 Request::WaitGroup.done() 通知 loopWriter-routine 处理结束。去掉了串行 dispatch 这个瓶颈。
我的注释版代码: github