交接不是总结
卷二的收口不是把前十二章再总结一遍,而是把“哪些结论只在单进程、单节点、内存适配器里成立”写成一张可执行的交接单。总结可以模糊,交接单不能:每一项必须有负责人、指标口径、验收方式和到期日,否则卷三无法判断该先验证什么。
这张清单只登记卷二没有证明的假设,不重复已经验证的结论。六项里五项 unknown、一项 target,measured 为 0;其中三项 blocking,必须在卷三开工前拿到验收结果或明确降级方案。
| ID | 假设 | 主题 | 负责人 | 指标口径 | 状态 | 阻塞 |
|---|---|---|---|---|---|---|
| H-01 | 跨节点写可以保持一致 | 分布式事务与一致性 | distributed-platform | 双写故障下的对账差异单数 | unknown | 是 |
| H-02 | 复制延迟不影响故障切换 | 复制与读写分离 | dba | 跨区复制 P99 延迟 | unknown | 是 |
| H-03 | 网络分区不会产生双主 | 分区、租约与 fencing | sre | 分区期间双主写入次数 | unknown | 是 |
| H-04 | 消息端到端不丢不重 | 消息投递语义与幂等 | messaging | 端到端重复率与丢失率 | unknown | 否 |
| H-05 | 分布式 ID 与锁在并发下无冲突 | 分布式 ID 与租约 | platform | ID 冲突数与租约误判次数 | unknown | 否 |
| H-06 | 对账与补偿能收敛最终一致 | 对账、补偿与最终一致 | finance-platform | 超过 24h 未平账订单数 | target | 否 |
验收方式也逐条写死:H-01 注入双写失败后 24h 对账差异必须为 0,否则回滚单写;H-02 计划内切换 RPO 不超过 5s 且切换后 checksum 一致;H-03 注入分区后 fencing 生效、双主写入为 0,仲裁不可用时拒绝写;H-04 用 10 万条故障注入消息检查重复与丢失,包含 DLQ 与重放;H-05 并发 1 万次分配冲突为 0、租约过期误判为 0;H-06 要求 T+1 对账完成率 100%、超 24h 未平账为 0。
检查清单的数据结构与校验规则
Gaps() 是唯一判定入口:必填 id、assumption、topic、owner、metric;acceptance 不能为空;volume 必须是 vol3;measured 必须同时有 command、result、source;unknown/target 不能带 result,且必须有合法到期日;blocking 项即使状态是 target,也必须有到期日。
// file: internal/handoffv3/handoff.go
package handoffv3
import (
"strings"
"time"
)
// Kind 只允许三种证据状态;measured 必须有命令、结果和来源。
type Kind string
const (
Measured Kind = "measured"
Target Kind = "target"
Unknown Kind = "unknown"
)
const (
CodeMissing = "MISSING_FIELD"
CodeBadKind = "BAD_KIND"
CodeBadDue = "BAD_DUE"
CodeDuplicateID = "DUPLICATE_ID"
CodeMeasuredNoEvidence = "MEASURED_NO_EVIDENCE"
CodeFakeMeasured = "FAKE_MEASURED"
CodeUnknownHasResult = "UNKNOWN_HAS_RESULT"
CodeBlockingNoDue = "BLOCKING_NO_DUE"
CodeNoAcceptance = "NO_ACCEPTANCE"
CodeWrongVolume = "WRONG_VOLUME"
)
type Item struct {
ID, Assumption, Topic, Owner, Metric, Acceptance string
Kind Kind
Command, Result, Source string
Blocking bool
Due string
Volume string
}
type Handoff struct {
From, To, Date string
Items []Item
}
type Gap struct {
Code, Ref, Detail string
}
type Counts struct {
Total, Measured, Target, Unknown, Blocking int
}
func validDate(s string) bool {
_, err := time.Parse("2006-01-02", s)
return err == nil
}
// fakeResult 是词法护栏,不是证明;它只拦截把“未测/unknown”写进结果字段。
func fakeResult(s string) bool {
t := strings.ToLower(strings.TrimSpace(s))
if t == "" {
return false
}
for _, marker := range []string{"unknown", "tbd", "n/a", "not measured", "未测", "待测", "未知"} {
if strings.Contains(t, marker) {
return true
}
}
return false
}
func (h Handoff) Gaps() []Gap {
var out []Gap
add := func(code, ref, detail string) { out = append(out, Gap{code, ref, detail}) }
if _, err := time.Parse(time.RFC3339, h.Date); err != nil {
add(CodeBadDue, "handoff", h.Date)
}
seen := map[string]bool{}
for _, it := range h.Items {
ref := it.ID
if ref == "" {
ref = "?"
}
if seen[it.ID] {
add(CodeDuplicateID, ref, "declared twice")
}
seen[it.ID] = true
for _, f := range []struct{ name, value string }{
{"id", it.ID}, {"assumption", it.Assumption}, {"topic", it.Topic},
{"owner", it.Owner}, {"metric", it.Metric},
} {
if strings.TrimSpace(f.value) == "" {
add(CodeMissing, ref, f.name)
}
}
if strings.TrimSpace(it.Acceptance) == "" {
add(CodeNoAcceptance, ref, "acceptance required")
}
if it.Volume != "vol3" {
add(CodeWrongVolume, ref, it.Volume)
}
switch it.Kind {
case Measured:
if it.Command == "" || it.Result == "" || it.Source == "" {
add(CodeMeasuredNoEvidence, ref, "measured needs command, result and source")
}
if fakeResult(it.Result) {
add(CodeFakeMeasured, ref, it.Result)
}
case Target, Unknown:
if it.Result != "" {
add(CodeUnknownHasResult, ref, "unknown/target must not carry a measured result")
}
if !validDate(it.Due) {
add(CodeBadDue, ref, it.Due)
}
default:
add(CodeBadKind, ref, string(it.Kind))
continue
}
if it.Blocking && !validDate(it.Due) {
add(CodeBlockingNoDue, ref, it.Due)
}
}
return out
}
func (h Handoff) Ready() bool { return len(h.Gaps()) == 0 }
func (h Handoff) Counts() Counts {
var c Counts
for _, it := range h.Items {
c.Total++
switch it.Kind {
case Measured:
c.Measured++
case Target:
c.Target++
case Unknown:
c.Unknown++
}
if it.Blocking {
c.Blocking++
}
}
return c
}
// MeasuredShare 只统计真正有证据的条目;unknown 不是 measured。
func (h Handoff) MeasuredShare() int {
c := h.Counts()
if c.Total == 0 {
return 0
}
return c.Measured * 100 / c.Total
}
func (h Handoff) BlockingUnknown() int {
n := 0
for _, it := range h.Items {
if it.Blocking && it.Kind != Measured {
n++
}
}
return n
}
fakeResult 只做词法护栏:结果里出现 unknown、tbd、n/a、not measured、未测、待测、未知,就判定为假 measured。它不是证明,真正的证据仍然要人看命令、运行次数和报告路径。
六项登记数据
登记数据与校验逻辑分离,评审时只改数据文件。六项全部标为 vol3 的目标,H-01 到 H-03 是阻塞项,到期日从 2026-12-15 排到 12-28;这些日期是计划日期,不是已经完成的验证日期。
// file: internal/handoffv3/data.go
package handoffv3
// VolumeTwoToThree 登记卷二仍未证明的六个跨节点假设。
// 它们全部是 unknown/target;卷二只证明了单进程内的行为。
func VolumeTwoToThree() Handoff {
return Handoff{From: "卷二 高性能与高可用", To: "卷三 分布式与一致性", Date: "2026-10-22T04:00:00+08:00", Items: []Item{
{ID: "H-01", Assumption: "跨节点写可以保持一致", Topic: "分布式事务与一致性",
Owner: "distributed-platform", Metric: "双写故障下的对账差异单数", Acceptance: "注入双写失败后 24h 对账差异=0;否则回滚到单写", Kind: Unknown, Blocking: true, Due: "2026-12-15", Volume: "vol3"},
{ID: "H-02", Assumption: "复制延迟不影响故障切换", Topic: "复制与读写分离",
Owner: "dba", Metric: "跨区复制 P99 延迟", Acceptance: "计划内切换 RPO<=5s,切换后 checksum 一致", Kind: Unknown, Blocking: true, Due: "2026-12-18", Volume: "vol3"},
{ID: "H-03", Assumption: "网络分区不会产生双主", Topic: "分区、租约与 fencing",
Owner: "sre", Metric: "分区期间双主写入次数", Acceptance: "注入分区后 fencing 生效,双主写入=0;仲裁不可用时拒绝写", Kind: Unknown, Blocking: true, Due: "2026-12-20", Volume: "vol3"},
{ID: "H-04", Assumption: "消息端到端不丢不重", Topic: "消息投递语义与幂等",
Owner: "messaging", Metric: "端到端重复率与丢失率", Acceptance: "10 万条故障注入消息,重复=0、丢失=0(含 DLQ 与重放)", Kind: Unknown, Due: "2026-12-22", Volume: "vol3"},
{ID: "H-05", Assumption: "分布式 ID 与锁在并发下无冲突", Topic: "分布式 ID 与租约",
Owner: "platform", Metric: "ID 冲突数与租约误判次数", Acceptance: "并发 1 万次分配冲突=0;租约过期误判=0", Kind: Unknown, Due: "2026-12-25", Volume: "vol3"},
{ID: "H-06", Assumption: "对账与补偿能收敛最终一致", Topic: "对账、补偿与最终一致",
Owner: "finance-platform", Metric: "超过 24h 未平账订单数", Acceptance: "T+1 对账完成率 100%,超 24h 未平账=0", Kind: Target, Due: "2026-12-28", Volume: "vol3"},
}}
}
用测试固定规则
测试分三组:第一组验证六项登记没有 gap 且计数为 total=6、measured=0、target=1、unknown=5、blocking=3;第二组用 11 个变异用例逐一触发校验码;第三组证明一条证据齐全的 measured 会被接受,避免校验器只会拒绝、不会放行。
// file: internal/handoffv3/handoff_test.go
package handoffv3
import "testing"
func baseItem() Item {
return Item{
ID: "H-99", Assumption: "replication lag stays bounded", Topic: "replication",
Owner: "dba", Metric: "replica lag p99",
Acceptance: "lag <= 5s during planned failover",
Kind: Unknown, Blocking: true, Due: "2026-12-18", Volume: "vol3",
}
}
func handoffWith(items ...Item) Handoff {
return Handoff{From: "vol2", To: "vol3", Date: "2026-10-22T04:00:00+08:00", Items: items}
}
func hasCode(gaps []Gap, code string) bool {
for _, g := range gaps {
if g.Code == code {
return true
}
}
return false
}
func measured(it *Item, cmd, result string) {
it.Kind, it.Command, it.Result, it.Source, it.Due, it.Blocking = Measured, cmd, result, "report", "", false
}
func TestVolumeTwoToThree(t *testing.T) {
h := VolumeTwoToThree()
if gaps := h.Gaps(); len(gaps) != 0 {
t.Fatalf("handoff has %d gap(s): %v", len(gaps), gaps)
}
c := h.Counts()
if c.Total != 6 || c.Measured != 0 || c.Unknown != 5 || c.Target != 1 || c.Blocking != 3 {
t.Fatalf("counts=%+v", c)
}
if h.MeasuredShare() != 0 || h.BlockingUnknown() != 3 || !h.Ready() {
t.Fatalf("share=%d blocking_unknown=%d ready=%v",
h.MeasuredShare(), h.BlockingUnknown(), h.Ready())
}
t.Logf("total=%d measured=%d target=%d unknown=%d blocking=%d measured_share=%d%% blocking_unknown=%d",
c.Total, c.Measured, c.Target, c.Unknown, c.Blocking, h.MeasuredShare(), h.BlockingUnknown())
}
func TestGapRules(t *testing.T) {
tests := []struct {
name string
mut func(*Item)
extra []Item
want string
}{
{"missing owner", func(it *Item) { it.Owner = "" }, nil, CodeMissing},
{"missing metric", func(it *Item) { it.Metric = "" }, nil, CodeMissing},
{"bad kind", func(it *Item) { it.Kind = "guess" }, nil, CodeBadKind},
{"blocking without due", func(it *Item) { it.Due = "" }, nil, CodeBlockingNoDue},
{"malformed due", func(it *Item) { it.Due = "2026/12/18" }, nil, CodeBadDue},
{"duplicate id", func(*Item) {}, []Item{baseItem()}, CodeDuplicateID},
{"measured without command", func(it *Item) { measured(it, "", "p99=12ms") }, nil, CodeMeasuredNoEvidence},
{"measured result says untested", func(it *Item) { measured(it, "go test ./...", "未测") }, nil, CodeFakeMeasured},
{"unknown carries result", func(it *Item) { it.Result = "p99=12ms" }, nil, CodeUnknownHasResult},
{"missing acceptance", func(it *Item) { it.Acceptance = "" }, nil, CodeNoAcceptance},
{"wrong target volume", func(it *Item) { it.Volume = "vol2" }, nil, CodeWrongVolume},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
it := baseItem()
tt.mut(&it)
gaps := handoffWith(append([]Item{it}, tt.extra...)...).Gaps()
if !hasCode(gaps, tt.want) {
t.Fatalf("gaps=%v want %s", gaps, tt.want)
}
t.Logf("case=%s gaps=%d first=%s/%s", tt.name, len(gaps), gaps[0].Code, gaps[0].Ref)
})
}
}
func TestMeasuredWithEvidenceAccepted(t *testing.T) {
it := baseItem()
measured(&it, "go test ./internal/replication/ -run TestLag -count=3", "p99=180ms (2026-11-30, 3 runs)")
it.Source = ".local/reports/be-arch/replication-lag.txt"
h := handoffWith(it)
if gaps := h.Gaps(); len(gaps) != 0 {
t.Fatalf("honest measured item rejected: %v", gaps)
}
if c := h.Counts(); c.Measured != 1 || h.MeasuredShare() != 100 {
t.Fatalf("counts=%+v share=%d", c, h.MeasuredShare())
}
t.Logf("measured_accepted total=%d measured_share=%d%%", h.Counts().Total, h.MeasuredShare())
}
真实运行输出
命令:go test ./internal/handoffv3/ -count=1 -v。环境为 macOS darwin/arm64、Go 1.26.0;测试串行执行,并发为 1,运行 1 次。完整输出保存在 .local/reports/be-arch/be-arch-perf-13-3-into-volume-three-tests.txt。
=== RUN TestVolumeTwoToThree
handoff_test.go:44: total=6 measured=0 target=1 unknown=5 blocking=3 measured_share=0% blocking_unknown=3
--- PASS: TestVolumeTwoToThree (0.00s)
=== RUN TestGapRules
handoff_test.go:75: case=missing owner gaps=1 first=MISSING_FIELD/H-99
handoff_test.go:75: case=bad kind gaps=1 first=BAD_KIND/H-99
handoff_test.go:75: case=blocking without due gaps=2 first=BAD_DUE/H-99
handoff_test.go:75: case=duplicate id gaps=1 first=DUPLICATE_ID/H-99
handoff_test.go:75: case=measured without command gaps=1 first=MEASURED_NO_EVIDENCE/H-99
handoff_test.go:75: case=measured result says untested gaps=1 first=FAKE_MEASURED/H-99
handoff_test.go:75: case=unknown carries result gaps=1 first=UNKNOWN_HAS_RESULT/H-99
handoff_test.go:75: case=wrong target volume gaps=1 first=WRONG_VOLUME/H-99
--- PASS: TestGapRules (0.00s)
=== RUN TestMeasuredWithEvidenceAccepted
handoff_test.go:91: measured_accepted total=1 measured_share=100%
--- PASS: TestMeasuredWithEvidenceAccepted (0.00s)
PASS
ok archlab/internal/handoffv3 0.463s
校验器拦住了什么
最容易发生的不是字段漏填,而是把“还没验证”写成 measured。MEASURED_NO_EVIDENCE 拦住缺命令、结果或来源;FAKE_MEASURED 拦住结果里写“未测”;UNKNOWN_HAS_RESULT 拦住 unknown 携带测量值;BLOCKING_NO_DUE 拦住阻塞项没有到期日。空到期日会同时触发 BAD_DUE 和 BLOCKING_NO_DUE,所以变异用例看到 2 个 gap,但第一个仍是 BAD_DUE。
另一个边界:到期日只是计划日期,不是验证结果;Due 合法不代表假设成立。Ready() 只说明清单格式完整,不说明系统已经具备跨节点一致性。校验器能拦住自欺,拦不住设计错误。
卷三回答什么,不回答什么
卷三会展开分布式事务与一致性、复制与读写分离、分区与 fencing、消息投递语义与幂等、分布式 ID 与租约、对账与补偿。它回答“跨节点怎么写、怎么读、怎么恢复”,但不替卷二回答单机性能问题;容量、压测、降级和故障注入仍是卷二的基础。
本节不引用卷三的具体章节链接,也不把计划中的验收写成已完成。六项假设全部保持 unknown/target,直到卷三给出命令、运行次数和报告路径;在那之前,measured share 就是 0。
小结
一份合格的交接清单不是“我们做了什么”,而是“我们还没证明什么、谁负责、怎么验收、什么时候到期”。六项假设、三个阻塞项、0% measured 是卷二交给卷三的起点;校验器保证格式和诚实性,真正的证明要等卷三的故障注入和跨节点实验。
阅读导航:上一节:13.2 订单系统必测的三个故障场景 · 下一节:回到目录 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。