前言
Tars是腾讯开源的微服务平台,包含了一个高性能的rpc框架和服务治理平台,TarsCpp是其C++版本。对于以C++为主要开发语言,同时还想深入了解rpc和微服务框架具体实现的同学来说,Tars是一个极佳的选择。
想像一下,如果你自己来设计一个rpc-client,都应该考虑哪些因素?
- 一个高效、可靠的调度器(epoll模型)
- 如何设计同步调用、异步调用
- 使用适当的协议来发送请求、解析结果
- 提供不同的选择服务节点的策略,包括但不限于轮询、hash、权重等
- 管理服务节点状态,包括不限于节点是否已经连接,是否正在连接中,连接是否超时,是否需要重连,连接超时时间设置,连接重连时间设置等
- 如何可靠的接收请求、发送结果,请求量怎么控制,接收到1.5个请求怎么办,请求发送完迟迟没收到结果怎么办
这些问题都可以从tars-rpc-client中寻找到实现答案
1 rpc-client概要设计
学习源码的一个重要目的是学习“别人家”模块或项目的设计思路,设计思路的珍贵之处在于其超脱了项目甚至语言的层次,可以迁移到其他地方。
tar-rpc-client主要由4个组件构成:ServantProxy,ObjectProxy,CommunicatorEpoll,AsyncProcThread,其中:
- ServantProxy:直接与使用者交互,提供简便易用接口
- ObjectProxy:封装网络层收发细节
- CommunicatorEpoll:提供收发调度功能
- AsyncProcThread:提供异步调用功能
四个组件的关系见图1。
图1
- 在主线程里,ServantProxy是工头,它承接客户需求并加以整理,然后按照一定顺序分给众小弟们(一个小弟是一个ObjectProxy)
- 小弟ObjectProxy在调度线程中依赖CommunicatorEpoll中的epoll模型高效有序的干活
- 如果是同步调用,ServantProxy会在主线程中等待,直到ObjectProxy在调度线程中完成请求发送和结果接收
- 如果是异步调用,主线程不会阻塞,主线程中注册的回调函数在回调线程AsyncProcThread中被执行
- 一个小弟ObjectProxy对应一个CommunicatorEpoll,一个ServantProxy可以对应多个ObjectProxy
- 一个CommunicatorEpoll可以对应多个AsyncProcThread
2 CommunicatorEpoll概要设计
CommunicatorEpoll在rpc-client设计中处于核心地位,它确认了client信息流的调度方式。
CommunicatorEpoll是一个封装后的epoll模型,CommunicatorEpoll设计巧妙之处在于将客户调用函数的动作也作为了epoll的触发条件。下面以图2说明CommunicatorEpoll的设计架构
图2
CommunicatorEpoll将epoll\_wait中得到的消息分为两类,一类是处理ObjectProxy消息,另外一类是处理EPOLLIN、EPOLLOUT、EPOLLERR消息。
最核心的系统调用函数也列在了图2上,包括紫色的epoll\_wait和epoll\_ctl、绿色的connect、send和readv。
图2中展示了一个最简单的函数调用流程,下面的序号对应了图2中箭头里的序号
- 当用户调用ServantProxy::Hello方法时候,ServantProxy将这个方法包含的内容组成一个msg
- 通过ObjectProxy将msg注册到CommunicatorEpoll中。
- epoll\_wait获取了通知,处理msg
- 链接server端,成功后获取connent fd
- 将connect fd注册到CommunicatorEpoll中
- 发送请求
- 接收结果
2.1 同步call
rpc\_client一般有两种基本的调用方式,sync\_call和async\_call,即同步call和异步call。先看下同步call的实现机制。
同步call实现机理较简单,只需要主线程、调度线程配合就能实现。见图3.
图3
- 主线程调用函数后阻塞等待调度线程的信号通知,
- 调度线程收到结果后,主备发送信号通知
- 发送信号通知
- 主线程接收到信号后,本次调用结束
2.2 异步call
异步call需要主线程、调度线程、回调线程三个组件,见图4
图4
- 主线程调用完方法后直接结束
- 调度线程接收到结果后,放入回调线程的队列\_msgQueue中
- 回调线程循环等待\_msgQueue中的msg,当有msg进入时,会使用pop\_front取出
- 调用回调函数处理msg
未完--待续
下一篇文章会继续ObjectProxy的设计思路