feat(codec): 观测未落库的 SRVT/VIPF 集合

SRVT/VIPF 只保留在 wire/domain 并计数告警,不落明细表、不参与合并:
出现事实不再被静默丢弃,为 Q13 定案提供真实流量证据([G-SRVT-VIPF])。

- wire DTO:SRVT/SERVICEDATA、VIPF/VIPDATA 与嵌套 VIPT(OPER 为元素属性)
- 出现即留键:缺席与"出现但为空"不再等价;已落库 10 类集合语义不变
- 新增 msgx.pipeline.codec.srvt_seen.total / vipf_seen.total(reference.md 已登记)
- 一并提交此前的 MAFL 文档改动(INV-21/INV-22、flight-state §2.3、G-MAFL 措辞)
This commit is contained in:
windyboy
2026-09-13 09:21:51 +08:00
parent b8738554e6
commit 17a4bfe91b
11 changed files with 283 additions and 18 deletions
@@ -1,6 +1,7 @@
package com.gzzn.omms.msgexchange.infra.metrics
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicLong
/**
* 管道运行期的**进程内**计数。
@@ -11,4 +12,22 @@ import jakarta.inject.Singleton
* 注意:计数在重启后归零。需要跨重启的累计值应由指标后端聚合,不在这里做持久化。
*/
@Singleton
class PipelineCounters
class PipelineCounters {
private val srvtSeen = AtomicLong(0)
private val vipfSeen = AtomicLong(0)
/**
* 入站记录里出现 `SRVT`/`VIPF` 段的条数(`[G-SRVT-VIPF]`)。
*
* 这两个集合目前只保留在 wire/domain,不落明细表、不参与合并;计数是"真实报文有没
* 有在用"的唯一取证渠道(清空语义 `Q13` 需要真实样例才能定案)。> 0 表示确有流量携带该段。
*/
fun unpersistedCollectionSeenAdd(hits: Map<String, Int>) {
hits["SRVT"]?.let { srvtSeen.addAndGet(it.toLong()) }
hits["VIPF"]?.let { vipfSeen.addAndGet(it.toLong()) }
}
fun srvtSeenCount(): Long = srvtSeen.get()
fun vipfSeenCount(): Long = vipfSeen.get()
}
@@ -26,6 +26,8 @@ import java.time.Duration
* - `msgx.pipeline.job.last_failure_age_seconds`:距最近一次作业 tick 失败的秒数(从未失败为 -1)
* - `msgx.pipeline.job.ticks.total` / `msgx.pipeline.job.failures.total`:作业 tick 完成/抛错次数
* - `msgx.pipeline.job.last_sweep_selected`:上一轮回填扫描选中的待办条数(扫描积压)
* - `msgx.pipeline.codec.srvt_seen.total` / `msgx.pipeline.codec.vipf_seen.total`:入站记录里出现
* `SRVT`/`VIPF` 段的条数(尚未落明细表,`[G-SRVT-VIPF]`> 0 表示真实流量确有该段)
*
* 取数统一走 [BacklogSnapshotProvider]30 秒 TTL),因此指标抓取不会打穿数据库。
* 无法取数时以 `NaN` 上报(Micrometer 的惯例表示"本次无值"),而不是伪造 0。
@@ -45,6 +47,7 @@ class PipelineMetrics(
private val mailbox: BeanProvider<CminmsgInboxRepository>,
private val activity: JobActivity,
private val clock: Clock,
private val counters: PipelineCounters,
) {
@PostConstruct
@@ -89,6 +92,15 @@ class PipelineMetrics(
Gauge.builder("msgx.pipeline.job.last_sweep_selected", activity) { it.snapshot().lastSweepSelected.toDouble() }
.strongReference(true)
.register(registry)
// SRVT/VIPF 尚未落明细表([G-SRVT-VIPF]):计数替代静默丢弃,为 Q13 提供真实流量证据。
Gauge.builder("msgx.pipeline.codec.srvt_seen.total", counters) { it.srvtSeenCount().toDouble() }
.strongReference(true)
.register(registry)
Gauge.builder("msgx.pipeline.codec.vipf_seen.total", counters) { it.vipfSeenCount().toDouble() }
.strongReference(true)
.register(registry)
}
private fun backlogGauge(name: String, value: (com.gzzn.omms.msgexchange.infra.persistence.Backlog) -> Double) {