消费者幂等设计:重复消息为什么不可避免

重复消息是消息系统里的常态,消费者必须通过业务唯一键、去重表、状态机和条件更新保证幂等。

消费者幂等设计:重复消息为什么不可避免

只要系统允许重试,就要接受重复消息。

生产者可能因为超时重发。消费者可能处理成功但提交 Offset 失败。Broker 可能重新投递未确认消息。消费者 Rebalance 也可能让某些消息再次被处理。

所以消费者必须幂等。

Rendering diagram...

用事件 ID 去重

最直接的方式是去重表。

CREATE TABLE message_dedup (
  message_id VARCHAR(128) PRIMARY KEY,
  consumer_group VARCHAR(128) NOT NULL,
  processed_at TIMESTAMP NOT NULL
);

处理前插入去重记录。如果唯一键冲突,说明消息已经处理过。

用业务唯一键

有些场景不需要单独去重表,业务表本身可以兜底。

比如支付流水可以用支付单号做唯一键。

INSERT INTO payment_flows(payment_no, order_id, amount)
VALUES ('pay_10001', 10001, 199.00)
ON CONFLICT (payment_no) DO NOTHING;

同一支付事件重复消费时,不会重复写流水。

用状态机

订单状态机也能防止重复和乱序。

UPDATE orders
SET status = 'PAID'
WHERE id = 10001
  AND status = 'CREATED';

如果订单已经是 PAID,重复的支付成功消息不会再次修改。

幂等不是只防重复

幂等还要防乱序。比如先收到 OrderCancelled,后收到迟到的 OrderPaid。如果状态机不限制,就可能把已取消订单改成已支付。

所以幂等设计通常要和状态机一起做。

小结

消费者幂等是消息系统的底线能力。不要假设消息只会来一次,要默认它可能重复、迟到、乱序,然后用唯一键、去重表和状态机兜住。

参考链接