1. 本地消息表 + 定时任务中可靠发布的经典实现与重复消息的处理?
解释本地消息表 + 定时任务的可靠消息发布经典实现,以及重复消息的处理?
- 本地消息表与业务同库同事务
- 定时任务扫描并投递消息
- 幂等处理重复消息
本地消息表(Local Message Table)方案:在业务数据库里建一张消息表,业务操作与"写消息记录"放在同一个本地事务中——业务成功则消息记录一并提交,从而保证"业务与消息发布"的原子性。随后由定时任务周期性扫描消息表,把待发送(未发送或发送失败)的消息投递到 MQ,并标记发送状态。这样即使投递失败,任务会重试,保证消息最终被发布(可靠投递)。重复消息的处理:由于定时任务可能重发、MQ 投递可能重复,消费端必须幂等——用唯一消息 ID(或业务主键)去重,消费前查重表/或用业务幂等键判断是否已处理,只处理一次。所以本地消息表保证"至少一次"发布,配合消费端幂等实现"恰好一次"业务效果。
核心是"同库同事务保证原子性 + 定时任务重试保证投递 + 消费端幂等去重"。本地消息表把"业务"与"消息"绑定在同一个本地事务,避免先发消息后业务失败的不一致。重复消息靠幂等消费兜底。
// 本地消息表:业务与消息同事务写入
@Transactional
public void businessOperation(Order order) {
// 1. 业务处理
orderDao.insert(order);
// 2. 写入本地消息表(同一事务)
MessageRecord msg = new MessageRecord(order.getId(), "order.created", serialize(order));
messageRecordDao.insert(msg);
}
// 定时任务:扫描并发布待发送消息(带重试)
public void publishPendingMessages() {
List<MessageRecord> pending = messageRecordDao.findPending(100);
for (MessageRecord m : pending) {
try {
mqProducer.send(m.getTopic(), m.getPayload());
messageRecordDao.markSent(m.getId());
} catch (Exception e) {
// 记录失败,下次定时任务重试
}
}
}