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:
@@ -11,18 +11,22 @@ import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 收报(docs/message-lifecycle.md §5.1):共享 MySQL 信箱按 **ID 区间**升序有界读取,
|
||||
* 在自有 PG 建 `PROC_STATE(PENDING)`,并把消费水位推进到连续上界;入队与水位推进在同一
|
||||
* PG 事务内提交,中断即重扫补建(§4 第一行)。收报层不解析业务载荷、不写处理标记。
|
||||
* 收报:轮询共享信箱,把新消息登记到自有 PG 的 PROC_STATE,等着主泵处理。
|
||||
*
|
||||
* 三条红线:
|
||||
* - **扫描谓词不含处理标记**:标记只用于回填与库方清除,不参与消息发现;否则已入队而未
|
||||
* 回填的行会永久占据批次,死信累积到批大小时收报整体停摆;
|
||||
* - **水位遇空洞即停**:不越过空洞入队(越过后较小 ID 迟到即 FIFO 越序,architecture §5);
|
||||
* - **空洞老化**:超过最大提交时延(Q2 承诺)的空洞判定为永久并放行,否则水位会永久停摆于
|
||||
* 一次自增回滚留下的空位。
|
||||
* 每一轮只做三件事:
|
||||
* 1. 从水位 W 之后按 ID 升序读一批行(只看 ID,不看处理标记);
|
||||
* 2. 把读到的行登记成 PENDING,并把水位推到"连续"的位置;
|
||||
* 3. 登记和水位推进写在同一个事务里——中途崩溃时水位没动,下一轮重扫即可补齐。
|
||||
*
|
||||
* 空转代价为每轮一次区间 SELECT。
|
||||
* "连续"是这里唯一需要理解的规则。如果 W 后面缺了一个 ID,说明可能有 ID 更小的消息
|
||||
* 还没提交上来。此时先停在缺口前,不把缺口后面的消息放进队列:否则那条迟到的消息
|
||||
* 会排到它们后面,破坏"先来先处理"的约定,同一航班的报文可能被乱序应用。
|
||||
*
|
||||
* 缺口等超过 `msgx.pipeline.max-commit-delay` 仍未出现,就认定它永远不会来了
|
||||
* (典型情况是自增回滚留下的空位),跳过它继续推进——否则水位会卡在第一个空位上
|
||||
* 再也不动。
|
||||
*
|
||||
* 这一层不解析报文,也不写信箱处理标记:标记由处理完成后的 BackfillService 负责补。
|
||||
*/
|
||||
@Singleton
|
||||
class InboxPoller(
|
||||
@@ -37,14 +41,14 @@ class InboxPoller(
|
||||
@Volatile
|
||||
private var running = false
|
||||
|
||||
/** @return 本轮新建的入队条数 */
|
||||
/** @return 本轮新登记的消息条数(已登记过的行不计入,也不影响水位推进)。 */
|
||||
fun pollOnce(now: Instant = Instant.now()): Int {
|
||||
val batch = props.pipeline.claimBatch.coerceAtLeast(1)
|
||||
val watermark = cursor.load()
|
||||
val rows = mailbox.readRange(watermark.committedUpTo, batch)
|
||||
if (rows.isEmpty()) return 0
|
||||
|
||||
// 连续上界;读取区间内出现空洞时,只推进到连续部分,空洞之后的行暂不入队(防较小 ID 迟到被越过)
|
||||
// 找到连续部分的末尾;如果这批里出现了缺口,缺口后面的行这一轮先不入队
|
||||
val contiguous = contiguousUpTo(watermark.committedUpTo, rows) ?: watermark.committedUpTo
|
||||
var committedTo = contiguous
|
||||
var holeSince: Instant? = null
|
||||
@@ -53,7 +57,7 @@ class InboxPoller(
|
||||
if (Duration.between(since, now) < props.pipeline.maxCommitDelay) {
|
||||
holeSince = since
|
||||
} else {
|
||||
// 空洞老化:超过最大提交时延仍缺席即判永久(Q2),放行水位,否则永久停摆
|
||||
// 缺口等太久了:当成永久缺失跳过,让水位继续往前走
|
||||
committedTo = rows.first { it.msgId > contiguous }.msgId - 1
|
||||
log.warn("hole after W={} aged out, watermark advanced to {}", contiguous, committedTo)
|
||||
}
|
||||
@@ -95,7 +99,10 @@ class InboxPoller(
|
||||
running = false
|
||||
}
|
||||
|
||||
/** 连续上界:从 W+1 起 ID 逐 1 相邻的最后一个;首个空位即停(rows 为升序且覆盖该区间)。 */
|
||||
/**
|
||||
* 从 W+1 开始数,返回 ID 逐 1 相连的最后一个 ID;遇到第一个缺号就停。
|
||||
* 返回 null 表示 W+1 本身就不存在。
|
||||
*/
|
||||
private fun contiguousUpTo(from: Long, rows: List<MailboxRow>): Long? {
|
||||
var expected = from + 1
|
||||
var last: Long? = null
|
||||
|
||||
Reference in New Issue
Block a user