前言:
前两天看到高可用架构公众号里推荐了一篇58架构师沈剑写得文章,话题是关于 rpc client异步化的架构描述, 初看觉得方案不错.. 主要是在soa的rpc client里做了通信的异步化,这样理论上是可以承接更多的请求…
这里申明下,我没有喷58架构和沈剑大神的意思,只是看了这rpc异步化后产生了一些疑问… 公众号文章的详细地址 … 看过很多沈剑的文章,基本是那种偏高端又通俗易懂的系列…
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新. http://xiaorui.cc/?p=3774
我本人是有一些高并发请求的rpc开发经验,开发的语言和框架也尝试不少. python和golang都有用过。 实现较多的还是python。我自己用过的rpc框架,有那种开源的thrift 协议,也有自己使用http框架抽象出来的rpc。
沈剑说的关于同步,逻辑异步,异步的模式,我自己在曾经在线上调度系统中实践过,准确来说是过度过…. 使用python实现一些网络服务是比较蛋疼的,大家知道python是有gil限制的,同一时间只有一个线程在跑 ! 看起来网络服务只是单纯的网络服务,网络服务也就是一堆的IO,理论上完全可以单进程多协程的框架来实现,但问题是….. 你还是高看了python的性能…. 协程确实避免了因为io引起的上下文切换,但python作为解释性语言,由cpython虚拟机解释字节码就那些性能了… 当一个用户去访问服务的时候,其实还是会涉及到一些密集计算,比如粘包正则的匹配,路径的判断,序列化的解析,模板的生成,跟后端接口交互的开销等等…
好,既然我们知道python有gil限制,那么可以用多进程… 但是多进程之间的数据共享又是问题…. 当然了python multiprocessing提供了一些mmap ctypes的封装,可以实现数据共享…. 如果可以换语句,那么支持你用goalng or xxx , 如果就要用python, 那么多进程和协程最适合你了…
废话不多说,我们还是开聊 rpc client端异步化的处理.
首先沈剑说的同步模式的优缺点,我很赞成…. 同步模式很容易受网络和对端服务质量的影响而发生io阻塞。 大多数时候我们是不喜欢看到io堵塞的发生,尤其是对外服务… 如果单纯的一方在调用,那么同步异步倒是无所谓了. 看下图很明显得知,我们在socket send、recv的时候会收对端的影响. 不适合高并发的场景…
那么异步又是什么概念? 假设场景图如下.
client —- > rpc client —-> rpc server
当用户的请求过来的时候,我会把client的关于rpc相关请求扔到程序内置的queue队列里面,在rpc client里有一组内置的收发线程组来处理这些queue中的任务… 当处理完该请求后,我们会进行callback回调上下文,回调的函数可以在我们put queue时指定.. 这里的callback 是个含有连接的上下文.
这个架构耦合了两组线程,一组是对外的工作线程,一组专门处理rpc server及回调的线程. 但问题是,在这里处理收发的线程组是同步io的逻辑…
第一种情况:
如果 client 通过rpc client 调用rpc server的函数,对结果也很关心, 那么不管你的收发线程怎么异步, 对外的线程变为同步阻塞的状态,会block等待该rpc调用的完成…. 感觉不合理…
其实沈剑的文章没有描述完工作线程的对外的设计,我这里的设想有些假设的意思…
第二种情况:
如果 client 不关心rpc server 函数处理的结果,那么这就不会堵塞对外线程了,毕竟对外线程把任务扔给了queue, 剩下的事情就是让收发线程来处理了…
我认为的纯异步又是怎么回事…
你可以想成netty,tornado,或golang goroutine调度… 他们都实现了epoll的封装及关于事件上下文的管理。 总的来说需要这么几个组件支持,一个是 IO调度器,一个是 逻辑的生成器, 一个是 fd map generator .
我们可以把请求丢给所谓的协程处理,协程的调度器对网络IO是感冒的,他会帮你做任务流的切换. 你如果使用那种http的异步网络框架,那么只需要写你的逻辑就可以了。 如果是那种thrift 和 protocol buffer 通信协议 ,就需要实现我说的那三个基本组件. 我们每次去rpc 请求其实都是个网络io的操作,你要和对端产生网络IO就要有FD…
首先我们需要注册FD到IO 调度器, 需要注册该fd的读写事件及回调的函数.. 然后通过 IO 调度器 是用来监听fd的读写,当一个fd被唤醒的时候,我们通过map里找到相关的回调函数,回调就OK了… 这里的回调函数我们可以改为生成器… 生成器可以让你的逻辑更加清晰,不再需要一个个的回调函数来支撑…
超时的控制…
这个是需要控制实现的,在完全的同步IO里可以结束socket timeout. 那么逻辑异步里是如何实现的? 你需要实现一个时间管理器,可以用小顶堆来实现,由一个线程负责来刷这个时间堆.. 还在queue里面的,当pop的时候过滤下超时就可以了. 如果已经从queue pop出来了,在事件一层打上flag标记也可以实现.
连接池的必要性…
一定要有连接池,避免了重复创建连接的开销… 最好由一个线程去维护连接池的心跳…. TCP本身的keepalive的维护周期比较长,心跳最好是用逻辑层的,类似mysql的pong…
总结:
老生常谈 ! 如果想学习一些精巧的框架设计,那么可以自己造一个epoll的全套封装…. 如果只是想把事情干完? 那么可以选择一些异步框架来实现调度,你只需要写自己的逻辑就可以了…. 对于高并发的需求,我还是推荐直接用Golang搞定….