高并发环境下rpc client异步化的设计

前言:   

    前两天看到高可用架构公众号里推荐了一篇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搞定….  






大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注