SEMQ消息重复处理问题

来源:互联网 发布:数据库索引的实现原理 编辑:程序博客网 时间:2024/06/16 00:02
如何保证一个消息在目标接收方只被成功处理一次。
如一张订单不应该被接收方处理两次,即不能认为对方下了2个订单,出现这种问题在交易系统如银行系统中是严重的。
如果存在唯一的订单号码,则应用层可以避免,在接收到订单后,检查是否有该订单的历史处理记录来避免。
对于简单的接收保存操作,则可以通过数据库的唯一性约束进行控制。

如果数据库没有约束,或者不由应用层来判别而由传输层保证,这样,传输机制具有更好的适应性,并简化了应用层逻辑。


从发送方而言,进入发送队列就意味着执行了发送,到达目标接收方并被应用处理的过程中可能出现主机,网络,进程问题而失败。
只有得到对方应用的确认才能保证消息可靠地送达了。
所以,可靠的传输必须在网络层以上保证。

网络上发送同一消息可能是多次,但接收方只能成功处理1次.

SEMQ的可靠传输分3个阶段:
(1)发送方发送:从SEMQ的节点的待发送消息队列(tb_0031)中
(2)接收方在接收并成功处理后返回确认消息(810-Indication)
并把定位信息写入待确认消息队列(tb_0030)中.tb_0030是发送方在没有接收确认时询问的目标.
其记录只有在接收到发送方的反向确认消息后才能安全删除.
(3)发送方接收到810-Indication后,返回接收方反向确认消息(810-Response)

为了在接收方已经接收到消息后,避免处理时失败时再次发送,对于处理失败的记录保存在失败重做队列表中(tb_0033),由失败重做任务定时自动尝试处理。

接收方处理失败时,写入失败重做队列(tb_0033),并返回810-Indication给R(告诉R不要再发送了),后续在S端处理此失败的记录。
失败的原因有:
.与数据库的网络连接,或者数据库服务故障,死锁
这种问题可以在故障恢复后重新处理消息
.业务逻辑错误:如处理消息的单据数据时出现数据无效或违反唯一性约束
这种问题排除程序bug原因(如数据无效,如超长),需要系统维护人员处理,丢弃此消息或者删除引起冲突的记录。

1.对于gyb

用R,P,S分别代表零售商服务器,平台,托管服务器。
分析一张单据的传输过程,单据由R经过P发送给S。
把封装单据的消息称为单据消息.(实际采用的是2800-Indication)
R发送单据.首先写入tb_0031表.消息发送状态初始为SS_UNSEND(0).

情况1:理想情况
R,P,S都在线。
(1)R端SEMQ节点执行发送,发送状态修改为SS_SEND(4).
(2)平台接收后直接转发,返回R端810-Indication消息
(3)R接收到平台的810-Indication后,修改发送状态为SS_ACK(6)
(4)S接收消息后处理,不论成功与否,写入tb_0030,确认状态(f004n_0030)为AS_INIT(0),返回810-Indication给R端,失败时写tb_0033.
(5)R端接收到810-Indicaiton后,修改发送状态为SS_ACK2(100),返回810-Response给S端.
(4)S端接收810-Response后修改tb_0030的确认状态为AS_OK(1).

情况2:目标S不在线
(1)R端SEMQ节点执行发送,发送状态修改为SS_SEND(4).
(2)平台接收后存储在平台SEMQ节点,并向R返回确认.
(3)R接收到平台的确认后,修改发送状态为SS_ACK(6),表示已送出
(4)待S上线后触发平台执行发送.
(5)S接收到消息后处理,不论成功与否,写入tb_0030,确认状态(f004n_0030)为AS_INIT(0),返回810-Indication给P和R端,失败时写tb_0033.
(6)P接收到S的810-Indication后,修改发送状态为SS_ACK(6),并返回S端810-Response.
(7)R接收到S的810-Indication后,修改发送状态为SS_ACK2(100),返回810-Response给S端.
(8)S接收到来自P和R的810-Response消息后,修改tb_0030对应的记录的确认状态为AS_OK(1)


目前发送状态没有区分平台是直接转发还是存储转发.

确认是在R和S之间进行的,平台只执行一次转发操作,平台不保证一定送达.(另一种模式是P负责成功送达.那么R要区分是直接转发还是存储转发,而且如果支持R,S之间直接连接时有需要R负责送达,增加了逻辑复杂度)
S对于发送状态为非初始状态SS_UNSEND(0),且没有SS_ACK2的记录,定时向S端询问。
只有在得到S的明确的回复(接收到或未接收到)后,R端才进行相应动作.如果得到肯定的答复则直接修改发送状态为SS_ACK2,否则,再次执行发送,S不会盲目重发.

在分布式网络,多线程和偶发性的环境下,可能存在以下情况:
.R执行发送后立即问询: 发送的消息在问询之后处理,或者R端810-Indication在问询的回复之后接收或处理.
.P存储转发的消息在R的问询消息之后被S处理.
所以,理论上S接收一个消息超过1次是很难避免的.(在现有机制下).

要避免消息被多次成功处理.

以下是路由消息的代码片段
int CBBoxPlugin::HandleInput_i(CWrappedMsg<> *pwm,void **pploc,int &action)  {    if (loc&&msg->IsAutoAck()) { ///< 检查是否是重复发送的消息(需要确认的消息接收后会写入Ack队列)        int ret = CheckAck(loc);        if (ret==-1) {            action = 0;            return -10;        }        if (ret==1) {            return 10;        }        ...}      
接收到消息后,检查该消息是否是自动确认的(自动确认标志的含义与初期定义已变化,实际上是可靠传输的标志).
如果是则检查tb_0030是否已经接收过此消息.
如果已经存在,则直接确认,不再交由插件处理消息.

但是,目前的实现中,有2种情况下会出现重复处理:
(1)单据消息处理和SEMQ的确认在数据库操作上不在一个事务中,先单据消息处理后确认。这可能导致已经处理单据消息但没有进行确认,R会继续重发并被S再次处理。
(2)如果同一待发送记录的2个单据消息同时到达,在CheckAck时都没有检查到,都交给插件进行处理.

解决方案:
(1)修改:idx_0030_1索引(f003v_0030)为唯一性索引
ALTER TABLE `cqq_retail_aj`.`tb_test_0001` ADD UNIQUE `f001n_0001` (`f001n_0001`)
(2)在CBBoxPlugin::HandleInput_i中开启事务,在CSEMQ::PostHandle中根据flag_和action决定是否提交
int CBBoxPlugin::HandleInput_i(CWrappedMsg<> *pwm,void **pploc,int &action)  {    if (loc&&msg->IsAutoAck()) { ///< 检查是否是重复发送的消息(需要确认的消息接收后会写入Ack队列)        int ret = CheckAck(loc);        if (ret==-1) {            action = 0;            return -10;        }        if (ret==1) {            return 10;        }        ...      启动确认事务      pdbor = GetDbConnection(ack_queue_dbc_.c_str());      SaveAck(loc); ///<  先写确认消息记录                              ///SEMQ_ACK_HELPER结构中增加一个成员(flag_:1-预写确认记录(需要显式提交或回滚) 默认:0)        loc->flag_ = 1;    ///<     等待插件处理完毕后再根据结果提交或回滚事务    ...}


这要求SEMQ和单据业务表操作在一个事务中,对于非XA环境则要求在一个单机事务中.

2.das 1.0

das的传输系统继承自gyb,但所有改变.
其改变源于体系结构的变化,在das中,数据从多个R汇集到平台.gyb中的S变成了对等的内部服务器,它们共享一个SEMQ.
.平台服务器只支持直接转发,不支持存储转发.
因为内部服务器是对等的,只要有一个内部服务器就不需要存储转发了.当没有内部服务器时,实际上可以拒绝服务器注册。
存储转发还带来了多个内部服务器同时注册时如何分配待发送消息队列的消息的设计问题
.平台服务器不需要返回810-INDICATION给R.即前置机tb_0031的发送状态没有SS_ACK(6)的状态了.

内部服务器的逻辑如下:
.接收消息写入平台数据存储库,同时导出到外部数据库(如jxj db---数据平台数据库)
.平台数据存储库(包括SEMQ存储)和外部数据库是2个不同的数据库
.通过dxi_change_log:捕获单据接收事件(BILL_ACCEPT)。接口处理方式有2种:
(1)写入tb_change_log。然后由dxi_change_log扫描处理.
   tb_chaneg_log由专门的进程处理,内部服务器只负责写. (多个处理tb_change_log的进程如何协调,避免同时处理相同的记录)-----任务协调器的概念又一次出现.
   考虑容量和可用性,任何处理都不能由单个进程执行。
(2)内部服务器直接调用dxi_chaneg_log处理,所有操作在一个顺序操作序列中全部完成。这就不需要任务协调者。
   <handle_mode>3</handle_mode> <!-- 处理模式: bit0-1:同步 0-异步 bit1-同步模式下是否写变更日志表.默认:0 -->
方式(1)需要在多个内部服务器之间协调tb_change_log记录的处理者.
静态分配:接收的内部服务器负责处理.但如果该服务器宕机后需要另行指定新的处理者.
动态分配:需要开发一个任务协调器程序.
所以采用方式(2).

方式(2),外部导出操作是在业务单据处理过程中执行的.由于没有XA支持,可能出现导出成功但平台数据库处理失败的情况,这会导致重新发送.
在这种模式下,das的重复处理是不可避免的.

即使是方式(1),当tb_chaneg_log和外部数据库跨数据库时都存在事务不一致的问题,而导致重复处理。
在没有XA支持的情况下,宁可多做不能漏做是不二的选择。这意味着重复处理是不可避免的.

3.结论

在das for gigamore中,如果不做唯一性约束,会出现同一单据写入数据平台多次的情况.
避免这种情况有以下途径:
。支持XA:
。在数据平台业务数据表中增加唯一性约束
。允许重复,但数据平台的数据处理者能够消除其影响
。写入时锁表,先检查,只有再没有重复数据的情况下才写入:(不如直接加约束)
或者,在以下情形下该问题并不重要,不需要理会:
。大数据思想:不追求精确
。多次写入的概率比较低
0 0
原创粉丝点击