高性能服务器设计——模块间通信

       在同一台机器上,不同进程之间的,数据通信方式主要有:socket、unix socket、消息队列、管道、共享内存等多种手段,各个通信方式均存在比较合适的使用场景。先做一个简要的介绍与分析:

     常见进程间的通信方式

       1) socket,主要用于机器之间的网络数据传输,当然也可以用在同一台机器不同进程之间,优点在于可以不做任何的修改,就可以做到跨机器之间的数据传输;
       2) unix socket,是对网络socket的一种延展。网络socket传输的数据,需要经过网卡设备网络协议栈、网络打解包、数据校验和计算、维护序号和应答等比较复杂的操作,而unix socket相对而言,虽然采用与网络socket基本相同的系统函数,但是却没有这些复杂的操作,只是将数据从一个进程Copy到另外一个进程;、
       3) 管道(FIFO),管道最常见的使用场景是shell中,在多个顺序执行的命令间传输数据时,所使用到的通信方式就是管道。在Linux实现管道的时候,实际上就是一个固定大小的内存缓冲区,一般是4k,超出管道的空间大小之后就会导致写入失败,直到对端从管道中读取数据释放占用的内存空间,才可以写入新的数据,为此FIFO在服务器开发开发中, FIFO经常被用于不同进程之间的命令通知,而不会承担大量数据传输的任务;
       4)消息队列(msgsnd、msgrcv),向消息队列中发送数据之后,send操作会立刻返回,当出现以下两种情况时,会导致send被阻塞直至两个条件均不成立时才返回,两个条件分别为:
            a) 消息队列中消息的大小超过限制;
            b) 消息队列中消息的个数超出消息;
            或者在调用发送或者接受函数时,指定这两个操作不执行等待操作,而是直接返回错误;
        5) 共享内存(shm),在高性能服务开发设计过程中,经常被使用到的一种进程间数据通信的方式。区别于管道和消息队列,共享内存在创建的时候,可以根据业务的需求指定共享内存的大小,当本机进程间需要传输大量数据的时候可以采用这种方式;

      框架中各模块间的通信方式
       1)代理进程与业务进程
          代理进程与业务进程之间所需要传输最主要的数据为:接受自外部请求的数据包,根据经验分析可以得出,在面对大量外部请求时,代理进程与业务进程之间会存在大量数据的传输,选择共享内存作为代理进程与业务进程之间的数据传输方式,无疑是最优的选择方案。
          在实际使用过程中,通常将共享内存实现为一种先进先出的消息队列,与IPC消息队列最大的区别在于:共享内存版消息队列的消息大小以及消息的总个数,完全可以根据业务场景来进行定制化设计。
          代理进程与业务进程之间,存在双向的数据流动,为此需要设计两条数据流动方向相反的共享内存消息队列。例如:消息队列A,代理进程负责写入,业务进程负责消费;消息队列B,业务进程负责数据的写入,代理进程负责数据的消费,两个消息队列各司其职,共同完成数据在代理进程与业务进程之间数据传输的任务。
          Ok,在代理进程与业务进程之间传输外部请求包的任务已经完成,那如何通知消息队列的消费者能够及时的获悉消息队列中已经存在数据更新这个动作。如果是管道或者消息队列,那我们可以将管道或者消息队列的句柄添加到网络异步模型的侦听队列中,来及时的获取数据变更消息。难不成,要编写一个定时逻辑,来不断检测共享内存消息队列是发生变更,为了非常及时的检测数据变更,意味着定时间隔非常小,将会给CPU带来不小的消耗。为了更加有效的解决这个问题,我们可以在代理进程与业务进程之间再架设两条管道,专门用于数据生产者向数据消费者发送,共享内存消息列队中已经添加了新的消息数据,消费者接到通知之后就可以直接从共享内存消息队列中来获取消息。

          为此,代理进程与业务进程之间的数据通信结构图如下:

            

          代理进程与业务进程之间数据传输的全流程为:
                 a)代理进程接受外部的请求包,检查请求包的完整性之后,将消息包压入到SHM消息队列A中;
                 b)消息包压入成功之后,通过管道向业务进程组发送消息包写入的通知;
                 c)业务进程组的一个进程“竞争”获取到管道的数据达到通知之后,从消息队列A中获取压入的数据包,进行对应的业务处理;
                 d)业务处理之后,将响应包压入到消息队列B中;
                 e)响应包压入消息队列B成功之后,向代理进程发送响应包写入通知;
                 f)代理进程获取管道的通知之后,从消息队列B获取响应包,回复给外部的请求客户端,至此完成整个数据的交互传输流程;

         2) 代理进程与监控进程
              代理进程与监控进程之间的主要数据通信为:
               a)代理进程向监控进程发送通知“我还活着”,同时需要告诉监控进程自己的进程ID(pid),在这里采用IPC通信中的任何一种类型均可以满足这个场景;
               b)代理进程向监控进程汇报当前系统的收包量,便于监控进程根据业务进程上报的消息处理量,来决定是否拉起更多的业务处理进程;

          3) 业务进程与监控进程
               业务进程与监控进程之间需要通信的数据内容,和代理进程与监控进程之间通信的数据基本一致。
               结合当前针对这种场景比较常用的数据通信模型,我们这里采用共享内存的方式来实现代理进程、业务进程与监控进程之间的数据通信。这块共享内存,在此我们可以称之为“统计信息卡”,代理(业务)进程,定时向“统计信息卡”中写入本进程对应的信息,类似于比较常见的心跳登记。监控进程定时扫描“统计信息卡”中的数据,发现代理(业务)进程未在规定的心跳时间范围内进行登记,会根据进程之前登记的pid,来检测该进程是否存在,以便做出是否拉取新的进程的决定。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页