论文部分内容阅读
摘要:为了应对民航业务扩展、业务复杂度增加的趋势,分析了当前常见的多线程通讯方式,提出一种基于共享内存的多进程纯异步通信方法。建立快速、可靠、可扩展、可独立部署并完全托管的消息队列服务,在保障传输可靠性、数据一致性的情况下,降低子系统间的耦合度,实现系统间的高效通信。通过部署少量进程就可以提供系统的吞吐量,降低交易响应时间。
关键词:多进程;异步;并发;通讯
中图分类号:TP319 文献标识码:A
文章编号:1009-3044(2021)27-0064-04
1 背景
随着民航业务板块的扩张、业务服务的增加、互联网技术的发展,企业系统平台逐步根据服务内容由集中的单体系统拆分成多个子业务系统。相较于集中式单体系统中模块间的数据交互,子业务系统间交互更为复杂, 在业务处理过程中,往往需要各业务系统之间分工合作,访问各子系统对外开放接口获取信息[1]。因此,对系统数据传输的吞吐量及可靠性也有了更高的要求。
2 多进程通讯现状
大部分的业务系统为保障数据的一致性,满足各业务系统间合作的需求,子系统间通常通过RPC同步调用服务,但在某些场景中,RPC同步调用的响应时间较长,造成资源的浪费,系统吞吐量下降。以AV航信查询系统为例:当用户要查询北京到洛杉矶的航班时,AV系统需要同时访问Delta、Southwest航空公司的航班信息,整合后返回给调用方。如图所示:
假设业务处理时间为0秒,Delta响应时间为2秒,Southwest响应为3秒。那么,一个请求的响应时间为5秒。我们系统的最大并发量为3个请求,那么当并发请求数大于3时,剩余的请求只能堆积在消息队列中。其次,3个进程需要花费5秒的时间在等待應答,并且不做任何其他事情。这将导致系统的吞吐量遇到瓶颈,且系统的CPU、内存使用率低。由此可见,RPC服务调用虽然能够满足实时调用的业务场景,但针对上述异步、业务操作复杂的场景,业务系统的性能目标无法满足。
为了提高吞吐量、降低响应时延,现有技术提出了通过增加进程个数来提高吞吐量的半同步通讯方式。该方式是在上述方案的基础上进行的改进,将两次同步调用变为异步调用[2]。可以同时发送两个请求,再获取应答。
如上图所示,这种方案的处理时间取决于外部系统的最大响应时间,即3秒。
然而,这种优化方案依然存在问题。首先,这种方案无法提前预估并发量。高峰期时并发量大,而低峰期时并发量很少。而在低峰期部署大量的处理进程反而会导致资源的浪费[3]。“部署多少进程合适?”的问题依然没有得到解决[4]。其次,进程在等待应答的3秒过程依然无法处理其他请求,意味着其他请求依然需要排队。
3 多进程异步并发通讯设计
针对上述现有技术的问题,本文提出一种基于共享内存的多进程纯异步通信方法,抽取进程状态并存储于在传输过程中动态创建的共享内存组件存储中,释放无状态进程的等待时间,建立快速、可靠、可扩展、可独立部署并完全托管的消息队列服务,在保障传输可靠性、数据一致性的情况下,降低子系统间的耦合度,实现系统间的高效通信。一方面部署少量进程即可以增加吞吐量,另一方面在等待异步应答的过程中依然可以响应其他交易请求。
3.1 系统组成
面向多进程的纯异步通信装置由服务消费方、异步消息交互服务和服务提供方组成。其中异步消息交互服务分为4个组件,分别是数据接入服务组件、消息队列组件、异步通信进程池和共享内存组件。
数据接入服务组件:负责接受服务消费方各种形式的并发连接请求,并将请求放到对应的消息队列中。
消息队列组件:面向流量峰值的处理方案,当消息涌入时,首先将请求或异步应答存放入消息队列中,再根据流量控制策略进行后续处理。
异步通信进程池:每个进程有一个I/O线程,负责与服务提供方进行直接通信,根据消息中的参数信息,与对应的主服务端或从服务端建立连接、异步发送相关消息,接收服务提供方的应答结果,并存放至消息队列中。
共享内存组件:共享内存组件在数据交互的过程中动态创建异步交互上下文,根据业务特征存储某组数据交互的上下文,及中间结果。
3.2 实现原理
面向多进程的纯异步通信方法在整个交易过程中采用完全异步的方式,没有任何阻塞点。在进程发起异步请求后,进程并不等待,而是继续处理其他请求;当异步应答回来后,再通过读取上下文,恢复现场,继续处理上次交易。
具体进程处理过程如下图所示:
步骤1. http接入收到请求后将请求放入消息队列
步骤2. 进程读取消息队列后,
1)如当前消息属性为请求,则生成全局交易号,保存原始请求报文,应用需要暂存的kv数据,以及要等待的异步应答个数等上下文数据,发起异步调用,转步骤2。
2)如当前消息属性为应答,读取上下文。
①如果应答未全部接收完毕,则保存上下文,转步骤2
②如果应答全部接收完毕,则删除上下文,转步骤3
步骤3.依据原始请求,及收到的全部异步应答,整合数据,返回消费者。
注:异步应答回来后,异步通讯线程会将异步应答放入消息队列。
4 共享内存
实施方案的关键问题在于共享内存结构如何快速定位,在此选择了hash+链表的方式。此外,由于应用保存上下文的数据长度的不确定性,需要实现基于共享内存的slab内存分配算法。
4.1 创建共享内存
进程间通讯采用SysV SHM共享内存/SEM信号机制[5]。其共享内存结构如图6所示。
1)AsynCtl:记录整个共享内存大小及hash桶偏移量 2)AsynHead:记录每个交易的流水号及原始请求信息
3)AsynCtx:应用上下文,是一对键值对
4)AsynData:原始数据
注:
BUCKETENTRY为hash桶的大小;
每个group共用一个sem信号灯。
4.2 基于共享内存偏移量slab内存分配算法
AsynHead AsynCtx AsynData均采用slab内存分配算法,本slab内存分配算法实现为slab内存分配算法的偏移量实现。使用偏移量实现的原因在于每个进程attach到共享内存的逻辑地址是不一样的,因此只能采用偏移量的实现。
多个线程间的内存实施共享时可以采用Slab原则进行管理,Slab原则的思想是将内存切割为多个槽位,属于同一页的内存切割后形成容量相等的槽位,属于不同页的内存块切割后的槽位容量可以相等也可以不相等。切割完成之后采用一个页数组记录不同的内存页所处的地址,使用另一个槽位数据记录属于同一个页面的内存槽位[6]。在寻址时先计算出槽位所属的页,然后到页数组中查找页地址,即目标槽位的基地址,最后在基地址上加上目标槽位的偏移量即可得到目标槽位的内存地址[7]。
SLAB原则将内存空间按照容量进行分类,例如当程序申请一块容量为100KB的内存块时,内存管理器就从大小为100KB的内存块集合中随机选择一个内存块返回给申请者。当应用程序释放一块100KB大小的内存块时,内存管理器会将这块内存返回到容量为100KB的内存集合中去。当另一个程序申请一块容量为50KB大小的内存块时,内存管理器就从大小为50KB的内存集合中随机选择一个内存块返回给调用者,当程序释放内存时内存管理器也仍然将这块内存返回到50KB的内存集合中去,从而避免了内存碎片的产生。
头部数据结构汇总了SLAB内存管理机制的基础数据,这些基础数据是内存分配、地址计算等处理的依据,SLAB头部数据结构的伪代码如下所示:
typedef struct {
size min_capacity; // 內存槽位容量最小值
size offset; // 内存槽位偏移量
Arr_page *pgs; // 页内存块基地址数组
Arr_page_t *slot; // 内存槽位数据
char *head; // 空闲内存块起始地址
char *tail; // 空闲内存块结束地址
...
}slab_head
初始化过程中,对如下数据进行处理:
pgs数组:计算pgs数组长度并设置数组成员变量初始数据;
slot数组:计算slot数组长度并设置数组成员的变量初始数据;
内存槽位容量最小值:根据配置设置单个内存槽位的最小容量;
内存槽位偏移量:根据当前页的内存槽位大小设置偏移量[8]。
当申请一页时,则将pgs数组第一个元素从free链表中分离出去。当调用者申请第二页时,内存分配器将pgs数组的第2、3位置上的元素进行合并,将合并后的页面作为一个整体移出head链表[9]。当需要回收某个内存页时,使用一个链表将所有被回收的页面连接起来,挂载到空闲内存块结束地址,并更新tail指针。
5 结束语
本文提出的面向多进程的纯异步通讯方法,通过部署少量进程就可以提供系统的吞吐量,降低交易响应时间。
1)构建了一个与业务应用组件、三方服务平台完全解耦的、可被托管的消息队列服务。在消息通信的过程中,用户只需关心核心的数据传输流程,而不要求其他需要依赖的应用平台始终可用——接收到返回消息后,即可执行下一组交互,无须等待本次业务的最终结果。
2)进程的业务处理过程完全采用异步通讯机制,没有阻塞点。系统的吞吐量仅取决于上下文的大小,通过调整上下文空间大小,即可提高系统吞吐量。
3)提供了消息的并发处理机制。每个进程在发起异步请求后,当前交易流程中断,可继续处理其他交易请求;待异步应答回来后,继续中断的交易。
4)自动在共享内存中生成“交易上下文”的方式,将状态存储在上下文中,构建了无状态的进程处理模型,解决了由系统拥塞导致的一系列问题。
参考文献:
[1] 陶志勇,王如龙,张锦.基于多进程的区域间通信方法[J].计算机系统应用,2013,22(4):174-177.
[2] Shi J H,Zhang S L,Chang Z G.The security analysis of a threshold proxy quantum signature scheme[J].Science China Physics,Mechanics and Astronomy,2013,56(3):519-523.
[3] 刘新强,曾兵义.用线程池解决服务器并发请求的方案设计[J].现代电子技术,2011,34(15):141-143.
[4] 萧毅鸿,周献中,朱亮,等.基于改进层级任务网络规划的Web服务自动组合[J].江苏大学学报(自然科学版),2012,33(1):77-82.
[5] 李朋辉,严军.多进程间的数据通讯方式的探讨[J].电脑知识与技术,2012,8(8):1821-1823.
[6] 宋雅琴,郭志川.Nginx Slab算法研究[J].网络新媒体技术,2018,7(2):54-61.
[7] 王峰,张智金,杨春媚,等.一种多进程并发解算差分服务器系统及其实现方法:CN106095601B[P].2019-08-30.
[8] 涂海,郭书伟.并发和异步任务处理方法及其设备,CN111131499A[P].2020.
[9] 王晨,刘学纵.基于系统内核与共享内存的守护进程实现研究[J].工业控制计算机,2020,33(3):115-117,121.
【通联编辑:谢媛媛】
关键词:多进程;异步;并发;通讯
中图分类号:TP319 文献标识码:A
文章编号:1009-3044(2021)27-0064-04
1 背景
随着民航业务板块的扩张、业务服务的增加、互联网技术的发展,企业系统平台逐步根据服务内容由集中的单体系统拆分成多个子业务系统。相较于集中式单体系统中模块间的数据交互,子业务系统间交互更为复杂, 在业务处理过程中,往往需要各业务系统之间分工合作,访问各子系统对外开放接口获取信息[1]。因此,对系统数据传输的吞吐量及可靠性也有了更高的要求。
2 多进程通讯现状
大部分的业务系统为保障数据的一致性,满足各业务系统间合作的需求,子系统间通常通过RPC同步调用服务,但在某些场景中,RPC同步调用的响应时间较长,造成资源的浪费,系统吞吐量下降。以AV航信查询系统为例:当用户要查询北京到洛杉矶的航班时,AV系统需要同时访问Delta、Southwest航空公司的航班信息,整合后返回给调用方。如图所示:
假设业务处理时间为0秒,Delta响应时间为2秒,Southwest响应为3秒。那么,一个请求的响应时间为5秒。我们系统的最大并发量为3个请求,那么当并发请求数大于3时,剩余的请求只能堆积在消息队列中。其次,3个进程需要花费5秒的时间在等待應答,并且不做任何其他事情。这将导致系统的吞吐量遇到瓶颈,且系统的CPU、内存使用率低。由此可见,RPC服务调用虽然能够满足实时调用的业务场景,但针对上述异步、业务操作复杂的场景,业务系统的性能目标无法满足。
为了提高吞吐量、降低响应时延,现有技术提出了通过增加进程个数来提高吞吐量的半同步通讯方式。该方式是在上述方案的基础上进行的改进,将两次同步调用变为异步调用[2]。可以同时发送两个请求,再获取应答。
如上图所示,这种方案的处理时间取决于外部系统的最大响应时间,即3秒。
然而,这种优化方案依然存在问题。首先,这种方案无法提前预估并发量。高峰期时并发量大,而低峰期时并发量很少。而在低峰期部署大量的处理进程反而会导致资源的浪费[3]。“部署多少进程合适?”的问题依然没有得到解决[4]。其次,进程在等待应答的3秒过程依然无法处理其他请求,意味着其他请求依然需要排队。
3 多进程异步并发通讯设计
针对上述现有技术的问题,本文提出一种基于共享内存的多进程纯异步通信方法,抽取进程状态并存储于在传输过程中动态创建的共享内存组件存储中,释放无状态进程的等待时间,建立快速、可靠、可扩展、可独立部署并完全托管的消息队列服务,在保障传输可靠性、数据一致性的情况下,降低子系统间的耦合度,实现系统间的高效通信。一方面部署少量进程即可以增加吞吐量,另一方面在等待异步应答的过程中依然可以响应其他交易请求。
3.1 系统组成
面向多进程的纯异步通信装置由服务消费方、异步消息交互服务和服务提供方组成。其中异步消息交互服务分为4个组件,分别是数据接入服务组件、消息队列组件、异步通信进程池和共享内存组件。
数据接入服务组件:负责接受服务消费方各种形式的并发连接请求,并将请求放到对应的消息队列中。
消息队列组件:面向流量峰值的处理方案,当消息涌入时,首先将请求或异步应答存放入消息队列中,再根据流量控制策略进行后续处理。
异步通信进程池:每个进程有一个I/O线程,负责与服务提供方进行直接通信,根据消息中的参数信息,与对应的主服务端或从服务端建立连接、异步发送相关消息,接收服务提供方的应答结果,并存放至消息队列中。
共享内存组件:共享内存组件在数据交互的过程中动态创建异步交互上下文,根据业务特征存储某组数据交互的上下文,及中间结果。
3.2 实现原理
面向多进程的纯异步通信方法在整个交易过程中采用完全异步的方式,没有任何阻塞点。在进程发起异步请求后,进程并不等待,而是继续处理其他请求;当异步应答回来后,再通过读取上下文,恢复现场,继续处理上次交易。
具体进程处理过程如下图所示:
步骤1. http接入收到请求后将请求放入消息队列
步骤2. 进程读取消息队列后,
1)如当前消息属性为请求,则生成全局交易号,保存原始请求报文,应用需要暂存的kv数据,以及要等待的异步应答个数等上下文数据,发起异步调用,转步骤2。
2)如当前消息属性为应答,读取上下文。
①如果应答未全部接收完毕,则保存上下文,转步骤2
②如果应答全部接收完毕,则删除上下文,转步骤3
步骤3.依据原始请求,及收到的全部异步应答,整合数据,返回消费者。
注:异步应答回来后,异步通讯线程会将异步应答放入消息队列。
4 共享内存
实施方案的关键问题在于共享内存结构如何快速定位,在此选择了hash+链表的方式。此外,由于应用保存上下文的数据长度的不确定性,需要实现基于共享内存的slab内存分配算法。
4.1 创建共享内存
进程间通讯采用SysV SHM共享内存/SEM信号机制[5]。其共享内存结构如图6所示。
1)AsynCtl:记录整个共享内存大小及hash桶偏移量 2)AsynHead:记录每个交易的流水号及原始请求信息
3)AsynCtx:应用上下文,是一对键值对
4)AsynData:原始数据
注:
BUCKETENTRY为hash桶的大小;
每个group共用一个sem信号灯。
4.2 基于共享内存偏移量slab内存分配算法
AsynHead AsynCtx AsynData均采用slab内存分配算法,本slab内存分配算法实现为slab内存分配算法的偏移量实现。使用偏移量实现的原因在于每个进程attach到共享内存的逻辑地址是不一样的,因此只能采用偏移量的实现。
多个线程间的内存实施共享时可以采用Slab原则进行管理,Slab原则的思想是将内存切割为多个槽位,属于同一页的内存切割后形成容量相等的槽位,属于不同页的内存块切割后的槽位容量可以相等也可以不相等。切割完成之后采用一个页数组记录不同的内存页所处的地址,使用另一个槽位数据记录属于同一个页面的内存槽位[6]。在寻址时先计算出槽位所属的页,然后到页数组中查找页地址,即目标槽位的基地址,最后在基地址上加上目标槽位的偏移量即可得到目标槽位的内存地址[7]。
SLAB原则将内存空间按照容量进行分类,例如当程序申请一块容量为100KB的内存块时,内存管理器就从大小为100KB的内存块集合中随机选择一个内存块返回给申请者。当应用程序释放一块100KB大小的内存块时,内存管理器会将这块内存返回到容量为100KB的内存集合中去。当另一个程序申请一块容量为50KB大小的内存块时,内存管理器就从大小为50KB的内存集合中随机选择一个内存块返回给调用者,当程序释放内存时内存管理器也仍然将这块内存返回到50KB的内存集合中去,从而避免了内存碎片的产生。
头部数据结构汇总了SLAB内存管理机制的基础数据,这些基础数据是内存分配、地址计算等处理的依据,SLAB头部数据结构的伪代码如下所示:
typedef struct {
size min_capacity; // 內存槽位容量最小值
size offset; // 内存槽位偏移量
Arr_page *pgs; // 页内存块基地址数组
Arr_page_t *slot; // 内存槽位数据
char *head; // 空闲内存块起始地址
char *tail; // 空闲内存块结束地址
...
}slab_head
初始化过程中,对如下数据进行处理:
pgs数组:计算pgs数组长度并设置数组成员变量初始数据;
slot数组:计算slot数组长度并设置数组成员的变量初始数据;
内存槽位容量最小值:根据配置设置单个内存槽位的最小容量;
内存槽位偏移量:根据当前页的内存槽位大小设置偏移量[8]。
当申请一页时,则将pgs数组第一个元素从free链表中分离出去。当调用者申请第二页时,内存分配器将pgs数组的第2、3位置上的元素进行合并,将合并后的页面作为一个整体移出head链表[9]。当需要回收某个内存页时,使用一个链表将所有被回收的页面连接起来,挂载到空闲内存块结束地址,并更新tail指针。
5 结束语
本文提出的面向多进程的纯异步通讯方法,通过部署少量进程就可以提供系统的吞吐量,降低交易响应时间。
1)构建了一个与业务应用组件、三方服务平台完全解耦的、可被托管的消息队列服务。在消息通信的过程中,用户只需关心核心的数据传输流程,而不要求其他需要依赖的应用平台始终可用——接收到返回消息后,即可执行下一组交互,无须等待本次业务的最终结果。
2)进程的业务处理过程完全采用异步通讯机制,没有阻塞点。系统的吞吐量仅取决于上下文的大小,通过调整上下文空间大小,即可提高系统吞吐量。
3)提供了消息的并发处理机制。每个进程在发起异步请求后,当前交易流程中断,可继续处理其他交易请求;待异步应答回来后,继续中断的交易。
4)自动在共享内存中生成“交易上下文”的方式,将状态存储在上下文中,构建了无状态的进程处理模型,解决了由系统拥塞导致的一系列问题。
参考文献:
[1] 陶志勇,王如龙,张锦.基于多进程的区域间通信方法[J].计算机系统应用,2013,22(4):174-177.
[2] Shi J H,Zhang S L,Chang Z G.The security analysis of a threshold proxy quantum signature scheme[J].Science China Physics,Mechanics and Astronomy,2013,56(3):519-523.
[3] 刘新强,曾兵义.用线程池解决服务器并发请求的方案设计[J].现代电子技术,2011,34(15):141-143.
[4] 萧毅鸿,周献中,朱亮,等.基于改进层级任务网络规划的Web服务自动组合[J].江苏大学学报(自然科学版),2012,33(1):77-82.
[5] 李朋辉,严军.多进程间的数据通讯方式的探讨[J].电脑知识与技术,2012,8(8):1821-1823.
[6] 宋雅琴,郭志川.Nginx Slab算法研究[J].网络新媒体技术,2018,7(2):54-61.
[7] 王峰,张智金,杨春媚,等.一种多进程并发解算差分服务器系统及其实现方法:CN106095601B[P].2019-08-30.
[8] 涂海,郭书伟.并发和异步任务处理方法及其设备,CN111131499A[P].2020.
[9] 王晨,刘学纵.基于系统内核与共享内存的守护进程实现研究[J].工业控制计算机,2020,33(3):115-117,121.
【通联编辑:谢媛媛】