本篇主要介绍分布式框架的模块和其主要使用的通信方式zmq。
首先,对于任意的上游结点,它都有可能会把处理的结果发送到任意的一台下游结点中,同时如果下游结点有新增的结点,上游结点还能自动感知并处理。另一方面,任意的下游结点也会要和所有的上游结点保持心跳。如果使用原始的socket,解决上述的问题会比较麻烦,所以我们运用了zmq来解决上述的问题。Zmq具有下述的优点:1. 是一个跨协议的通信方式,目前支持inproc, ipc, tcp, tpic, multicast;2. 具有丰富且功能强大的设计模式,如req-reply,
pub-sub, push-pull, and router-dealer; 3. 高速异步的通信引擎。以上的三点优势均对我们整体的分布是系统产生了至关重要的影响。首先,之前提到过只有在node间传输的消息量不大的情况下,才对整体性能影响不大;但如果node间的传输信号量不能忽略,那么我们可以把两个服务放到一台机器上,通信协议由tcp改为ipc;其次,下游结点给上游所有结点的心跳可以通过push-pull的方式发送,而上游根据下游的负载来分发则可以使用router-dealer模式,丰富的传输模式极大简化的编程。最后,经本人实际测量发现,在不同机器之间zmq的通信传输速度几乎等同于socket。Zmq还有网络异常情况自动连接,可以先启动客户端再启动服务端等优势,增强了整体系统的可靠性。
下图是整个框架的模块示意图。其中,蓝色的方框为任一线程,socket send和worker线程可以是多个,而其它的只能为单线程(在config文件中配置);菊黄色的箭头是不同结点的传输,采用的是zmq方式通信,示意图中指画出了上游往下游的传输,下游往上游的心跳并没有显示在该示意图中;红色的箭头表示线程和数据存储单元的消息传输;黄色的方框则是线程间公用的数据存储单元。工作时,整个node的输入来源于两处,一是socket get模块的http请求,当获得请求后会记录socket号到socket
list中去,同时把具体的请求放到downstream list模块中;另一处是上游node给本node分派的任务,直接写到proxy模块中去。Worker线程会自己去downstream list里面去取任务,它的操作方式是执行完成一个再去取一个。Worker线程执行完一个任务后会根据情况分派输出,要么分发给下游的Node,要么传递给upstream list通过http协议发送回客户端;socket send从upstream list中获取处理的结果,通过http协议传递出去,socket send的处理方式也是执行一个再获取另一个。Socket
clean模块的作用是定期清理(close)socket list中的没有处理的socket,以免系统的文件描述符用尽。
该框架具有以下特点:1. 可以运用在整体服务中的任意结点,而不需要根据结点的不同位置或功能再添加其它结构;2. 整体框架无锁,性能优异;3. 部分模块可根据线上情况来配置数量,如worker等;4. 高可靠性和智能化。
下面我们来进一步观察两大核心模块,proxy和worker。
先来看proxy模块。它与其它结点有三个通信端口。Pub的作用是和所有的上游结点保持心跳,同时告知上游结点自己当前的负载量;sub的作用和pub相对,它能接收下游结点传来的心跳和负载信息;dealer的作用是接收上游结点分配的任务。RecordMessege()记录当前需要处理的任务,Where2PutProxy(message) 则根据情况分派消息给不同的公共数据模块。
再来看看worker模块。它只有一个router端口连接下游结点,并把本node处理后的任务分派出去,以便后续的处理;HandleRequest(message) 是业务处理的核心接口,RecordMessage()也是记录信息,Where2PutWorker(message) 则判断数据的流向,ChooseChildNode()则起到了负载均衡的作用。
最后提一下不同结点间的通讯协议,我采用的是zmq中自带的frame来区分不同的信号逻辑。
在一种分布式框架设计(三)中,我会分享上述两种类型的无锁数据模块的设计。