写在前面
这应该是phxrpc代码阅读系列正文的最后一篇。通过阅读代码,发现了自己在知识上的若干不足。
临渊羡鱼,不如退而结网。接下来可能会在网络编程方面再下一点工夫。请大家期待下一个系列吧。
其实真没人读,我就是在骗自己。
先补充一点 - 代码生成
protobuf并不包含RPC的实现,但是它可以声明rpc。客户端和服务端需要实现RPC接口,来实现通信。
phxrpc使用proto文件来定义接口,然后解析并使用代码模板进行生成。
这里我们不讨论代码生成的细节,因为pb实在太过流行,代码生成的方法也有不少的流派。并且用C++来做代码生成,真心不是我的菜。
想了解更多,可以参考这篇博客。
工作流程 - 客户端
客户端与服务器的通信有如下的特点:
- 连接少
- 负载少
- 通信的主动方
所以,所有的网络交互相关的内容可以托管给网络库中的协程。每个协程主动运行一段时间后,主动放弃CPU时间,将控制权交还给主控制流的epoll。
所以协程中不能有CPU密集的运算,幸好面对开发者,phxrpc并不暴露内部函数,而是将CPU密集的运算分配给工作线程来完成。
工作流程 - 服务端
服务器的通信有以下的特点:
- 连接多
- 负载多
- 通信的被动方
- 响应时间敏感
这里说一下响应时间的问题,响应时间是和负载多对应的。客户端一般只负责发请求,其响应时间并不在整个系统中占主导地位,换句话说,客户端发请求是完全有主动权的。而服务端负责响应请求,需要经过CPU运算,或者有一些服务的级联或扇出操作,所以响应时间是不可控的。
由上面我们可以了解到,服务器端肯定需要与客户端不同的策略来处理连接与响应。
首先,一个独立的线程来同步accept请求,这个线程大部分的时间都block在accept()
里,负载比较低。
在accept到fd后,将其压入到调度器中。之后fd会hang在IO循环上,直到有请求到来。与此同时,协程会调度其它的fd。
请求到来后,IO线程会把请求加入队列中,把自己从epoll调度中删除。之后就开始睡觉觉。睡醒了之后,发现响应没到碗里来,就shutdown连接。(我觉得这里应该加入重试,例如三次失败再断线)
如果响应来了,先会调用一个激活fd的操作,将响应放到封装fd的结构体中,再将自己放回到epoll调度,最后顺手给epoll发一个信号。当fd可写时,再把响应返回给客户端。
由于phxrpc使用的通信协议是HTTP,所以不需要考虑分包的问题。
可能的改进
调度器的超时
int nfds = epoll_wait(epoll_fd_, events, max_task_, 4);
这段调度器中的代码,生生的把IO复用模型改成了轮询。猜测这是为了照顾超时定时器,但实际上这个并无必要。我们可以使用timerfd
和eventfd
来取代定时器。但是这只是一种猜测,这种写法可能也是有性能上的考虑吧。
代码风格
代码风格可能是一个比较泛的问题,但是phxrpc的代码我能明显的感觉到编码风格的不同。以及元素层次不明朗,上下级模块相互耦合的情况。
没有测试!没有测试!没有测试!
因为phxrpc没有测试代码,所以我严重怀疑这就是一个玩票的项目,没有(或暂时没有)在生产环境上运行。
如果有可能的话,请加上性能测试!请加上功能测试!请加上性功能测试!
写在后面
时间仓促,水平有限。写点东西,方便大家学习交流。
如果我写的哪里不对,99%的锅归我傻逼,1%的锅归phxrpc代码没写测试。欢迎大家多做自我批评。
好啦好啦,就到这里吧。
Comments
comments powered by Disqus