如何设计本地消息表?
本地消息表是用于解决业务操作与消息发送的原子性问题,本文主要针对 RocketMQ 来说。
1、 首先一个表肯定要有主键 ID。
2、因为本地消息表是为消息服务的,所以要有一个字段存储消息内容
3、还要有一个字段存储具体的 Topic
4、为了幂等性,需要标识消息的状态,已经发送了还是待发送
5、为了防止消息一直重试,增加重试次数
6、模拟延时队列,增加下一次重试时间
7、还有上一次执行时间
8、如果多业务共用一张表,还需要增加业务字段,用来区分业务
9、增加业务幂等键
10、创建时间与修改时间
11、创建代码类(系统)和修改代码类(系统)
整理成表格的话:
字段名 | 类型 | 允许为空 | 默认值 | 说明 |
---|---|---|---|---|
id | BIGINT(20) | NOT NULL | AUTO_INCREMENT | 主键 ID |
biz_key | VARCHAR(64) | NOT NULL | 业务幂等键,用于防止重复处理同一业务消息 | |
biz_type | VARCHAR(32) | NOT NULL | 业务类型(如 order_create 、stock_update ) |
|
topic | VARCHAR(64) | NOT NULL | 消息所属 RocketMQ Topic | |
message_body | TEXT | NOT NULL | 消息内容(建议存储 JSON 序列化后的数据) | |
status | TINYINT(3) | NOT NULL | 0 | 消息状态:0-待发送,1-发送中,2-已发送,3-发送失败 |
retry_count | INT(11) | NOT NULL | 0 | 重试次数 |
next_retry_time | DATETIME | YES | NULL | 下次重试时间(用于模拟延时队列) |
last_execute_time | DATETIME | YES | NULL | 上一次执行(发送)时间 |
fail_reason | VARCHAR(255) | YES | NULL | 发送失败原因(错误码、异常信息摘要) |
create_by | VARCHAR(64) | YES | NULL | 创建人(代码类名/系统名) |
update_by | VARCHAR(64) | YES | NULL | 最后更新人(代码类名/系统名) |
gmt_create | DATETIME | NOT NULL | CURRENT_TIMESTAMP | 创建时间 |
gmt_modified | DATETIME | NOT NULL | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 修改时间 |
索引上,根据查询条件设置:
-- 扫描待发送的消息
CREATE INDEX idx_state_nextretry ON local_message (state, next_retry_time);
-- 按业务幂等键查找
CREATE UNIQUE INDEX uk_biz_type_key ON local_message (biz_type, biz_key);
-- 按 topic 查找
CREATE INDEX idx_topic ON local_message (topic);
CREATE TABLE `t_local_message` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`biz_type` varchar(64) NOT NULL COMMENT '业务类型(区分不同业务,例如order_create、stock_lock等)',
`biz_key` varchar(128) NOT NULL COMMENT '业务幂等键(关联具体业务记录,例如订单号)',
`topic` varchar(255) NOT NULL COMMENT 'RocketMQ Topic',
`message_body` text NOT NULL COMMENT '消息内容(JSON格式)',
`status` tinyint NOT NULL COMMENT '消息状态:0-待发送,1-发送中,2-已发送,3-发送失败',
`retry_count` int NOT NULL DEFAULT 0 COMMENT '消息已重试次数',
`next_retry_time` datetime DEFAULT NULL COMMENT '下一次重试时间(可实现延时发送)',
`last_exec_time` datetime DEFAULT NULL COMMENT '最后一次发送时间',
`fail_reason` varchar(512) DEFAULT NULL COMMENT '最后一次失败原因',
`created_by` varchar(64) NOT NULL COMMENT '创建人或系统代码标识',
`updated_by` varchar(64) NOT NULL COMMENT '修改人或系统代码标识',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_type_key` (`biz_type`, `biz_key`) COMMENT '业务幂等约束',
KEY `idx_status_next_retry` (`status`, `next_retry_time`) COMMENT '扫描待重试的消息',
KEY `idx_topic` (`topic`) COMMENT '按 topic 查找'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='本地消息表(生产者侧)';
┌───────────────────────┐
│ 0 待发送 │
│(事务提交后写入表) │
└──────────┬────────────┘
│ 定时任务扫描(status=0)
▼
┌───────────────────────┐
│ 1 发送中 │
│(调用 MQ 发送接口) │
└───────┬───────┬───────┘
│ │
MQ 发送成功 │ │ MQ 发送失败
│ ▼
│ ┌────────────────────────────┐
│ │ 记录 fail_reason │
│ │ retry_count += 1 │
│ │ 设置 next_retry_time │
│ └───────────┬────────────────┘
│ │
│ ▼
│ 达到最大重试次数?
│ │
│ │ 否
│ ▼
│ 回到 1 发送中(下次扫描)
│
▼
┌────────────────────────────┐
│ 2 已发送 │
│(成功投递至 Broker,归档或删) │
└────────────────────────────┘
│
│ 是
▼
┌───────────────────────┐
│ 3 发送失败 │
│(人工处理或补偿机制) │
└───────────────────────┘