跳至主要內容

如何保证消息幂等

AruNi_Lu中间件消息队列约 1058 字大约 4 分钟

本文内容

1. 对幂等的理解

幂等是业务的一个特性,在 MQ 中,对于不满足幂等性的业务,在消息重复消费时,会导致数据不一致、或数据错乱的现象

例如一个支付业务,消费者需要消费扣款的消息,如果该消息出现了重复消费的情况,但最终的业务结果是只扣款了一次,那么就说这个扣款业务具有 幂等性

再用我们常见的 HTTP 为例,GET、PUT、DELETE 方法都是幂等的,因为它们不管执行多少次,都和执行一次效果一样,对业务结果都是无影响的。而 POST 就不是幂等的,它会创建多个资源。

在实际业务中,大多都是对 DB 的操作,在 CRUD 中:

  • READ 和 DELETE 是幂等的;

  • CREATE 不是幂等的;

  • 而 UPDATE 操作可能是幂等的,也可能不是幂等的。比如:

    UPDATE user SET age = 1 WHERE id = 1;		# 幂等
    UPDATE user SET age = age + 1 WHERE id = 1;		# 不幂等
    

2. 为何会出现消息不幂等的情况?

在 MQ 中,为了保证 消息的可靠传输,一般都会有 失败重传机制,比如:

  • 当生产者发现消息发送失败时,生产者会进行消息重发

    比如 发送的消息因网络延时,导致生产者未收到服务端的 ACK,导致 消息重发,而一段时间后延时的消息又到达了,导致消息重复,这种情况消息的 offset 是不同的

  • 当消费者消费失败时,服务端会再次投递该消息给消费者

    比如 消费者消费完消息后,还没来得及回复 ACK,机器就宕机了,重启后服务端会再次将该消息投递给消费者,这种情况消息的 offset 是相同的

出现了重复消息,自然而然就会有幂等性问题了。

3. 如何解决消息不幂等?

首先要明确一点,有些业务天然就不需要考虑幂等性问题,允许重复调用(即允许重试)。这些业务还可以通过合理的重试机制来提高可靠性。

那在需要考虑幂等性问题的业务中,应该如何保证消息幂等呢?通常有以下几种解决方案:

  • 利用数据库
  • 设置全局唯一标识 ID

3.1 利用数据库

利用数据库解决幂等性问题,可以有两种方案:

  • 使用唯一索引去重
  • 设计一个去重表

比如,业务是向数据库中插入数据,那么就可以使用唯一索引来保证幂等,重复消费时会插入失败,捕获异常处理即可。

同理,如果业务是操作 Redis 的 Set,那么也可以天然的保证幂等。

如果是不具有幂等性的更新数据(UPDATE user SET age = age + 1 WHERE id = 1;),此时可以使用第二种方案,我们 新建一张去重表,将 id 和时间戳做一个唯一索引进行约束。当消费者要更新数据库时,先往去重表进行写入,只有第一条消息会插入成功,后面的都会失败,在失败时捕获异常处理一下即可保证幂等。

3.2 设置全局唯一标识 ID

我们可以用 业务唯一标识 ID 作为 Message Key 放入消息中,消费者在消费消息时,首先需要取出该 Key,然后根据业务来做具体的幂等处理。

为了增加幂等处理的速度,可以将这个 Message Key 存入 Redis,通过 Redis 来快速判断该 Key 是否存在,即是否为重复消费。

上次编辑于: