1.SEMQ:
SEMQ是分布式的消息队列。作为供应宝系统的传输层,每个供应宝服务端节点都有一个或多个SEMQ单元,每个SEMQ单元由一个任务和一或多个存储组成,另外提供一组访问接口。
平台支持多实例,多存储。
私有本地服务器实现为单实例单存储,可以满足一家机构应用的需要.
虚拟服务器将来也可能需要支持多实例多存储,以均衡负载提高性能。
对单实例,单存储实例的服务器,semq_id=1,semq_db_id=1.
这里不严格区分SEMQ和SEMQ单元。
2.UMX分层
数据传输分2个层次:应用层,传输层(包括传输属性,路由,会话属性).
传输属性:是否要求有序,可靠,决定网络层采用的协议(TCP或者UDP)
路由:是否支持存储转发,决定到目标不可达时的行为,是否持久化存储后待目标上线后再发送;消息是否需要节点间确认,确认所需要的定位键
会话属性:是否需要端-端的确认,确认所需要的定位键;是否加密
应用层只表现和应用有关的内容。签名归属于应用层功能,传输层的数据签名意义不大。
UMX增加一个包层属性LAYER_MASK:区分应用包(0)还是传输层包(1)。
传输层的引入是是对分布式应用的扩展。对于基于hotfox的简单的C/S应用,传输层不是必须的。
2.1传输层协议(UMX-T)格式:
.是否可靠:@Reliable=1|0 默认1
.存储转发:@SaveFwd=1|2|3
.是否送达确认:@Ack2=1|0
.应用关联键: @AppKey(应用自定义:由域,数据类型,子类型,键4个项组成)
.加密属性:@Encrypt=1|0 是否加密,@SessionKey 会话密钥。用会话密钥加密应用数据,会话密钥用接收方公钥加密,根据端点可确定证书
.源定位键:@LK0(节点类型,节点ID,节点SEMQ ID,节点SEMQ存储ID,消息记录ID)
.平台定位键:@LK1(节点SEMQ ID,节点SEMQ存储ID,消息记录ID)
.应用层(@AppData):如果@Encrypt=1,则该包用@SessionKey已用加密
源定位键,平台定位键相当于路由表,记录消息的传递路径.
传输层UMX的源地址为发送时节点的地址,假设消息M从A发送给B,在平台保存后,由平台转发时,消息的源地址是平台而不是A,但应用层的消息还是A。
2.2传输属性
(1)存储转发属性:
.目标不在线时丢弃(1)
.目标不在线时存储(2)
.必须确保发送出去(3):对于必须送达的消息,要写入待发送消息队列,以便确认,询问和重发
对于平台,如果目标方在线,为提高效率,直接转发。确认过程在源和目标方之间进行,平台不参与。
存储转发属性代替了原来的可靠等级。
确保发送出去要利用传输确认和询问机制来保证,原来的是否确认合并到此属性中了.
(2)送达确认属性:
通信端点之间消息送达的确认,和原来的是否确认含义不同。如果通信路径只有2个节点,则可合并传输确认和送达确认。
相对于送达确认,必须确保送出的确认称为“传输确认”。
或者把送达确认称为端-端确认,把传输过程其它节点之间的确认称为传输确认。
如果端-端是直接通信的,则端-端确认和传输确认是等价的。
3.SEMQ机制
SEMQ需要实现包括消息存储,发送,询问,重发,确认,确认的确认,清理的若干机制。
存储到SEMQ的消息有以下情况:
.要求目标不在线时存储的消息,目标没在线时
.必须确保发送出去的消息的发送源
.需要送达确认的消息的发送源
发送状态(SS_STATUS): SS_INIT(初始,未发送),SS_SENT(已发送),SS_ACK(已确认),SS_ACK2(送达确认)
确认状态(AS_STATUS):SS_INIT(初始,未确认),SS_ACK(已确认)
3.1应用示例:
以单据传输为例,假设从A机构(机构ID:10008)通过平台发送给B机构(机构ID:56001)发送S0001号订单。
。本地服务器A:
--抽取单据后写入本地SEMQ存储的记录,消息用UMX-T协议表示(是否可靠(1),存储转发(3),是否送达确认(1),固定投递(1),加密)。源和目标分别为A和B。
--如果本地服务器已连接平台,则读取发送状态(SS_STATUS)为SS_INIT的记录,准备发送
--解析UMX-T消息,根据传输控制信息选择通道
--发送前在UMX-T协议包中设置源定位键
--执行发送,激发数据已发送事件(应用层可以对此事件进行响应),修改SS_STATUS=SS_SENT
发送后,如果A:
(1)接收到Ack-Fwd:
修改发送状态为SS_ACK_FWD.设置询问时间为当前时间。
对此状态的消息,以后直接向目标询问是否接收到。
必须等待B返回的询问结果,A对结果进行处理。如果B接收到,则表示已经送达(SS_ACK2);否则,执行重发(修改发送状态为SS_INIT)
(2)接收到Ack_Save:
修改发送状态为SS_ACK。设置询问时间为当前时间。
基于可靠的传输机制,消息已到达平台,平台必须保证B会接收到(除非B永远不上线)---如果数据长期未发送到B,这种情况需要通过工具发现,由人工干预。
预期应该接收到来自B的Ack2消息,表示B已经接收。
如果没有接收到来自B的Ack2的消息,启动询问机制
(3)没有接收到任何确认:
向目标询问。
(4)接收到来自B的Inquiry-Resp(询问响应)
如果对方已接收,则修改SS_STATUS=SS_ACK2;否则,修改SS_STATUS=SS_INIT,使重发。
。平台
平台接收消息后,检查目标是否在线。
如果目标在线,则转发,返回A一个已经转发的确认消息(Ack-Fwd)
如果目标不在线,则保存,返回A一个已经存储的确认消息(Ack-Save)
对于存储的消息,当目标上线后发送,发送前设置平台定位键。
如果接收到B的Ack,表示B已经接收,修改SS_STATUS=SS_ACK,同时返回ReAck给B,表示已经收到B的确认。
。本地服务器B
保存到待确认消息表中,把UMX-T中的定位键分别写入,并分别返回确认,对平台返回Ack,对发送方返回Ack2。平台和发送方的确认状态为AS_INIT(初始)。
如果接收到平台的ReAck,则修改待确认消息平台确认状态(P_ACK_STATUS)为“已确认”(AS_ACK);
如果接收到A的ReAck2消息,则修改发送方确认状态(S_ACK_STATUS)为AS_ACK;
3.2询问机制
询问的时机:目标在线,离上次询问时间超过一定时间间隔(单位:秒),该时间可配置(如设置为15秒)
询问的内容:询问的标的用(端点类型,端点ID,本地SEMQ ID,本地SEMQ DB ID,object_id)唯一确定。其中端点类型,端点ID可以省略(消息包头包含了此信息)
询问请求处理成功时,询问的结果有2种:对方已收到或者对方未收到。
如果对方已收到,则修改发送状态为SS_ACK2,同时返回ReAck2给询问发起方。
否则,启动重发。
有一种情况,超过了设置的询问间隔,但B才上线,源消息和A发出的询问消息到达B和处理的时序不能保证,导致可能做不必要的重发,但几率较低。
而且,应用层要能够保证处理重发的消息不会产生错误的逻辑,如一张订单发送两次仍然是一张订单。
3.3消息清理
解决及时清理SEMQ中存储已经没有作用的数据,前提是确保消息到达目标。
SEMQ存储的数据有(1)待发送消息队列 (2)待确认消息队列,只有本地服务器才有待确认消息队列.
对于平台,可以删除待发送消息队列的状态为SS_ACK的记录,消息已成功送达。即使目标方没有接收到平台返回的ReAck,删除也是安全的。
对于本地服务器,
待发送消息队列:可以删除(SS_STATUS=SS_ACK&&送达确认=0)||(SS_STATUS=SS_ACK2&&送达确认=1)的记录
待确认消息队列:可以删除(P_ACK_STATUS=AS_ACK&&S_ACK_STATUS=AS_ACK)的记录,如果对应的需要确认的标志为1的话。
如果本地服务器没有接收到ReAck2/ReAck,通过询问机制确定。
被询问一方有3种结果:以向平台询问为例
(1)发送状态已经是SS_ACK
(2)记录已经不存在:已经确认并删除
(3)发送状态为SS_SENT:这表示平台发送,询问方接收后返回的确认没有被平台接收到,且平台还未发起询问
对于前2种情况,询问方可以修改AS_STATUS为AS_ACK.
对于后者,被询问方可以直接把发送状态修改为SS_ACK.询问方可以删除
如果同时有对平台和发送方的确认需求,则要同时满足2个确认状态条件
4. 实现
待发送消息队列:tb_0031
待确认消息队列: tb_0030
4.1本地服务器实现:
。询问机制:
---向平台发送Inquiry
.如果SS_STATUS=SS_SENT且离上次询问时间(初始时询问时间等于发送时间)间隔超过指定时间
---向目标发送Inquiry
.如果SS_STATUS=SS_ACK_FWD或者SS_ACK且离上次询问时间间隔超过指定时间
。消息处理:
ReAck2:接收到发送方对Ack2的确认
Ack2:接收到目标方接收到消息的确认
Inquiry-Resp:处理询问结果
Inquiry:响应对方询问
Ack-Fwd:平台已经转发
Ack-Save:平台已经保存
。清理:
4.2 平台服务器实现:
。消息处理:
Ack: 接收方确认,返回ReAck
Inquiry-Resp:询问结果处理
。询问机制:向目标询问
4.3 消息定义:
4.3.1 Ack/ReAck/Ack2/ReAck2/Ack-Fwd/Ack-Save
采用810描述。
Ack/Ack2:对应810-Indication
ReAck/ReAck2:对应810-Response.
通过ack_flag区分Ack和Ack2,以及ReAck,ReAck2.
Ack/ReAck/Ack-Fwd/Ack-Save传输控制:存储转发(3),不需要送达确认
Ack2/ReAck2传输控制:存储转发(3),不需要送达确认
通信对象
。本地服务器-平台:
。平台-本地服务器
。本地服务器-本地服务器(Ack2/ReAck2)
参数:
src_semq_id: 源SEMQ实例ID,默认1
src_semq_db_id:源SEMQ存储实例ID,默认1
src_ref_id: 源SEMQ记录编号
源是指发起确认的一方。
///< 本地服务器端待确认消息记录定位信息
dest_semq_id:默认1
dest_semq_db_id:默认1
dest_ref_id:待确认消息表的object_id
ack_flag: 1-与平台相关的确认 2-送达确认 (保留,可以从消息头确定来源)
app_key:业务键,可以为空,在返回810-Response原样返回,本地服务器可以根据此键值定位业务记录,修改发送状态或者通知应用程序。
flag: 1- 平台已转发 2-平台已存储 (只出现在平台返回的Ack-Fwd,Ack_Save消息中)
返回:(810-Response)
src_semq_id: 源SEMQ实例ID,默认1
src_semq_db_id:源SEMQ存储实例ID,默认1
src_ref_id: 源SEMQ记录编号
源是指发起确认的一方。
dest_semq_id:默认1
dest_semq_db_id:默认1
dest_ref_id:待确认消息表的object_id
ack_flag: 1-与平台相关的确认 2-送达确认
app_key:
平台可以根据semq_id,semq_db_id,ref_id确定是哪个存储表的哪条记录,修改该记录的状态为SS_ACK。
4.3.3 Inquiry/Inquiry-Resp:
用809协议描述,取代原来的817.
通信对象:
.本地--平台
.本地--目标本地:
.平台--目标本地
参数:
.semq_id:默认1
.semq_db_id:默认1
.ref_id:
.flag: 1-来自平台的询问 2-来自对方的询问 (也可以从消息头确定消息的来源)
返回(809-Confirmation):
.semq_id:
.semq_db_id:
.ref_id:
.result: 1-已接收 0-未接收
5.其它
5.1兼容性:
.存储在SEMQ中的未处理的数据
.岁宝:与其他用户采用的机制不同,必须修改代码(历史原因造成)
5.2已知缺陷:
.目前不支持直接连接和P2P
.部分待办事宜的消息应该属于SEMQ范围:实际上是转发给用户的消息
同一机构内A用户向B用户发送离线消息时,应该保存在SEMQ中,而不是待办事宜(tb_0032)。待办事宜是应用层概念。
.目前本地tb_0031存储的是发往机构外部(包括平台和其它机构和用户的消息,来自外部给本地用户或者本机构用户之间的消息没有保存在tb_0031中。逻辑不统一。