✅为了避免丢消息问题需要落表,如何设计这张消息表?
典型回答
这个其实是典型的本地消息表的方案:
那么,我们就需要自己定义一张表,用来存储本地消息,这张表不仅用于存储,还会用来做扫表重试,那么这张表该如何设计呢?
以下是这张表需要包含的一些字段
| 字段名 | 类型 | 描述 |
|---|---|---|
id |
BIGINT (PK) | 主键 |
gmt_create |
DATETIME | 创建时间 |
gmt_modified |
DATETIME | 更新时间 |
message_key |
VARCHAR | 消息的业务唯一标识(用于幂等处理) |
message_id |
VARCHAR | 消息的唯一ID,发送成功后才有,如果没成功,则该字段为null |
message_type |
VARCHAR | 消息的类型,根据这个消息类型执行不同的消息处理逻辑 |
topic |
VARCHAR | 消息所属主题或分类 |
message_body |
TEXT / JSON | 消息体,内容序列化存储 |
state |
VARCHAR | 消息状态(如:待发送,已发送,失败,已消费等) |
retry_count |
INT | 重试次数 |
next_retry_at |
DATETIME | 下一次重试时间(用于任务调度) |
last_retry_time |
DATETIME | 上一次的重试时间(用于任务调度) |
fail_reason |
TEXT | 失败原因,可以记录具体的错误码,或者异常的堆栈信息 |
lock_version |
BIGINT | 乐观锁版本号 |
索引情况:
state作为普通索引,用于高效扫表。这个也可以和retry_count、next_retry_at等字段一起建一个联合索引。message_key+message_type作为唯一索引,防止重复插入
关于state设置索引是否有用,可以看下面这篇:
基本信息
id |
BIGINT (PK) | 主键 |
|---|---|---|
gmt_create |
DATETIME | 创建时间 |
gmt_modified |
DATETIME | 更新时间 |
lock_version |
BIGINT | 乐观锁版本号 |
这些都是一些基本字段了,
lock_version,主要用于乐观锁处理,避免出现并发问题导致更新错误
消息必要信息
message_key |
VARCHAR | 消息的业务唯一标识(用于幂等处理) |
|---|---|---|
message_id |
VARCHAR | 消息的唯一ID,发送成功后才有,如果没成功,则该字段为null |
message_type |
VARCHAR | 消息的类型,根据这个消息类型执行不同的消息处理逻辑 |
topic |
VARCHAR | 消息所属主题或分类 |
message_body |
TEXT / JSON | 消息体,内容序列化存储 |
以上这些就是消息的基本信息了,因为这张表需要在消息失败的时候重新发送,所以需要把消息的相关信息都记录下来,方面后续重新发送。
message_id这个是消息发送成功的时候才会有的一个信息,即MQ返回的,然后把他存下来,方便后续排查消息是否成功,以及对应的消息是否处理,以及后续如果要定位到具体的消息,也更加方便一点。
message_type这个表是一个消息的类型,比如是确认收货消息,还是发货消息,可以在扫表任务中,根据这个消息类型执行不同的消息处理逻辑。
状态信息
state |
VARCHAR | 消息状态(如:待发送,已发送,已失败,已消费等) |
|---|
状态表示一个消息的处理状态,可以包括待发送,已发送,失败,已消费,已挂起。
其中待发送是初始状态,已失败和已消费是终止状态。
这里面的已失败状态,可以根据发送次数进行逻辑处理,比如设置一个阈值,比如发送10次,还是不成功,这是为啥已失败状态,避免重复处理。
针对这种多次不成功的消息,比如已失败状态的消息,根据实际的业务情况,可以考虑降低重试次数(比如待发送的消息10分钟重试一次,已失败的消息6小时重试一次),或者告警出来人工跟进。
任务执行信息
retry_count |
INT | 重试次数 |
|---|---|---|
next_retry_at |
DATETIME | 下一次重试时间(用于任务调度) |
last_retry_time |
DATETIME | 上一次的重试时间(用于任务调度) |
fail_reason |
TEXT | 失败原因,可以记录具体的错误码,或者异常的堆栈信息 |
以上几个是任务重试执行相关的信息,包括了重试的次数,上次重试时间、下一次重试时间,以及失败原因等。方便我们知道消息的执行情况,和快速的定位问题。
next_retry_at和last_retry_time 这两个字段可以有一个也行,都没有也不是不行,看你的实际业务情况,有的任务是可以主动设定下次执行时间,比如特殊的消息就是要3小时执行一次,那么就可以在每次执行后,如果失败了,则把当前时间加上3小时,设置到next_retry_at上面去。
last_retry_time 这个是方便我们扫表的时候可以设置特殊的过滤条件,比如只针对没重试过的消息(last_retry_time is null )进行扫描,或者针对10分钟之前的消息处理(last_retry_time < now - 10 min)等等。