docs(kdoc): 重写信箱生命周期相关 KDoc,改为直白说明并去掉内部编号
原有注释大量引用 §5.1/§5.2/Q2/US-01 这类文档编号和内部简称,跳过了"这段代码在做什么、 为什么这么做",没有读过设计文档的人基本读不懂。本次统一改成先讲清这件事本身、 再说为什么要这样做,编号只在末尾留一处指路。 覆盖本次改动涉及的 24 个 Kotlin 文件(生产 16 个 + 测试 8 个): - 领域与端口:ProcState(补齐全量字段说明与状态/错误分类逐项注释)、 ProcStateRepository / InboxCursorRepository / CminmsgInboxRepository 及 MailboxRow / BackfillDue / Backlog; - 收报:InboxPoller(把"水位连续、遇缺口停下、缺口老化"用大白话讲透)、 InboxService、JdbcCminmsgInboxRepository; - 处理:Pump / MessageProcessor、ScheduleProcessor、DynamicProcessors、 ProcFailure、JdbcProcStateRepository 与游标实现; - 回填与观测:BackfillService、InboxLifecycleHealthIndicator、JobRunner; - 配置与 stub:PipelineProps(三个新增参数说清取值理由)、MailboxProps、 StubRepositories; - 测试:8 个测试类改为"这些用例在守哪几条规矩",并保留 H2 不覆盖 ON CONFLICT 的说明。 术语统一按第一次出现就地解释:水位、处理标记、回填、死信、队头、终态。 纯注释改动;除拆分枚举时按仓库风格补的两个行尾逗号外无代码变更 (已用剥离注释后比对 HEAD 的方式逐文件核对)。测试仍为 78 passed / 1 skipped。
This commit is contained in:
@@ -10,15 +10,20 @@ import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 信箱处理标记回填(docs/message-lifecycle.md §3/§4/§5.2)。
|
||||
* 把"已处理"标记写回共享信箱。
|
||||
*
|
||||
* 回填意图(`BACKFILL_NEXT_AT`)由处理器在终态事务内登记,与业务写入同提交同回滚,
|
||||
* 因此不存在"业务已提交、待办未记"的窗口;本服务只做两件事:
|
||||
* 1. [attempt]:终态提交后立即尝试一次(低延迟,失败静默留给扫描);
|
||||
* 2. [sweep]:到期或已达超期期限 R 的记录批量补写(§5.2),指数退避 30s 起步、封顶 15 分钟。
|
||||
* 一条消息处理完要做两件事:记下终态、把标记写回信箱。第一件在业务事务里完成,
|
||||
* 同时留下"还欠一次回填"的意图(PROC_STATE 上的 BACKFILL_NEXT_AT);
|
||||
* 这个类负责第二件:
|
||||
*
|
||||
* 回填失败绝不重放业务变更,也绝不回改终态(§11);写入侧只把空标写为已处理,
|
||||
* 重复执行无副作用。
|
||||
* - [attempt]:处理刚结束时马上试一次,让标记尽快落到信箱。失败也不影响处理结果,
|
||||
* 留给扫描重试即可。
|
||||
* - [sweep]:定时把还欠回填的记录挑出来重试。失败就按 30 秒起步、最长 15 分钟的
|
||||
* 退避往后推;如果一条消息从收到现在已经超过超期期限,则无视退避强制补写——
|
||||
* 否则退避可能一直失败下去,这些行永远打不上标记,库方就没法清理信箱。
|
||||
*
|
||||
* 两条底线:回填失败不会把终态改回去,也不会重新执行业务逻辑;写标记只写还是空标记的
|
||||
* 行,重复执行没有副作用。
|
||||
*/
|
||||
@Singleton
|
||||
class BackfillService(
|
||||
@@ -40,7 +45,10 @@ class BackfillService(
|
||||
}
|
||||
}
|
||||
|
||||
/** 单条最佳努力回填;失败只登记退避(异常不外抛,不阻塞提交后的处理路径)。 */
|
||||
/**
|
||||
* 处理完立刻试一次。失败只记一笔退避信息就返回,不抛异常——
|
||||
* 调用方是主泵的处理路径,不能被回填问题拖住。
|
||||
*/
|
||||
fun attempt(msgId: Long, now: Instant = clock.instant()) {
|
||||
record(msgId, attempts = 0, now = now)?.let {
|
||||
log.warn("backfill failed msgId={} error={} (sweep will retry)", msgId, it)
|
||||
@@ -48,8 +56,9 @@ class BackfillService(
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量补写(JobRunner 每 30s 触发;重启即继续,不依赖内存状态)。
|
||||
* @return 本批检查条数
|
||||
* 批量补写,由 JobRunner 每 30 秒调用一次。待办状态都在数据库里,
|
||||
* 进程重启后接着跑,不需要额外恢复步骤。
|
||||
* @return 本批处理的条数
|
||||
*/
|
||||
fun sweep(now: Instant = clock.instant()): Int {
|
||||
val due = procState.findBackfillDue(now, now.minus(props.pipeline.overdueBackfill), props.pipeline.backfillBatch)
|
||||
@@ -57,10 +66,10 @@ class BackfillService(
|
||||
return due.size
|
||||
}
|
||||
|
||||
/** @return 失败原因;null = 已确认标记(含"已被其他路径标记"的幂等成功) */
|
||||
/** 回填一条。@return 失败原因;返回 null 表示标记已确认(包括"别的路径已经标过了")。 */
|
||||
private fun record(msgId: Long, attempts: Int, now: Instant): String? =
|
||||
try {
|
||||
// 影响 0 行 = 已有标记;按幂等成功处理(§11 标记单调:不回撤、不覆盖)
|
||||
// 没有真正写进去说明库里已经有标记了,同样算成功(不覆盖已有值)
|
||||
mailbox.markProcessedIfUnmarked(msgId, mailboxProps.processedValue)
|
||||
procState.markBackfilled(msgId, now)
|
||||
null
|
||||
|
||||
Reference in New Issue
Block a user