三种你需要考虑一致性问题的场景

918 阅读5分钟

1.前言

分布式系统开发过程中往往会涉及到很多需要保证数据一致性问题的场景,比如接收mq消息接收http请求内部业务处理。如果你还不了解这些场景或者不知道如何处理,请继续往下阅读。

2.接收mq消息

接收mq消息场景在分布式系统开发过程中想必是一种比较常见的场景,具体过程就是,外围系统推送mq消息到开发系统,开发系统接收消息后进行业务逻辑处理。从表面上看是一个十分简单的流程,但是如果涉及数据一致性问题,就不那么简单了。

为什么说不那么简单呢?先来看看以下场景:

2.1 先ack消息再处理业务

ack();
// 处理业务逻辑

先ack消息再处理业务在理想场景下是不会有什么问题,看到这里你可能会有疑惑,没有问题那不就得了,还需要考虑什么呢?请注意这种没问题是建立在理想的前提下,如果业务处理过程中调用外部接口异常或者数据库宕机,就会导致消息丢失,进而出现数据不一致性的问题。

2.2 先处理业务再ack消息

// 处理业务逻辑
ack();

既然先ack消息再处理业务这条路走不通,那么就先处理业务再ack消息总没问题了吧。是的,即便业务处理失败消息没有被ack,消息还会被重新消费,不会出现数据不一致性问题。但是这会涉及到另外一个问题,那就是幂等性问题,幂等问题处理不好,还是会引起数据不一致性问题。

其实先处理业务再ack消息还会引起另一个问题,如果业务系统有bug会造成消息一直无法被ack,进而会导致消息处理进入死循环。

这样也不行,那样也不行,就没有解决方案了?当然不是,方案还是有的,且听我慢慢跟你说

2.3 结合消息ack机制 + 数据库 + 定时任务方案一

try {
    try{
        // 根据消息唯一编号查询该消息是否已处理过,如果没有处理过,进行处理业务;如果处理过,则说明都不做
    } catch (Exception e) {
       // 将处理异常的消息插入数据库中
    }
    ack();
} catch (Exception e) {
    unack();
}

大体思路就是,根据消息唯一编号判断消息是否被处理过,如果未被处理过,就对消息进行处理,处理成功则对消息进行ack;处理失败则将消息存入数据库中。如果存入数据库这一步操作还是失败,那么就对消息进行unack操作,将消息重新投递到消息服务器中,进而重新消费,直到数据库恢复为止。针对处理失败入库的消息,可以通过定时任务重试处理。

该方案不仅可以解决2.2中的幂等性问题,还可以解决业务出现bug进而导致消息处理进入死循环的问题(限制重试次数)。但是该方案还是会存在一个跟本文无关的问题,那就是消息积压问题。

2.4 结合消息ack机制 + 数据库 + 定时任务方案二

try {
    // 根据消息唯一编号查询该消息是否存在,不存在则直接插入数据库中,存在则不进行处理
    ack();
    // 异步处理业务逻辑
} catch (Exception e) {
    unack();
}

2.42.3优缺点对比

序号优点缺点
2.3只在业务处理失败将消息插入数据库中,消息数量不会太多消息处理慢会导致消息积压
2.4消息异步处理,不会导致消息积压所有消息都存储数据库,消息数量可能会很多

关于这两种方案可以根据实际情况进行自由选择,消息积压问题处理也可以参考:消息积压你作何处理?

3. 接收http请求

看到这个图你可能会想这不就是一个很简单的流程嘛,开发系统接收请求、处理请求、响应结果就可以了。如你所想,确实很简单,但是如果你的开发系统业务处理失败,就会导致外围系统进行重试,直到重试次数用完,开发系统还未恢复正常,那么此次的外围请求数据就会丢失,从而引起数据不一致性问题。

认真分析一下该场景,你会发现造成数据不一致性问题的关键在于开发系统的业务处理。如果开发系统能在正确接收外围系统请求后立刻进行响应,那么就可以解决该问题。

我们只需要在接收http请求后,将请求内容写入数据库,写入成功进行异步处理并返回成功;写入失败返回失败,通过外围重试来保证数据可以正常写入数据库。处理失败的内容可以结合定时任务对请求进行重试。

4.内部业务处理

开发系统某些业务在处理成功后往往需要通知某些外围系统并且还不能因为外围系统故障从而导致当前业务无法正确处理,既然不能影响当前业务,可以采用异步的方式进行处理,异步处理就会存在当前业务处理成功,通知外围失败的问题,进而引起数据不一致性问题。

那有没有办法可以保证业务处理成功的同时,对外围的通知也一定成功呢?

我们可以采用本地事务的方案,把通知给外围的数据放在当前业务的事务中插入到数据库,异步通知外围系统,处理失败的数据再结合定时任务进行重试。

5.总结

从文中我们可以看到一致性问题的解决方案都逃不开数据库 + 重试,因此在解决一致性问题的时候可以多往这方面考虑。