Skip to content

面试速答(先看这里)

**一句话结论:**那么,我们就需要自己定义一张表,用来存储本地消息,这张表不仅用于存储,还会用来做扫表重试,那么这张表该如何设计呢?

60秒标准回答:

这个其实是典型的本地消息表的方案

那么,我们就需要自己定义一张表,用来存储本地消息,这张表不仅用于存储,还会用来做扫表重试,那么这张表该如何设计呢?

以下是这张表需要包含的一些字段

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **要点1:**消息状态(如:待发送,已发送,失败,已消费等)
  • **要点2:**失败原因,可以记录具体的错误码,或者异常的堆栈信息
  • **要点3:**lock_version,主要用于乐观锁处理,避免出现并发问题导致更新错误
  • **要点4:**以上这些就是消息的基本信息了,因为这张表需要在消息失败的时候重新发送,所以需要把消息的相关信息都记录下来,方面后续重新发送。
  • **要点5:**状态表示一个消息的处理状态,可以包括待发送,已发送,失败,已消费,已挂起。

**记忆锚点:**DATETIME → last_retry_time → next_retry_at → message_type → lock_version → message_key

易错提醒:

  • 消息状态(如:待发送,已发送,已失败,已消费等) 状态表示一个消息的处理状态,可以包括待发送,已发送,失败,已消费,已挂起。
  • 其中待发送是初始状态,已失败和已消费是终止状态。

加分表达:

  • message_id 这个是消息发送成功的时候才会有的一个信息,即MQ返回的,然后把他存下来,方便后续排查消息是否成功,以及对应的消息是否处理,以及后续如果要定位到具体的消息,也更加方便一点。
  • 以下是这张表需要包含的一些字段 BIGINT (PK) gmt_create DATETIME gmt_modified DATETIME message_key 消息的业务唯一标识(用于幂等处理) message_id 消息的唯一ID,发送成功后才有,如果没成功,则该字段为null message_type 消息的类型,根据这个消息类型执行不同的消息处理逻…
  • message_key + message_type 作为唯一索引,防止重复插入 关于state设置索引是否有用,可以看下面这篇: BIGINT (PK) gmt_create DATETIME gmt_modified DATETIME lock_version 这些都是一些基本字段了, lock_version,主要用于乐观锁处理,避免出现并发问题导致更…

追问准备:

  • 围绕「DATETIME」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「last_retry_time」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「next_retry_at」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

这个其实是典型的本地消息表的方案:

📄 ✅如何基于本地消息表实现分布式事务?

打开文档:✅如何基于本地消息表实现分布式事务?

那么,我们就需要自己定义一张表,用来存储本地消息,这张表不仅用于存储,还会用来做扫表重试,那么这张表该如何设计呢?

以下是这张表需要包含的一些字段

字段名类型描述
idBIGINT (PK)主键
gmt_createDATETIME创建时间
gmt_modifiedDATETIME更新时间
message_keyVARCHAR消息的业务唯一标识(用于幂等处理)
message_idVARCHAR消息的唯一ID,发送成功后才有,如果没成功,则该字段为null
message_typeVARCHAR消息的类型,根据这个消息类型执行不同的消息处理逻辑
topicVARCHAR消息所属主题或分类
message_bodyTEXT / JSON消息体,内容序列化存储
stateVARCHAR消息状态(如:待发送,已发送,失败,已消费等)
retry_countINT重试次数
next_retry_atDATETIME下一次重试时间(用于任务调度)
last_retry_timeDATETIME上一次的重试时间(用于任务调度)
fail_reasonTEXT失败原因,可以记录具体的错误码,或者异常的堆栈信息
lock_versionBIGINT乐观锁版本号

索引情况:

  • state 作为普通索引,用于高效扫表。这个也可以和retry_count、next_retry_at等字段一起建一个联合索引。
  • message_key +message_type 作为唯一索引,防止重复插入

关于state设置索引是否有用,可以看下面这篇:

📄 ✅区分度不高的字段建索引一定没用吗?

打开文档:✅区分度不高的字段建索引一定没用吗?

基本信息 ​

idBIGINT (PK)主键
gmt_createDATETIME创建时间
gmt_modifiedDATETIME更新时间
lock_versionBIGINT乐观锁版本号

这些都是一些基本字段了,

lock_version,主要用于乐观锁处理,避免出现并发问题导致更新错误

消息必要信息 ​

message_keyVARCHAR消息的业务唯一标识(用于幂等处理)
message_idVARCHAR消息的唯一ID,发送成功后才有,如果没成功,则该字段为null
message_typeVARCHAR消息的类型,根据这个消息类型执行不同的消息处理逻辑
topicVARCHAR消息所属主题或分类
message_bodyTEXT / JSON消息体,内容序列化存储

以上这些就是消息的基本信息了,因为这张表需要在消息失败的时候重新发送,所以需要把消息的相关信息都记录下来,方面后续重新发送。

message_id这个是消息发送成功的时候才会有的一个信息,即MQ返回的,然后把他存下来,方便后续排查消息是否成功,以及对应的消息是否处理,以及后续如果要定位到具体的消息,也更加方便一点。

message_type这个表是一个消息的类型,比如是确认收货消息,还是发货消息,可以在扫表任务中,根据这个消息类型执行不同的消息处理逻辑。

状态信息 ​

stateVARCHAR消息状态(如:待发送,已发送,已失败,已消费等)

状态表示一个消息的处理状态,可以包括待发送,已发送,失败,已消费,已挂起。

其中待发送是初始状态,已失败和已消费是终止状态。

这里面的已失败状态,可以根据发送次数进行逻辑处理,比如设置一个阈值,比如发送10次,还是不成功,这是为啥已失败状态,避免重复处理。

针对这种多次不成功的消息,比如已失败状态的消息,根据实际的业务情况,可以考虑降低重试次数(比如待发送的消息10分钟重试一次,已失败的消息6小时重试一次),或者告警出来人工跟进。

任务执行信息 ​

retry_countINT重试次数
next_retry_atDATETIME下一次重试时间(用于任务调度)
last_retry_timeDATETIME上一次的重试时间(用于任务调度)
fail_reasonTEXT失败原因,可以记录具体的错误码,或者异常的堆栈信息

以上几个是任务重试执行相关的信息,包括了重试的次数,上次重试时间、下一次重试时间,以及失败原因等。方便我们知道消息的执行情况,和快速的定位问题。

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)等等。