工程黑话对照表¶
建立日期:2026-09-01 本仓库文档(PLAN、ADR、CLAUDE.md、专题文档)大量使用分布式系统与性能工程的行话。 本表用大白话逐条解释,并在有必要处给出「本项目里指什么」。 与
docs/glossary.md(领域名词)、docs/glossary-modules.md(名词×模块归属)分工: 那两份解释这个系统有什么,本份解释我们说话的方式。
1. 可靠性语义¶
失败闭合(fail closed) — 出错时选择「停下来」而不是「猜一个结果继续跑」。例:读到损坏的水位数据就拒绝服务,而不是假装水位是 0 继续投递——停服是可恢复的,投错数据不可恢复。反义词是 fail open(出错时放行)。本项目几乎所有存储校验都失败闭合;唯一显式 fail open 的地方是审核回调的 QIM_WEBHOOK_FAIL_POLICY 可配置项。
幂等(idempotent) — 同一个操作执行一次和执行多次,结果完全一样。网络会重发、进程会重启,所以任何「可能被执行两次」的操作都必须设计成幂等,否则用户会看到两条一样的消息。
幂等键(idempotency key) — 用来判断「这个请求我是不是已经处理过」的唯一标识。本项目发消息的幂等键是 client_message_id(客户端生成,重发时不变)。
至少一次(at-least-once) — 投递保证的一种:消息保证送到,但可能送到不止一次,由接收方靠幂等去重。与之相对的「恰好一次(exactly-once)」在跨进程崩溃场景下无法真正保证,所以本项目禁止宣称 exactly-once——统一说「至少一次 + 幂等收敛」。
静默(silent) — 坏事发生了但没有任何报错、日志或指标,谁也不知道。「静默丢消息」「静默降级」是最危险的失败模式,因为查不到。本项目大量设计(失败闭合、恒零指标)就是为了把静默失败变成显式失败。
假绿(false green) — 测试或健康检查显示通过,但实际功能是坏的。例:readiness 只检查「能建连接」不检查「能真的写入」,broker 挂了检查照样绿。
假阳性(false positive) — 报警说有问题,实际没问题。报警假阳性多了会被当狼来了忽略,所以本项目规定「恒零指标不允许假阳性」。
恒零指标(zero-invariant metric) — 一类特殊监控指标:正常时永远是 0,只要非 0 就代表出现了绝不该发生的事(如数据损坏),语义是「立即停止发布」。/healthz 看到非零直接返回 503 让编排系统摘掉实例。
兜底(fallback / safety net) — 主路径失败后的最后保障手段。例:推送丢了没关系,客户端重连后拉邮箱能补回来——邮箱就是推送的兜底。兜底被触发通常要告警,因为它意味着主路径出了问题。
收敛(converge) — 系统经过重试、重放之后最终到达正确状态,中间过程允许短暂不一致。「由下游去重收敛」= 上游可能发重复,下游靠幂等最终归一。
2. 并发与排队(性能话题的高频词)¶
ρ(rho,利用率) — 排队论符号:资源的繁忙程度,= 到达速率 × 单次服务时间。ρ 接近 1(100%)时排队时间会爆炸式增长而不是线性增长——这就是为什么 CPU 从 80% 到 95% 延迟可能涨十倍。本项目容量规划要求 Redis 的 ρ ≤ 0.65。
打满 / 饱和(saturated) — ρ 到了 1:资源没有任何空闲,新请求只能排队。「单线程 Redis 打满」= 那一个核 100% 忙。
排队(queueing) — 请求等待被处理的时间。客户端看到的延迟 = 排队 + 真正干活的时间。诊断口诀:服务时间 237µs 但客户端观测 4,388µs,多出来的 18 倍全是排队。
长尾(tail latency) — 大多数请求很快,极少数特别慢的那部分。平均值看不出长尾,必须看 P99。
P50 / P95 / P99 — 百分位延迟。P99 = 100 个请求里第 99 快的那个的耗时,即「最慢的 1% 至少这么慢」。IM 的体验由长尾决定,所以门禁看 P99 不看平均。
SLO(Service Level Objective) — 服务质量目标,如「端到端 P99 ≤ 300ms」。不达标就算功能正确也不能发布。
热路径(hot path) — 每条消息都要走的那段代码,性能敏感度最高。在热路径上多一次 Redis 写、多一次内存拷贝都会被放大成全局吞吐差异。反义词冷路径(如启动、恢复,慢点没关系)。
热点 / 倾斜(hot spot / skew) — 负载没有均匀摊开,某个分片/键/实例被压得比别人重。1000 个用户哈希到 64 个分片,必然有的分片摊到 25 个、有的只有 8 个——这就是倾斜;摊到 25 个的那个就是热点。
队头阻塞(HOL,head-of-line blocking) — 排在队伍最前面的那一个卡住了,后面所有人跟着等,哪怕他们的事一秒就能办完。本项目踩过的实例:一个过载分片的队列满了,中心消费循环在它上面 await,另外 63 个空闲分片全部陪着饿死。修法是只暂停那一个分片的 Kafka 分区。
反压(backpressure) — 下游忙不过来时,把「慢下来」的信号传回上游,而不是无限堆积或丢弃。例:队列满了就停止从 Kafka 拉取,让积压留在 broker 里。
单飞(single-flight) — 同一时刻同一件事最多只有一个在途请求;期间再来的触发只做个标记(置 dirty),等在途的完成后看标记决定要不要再来一次。本项目客户端收到推送后拉邮箱就是单飞。
惰性(inert / lazy) — 机制存在但平时完全不工作、零开销,只在特定条件下激活。「分区暂停在不过载时完全惰性」= 不过载时这段代码等于不存在。
在途(in-flight) — 已经发出、还没收到结果的请求。「登记在途、完成销账」是旧直写模型计算连续水位的方式;当前 dispatch offset 驱动的水位不依赖在途集合。
短压 / 长压 — 本项目自造:短时间压测(60 秒)与长时间压测(180 秒)。短压只能定位瓶颈,不能当容量结论——有些问题(哈希表翻倍、内存增长)只在长跑里显形。
冷库 — 本项目自造:空数据起跑的压测环境,与「预热过的库」对照。
3. 分布式协调¶
fence(围栏 / 隔离旧主) — 换新负责人之前,先确保旧负责人再也写不进来(停掉它、吊销它的凭证),否则新旧两个都认为自己是主,同时写就会互相覆盖。「升级必须先 fence 旧 owner」= 先停旧进程再起新的,不允许两代码同时在线。
脑裂(split-brain) — 没 fence 好的后果:两个节点同时认为自己是唯一的主,各写各的,数据分叉。
CAS(compare-and-swap) — 「先比对再写入」的原子操作:只有当前值等于我预期的旧值时才写入新值,否则失败重来。用于无锁并发下防止互相覆盖。本项目水位推进就是 CAS:带上「我认为的前一个值」,对不上就说明有并发或损坏。
位点(offset / position) — 消费者在日志里读到哪儿了的记录。「位点提交」= 把这个进度持久化,崩溃重启后从这里继续。本项目 mailbox 从不提交位点——恢复真相是自己的持久水位,Kafka 位点只当性能提示。
高水位(high watermark) — Kafka 术语:分区里已确认写入的最后位置。消费者追到高水位 = 没有积压。
消费者组(consumer group)/ rebalance — Kafka 的自动分工机制:一组消费者自动瓜分分区,成员变化时自动重新分配(rebalance)。本项目禁用它(assign() 静态指派):rebalance 的时机不可控,会在最坏的时刻把分区从一个节点挪到另一个。
事务生产者(transactional producer) — Kafka 的「读-处理-写」原子机制:一批输出和位点提交要么都生效要么都不生效。本项目 fanout 曾用过、后来去掉了(ADR-0012)——去重靠确定性 dispatch_id,不靠 Kafka 事务。
read_committed — Kafka 消费隔离级别:只读已提交事务的消息,未提交/已中止的跳过。
compacted topic — Kafka 的「只保留每个 key 最新值」的 topic 类型,适合存最新状态(如在线位置),不适合存历史。
时钟回拨(clock regression) — 机器时间往回跳(NTP 校时、虚拟机迁移都会造成)。靠墙钟生成的递增 ID 会因此重复。本项目 HLC 从 Redis 领取持久时间窗口,回拨时拒绝发号而不是复用旧 ID。
墙钟(wall clock) — 机器上的日历时间(SystemTime),会跳、会回拨。与之相对的单调钟(Instant)只会前进但没有日历意义。规则:算时长用单调钟,记时刻用墙钟,排序不信任何钟(用序列号)。
HLC(Hybrid Logical Clock,混合逻辑时钟) — 物理时间 + 逻辑计数器的组合:正常时接近真实时间,时钟异常时靠计数器保证仍然单调递增。本项目 message_id 的时间来源。
TOCTOU(time-of-check to time-of-use) — 「检查时没问题,使用时已经变了」的竞态。例:检查端口空闲 → 别的进程抢先占用 → 我绑定失败。本项目集成环境选端口就有这个已知窗口,靠「绑定失败即安全退出」兜底。
滚动升级(rolling upgrade) — 一台一台换新版本,新旧代码短暂共存,服务不中断。本项目 packed-v2 明确不支持同分片内滚动升级——旧代码读到新格式会失败闭合(安全但停服),所以必须先 fence 再换。
灰度(canary / gradual rollout) — 先让一小部分流量走新版本,观察没问题再放量。
租约(lease) — 带过期时间的独占权:拿到租约才能当负责人,必须定期续约,断了自动失效——防止挂掉的节点永远占着位置。本项目 writer_id 由 Redis 租约分配(20 秒续约)。
接管(takeover) — 新节点接手一个分片时,从持久状态推导出「从哪儿继续」的过程。
4. 存储¶
物化(materialize) — 把「一条待办的投递指令」变成「用户邮箱里真实存在的条目」的过程。更一般地:把计算结果落成持久数据,之后读它不用再算。mailbox 的核心工作就是物化 dispatch 日志。
水位(watermark) — 「处理到哪儿了」的持久进度标记。本项目当前特指按 dispatch partition offset 驱动的连续水位:同一 partition/shard 顺序消费,W[lane] 是持久的 64-lane 物化状态;仅当某条 record 的全部 lane/chunk/entries 已 durable,且 expected_previous 的 CAS 连续校验通过后,才推进到该 record 的 observed_log_offset。任一失败或缺口都不推进,因此它表示此前记录均已完整物化,不能以「见过的最大 offset」跨过未完成记录,否则客户端游标会静默跳过消息。W = min(在途) - 1 及分配时登记、完成时销账仅是旧直写模型,非现行实现。
空洞(gap / hole) — 序列号不连续。本项目 conversation_seq 合法存在空洞(预留窗口、治理删除),所以禁止用「序号差值」判断丢没丢消息。
盲写(blind write) — 不先读旧值、直接覆盖的写入。快,但前提是你确信不需要看旧值。「校验后盲写」= 前面已经证明过状态了,写入阶段不再读。
写放大(write amplification) — 逻辑上写 1 份数据,物理上产生多份写入(存储引擎整理、AOF 追加、副本复制)。减少每条消息的写命令数就是在降写放大。
末值合并(last-write-wins conflation) — 同一个 key 连续多次更新时只保留最后一次。有效前提是「到达顺序 = 逻辑顺序」;本项目曾因乱序到达 + 末值合并把会话头写倒退。
AOF(append-only file) — Redis 的持久化方式:每个写命令追加进日志文件,重启回放恢复。本项目生产 Redis 强制开启。
fsync / everysec — 把操作系统缓冲的数据真正刷到磁盘。appendfsync everysec = 每秒刷一次,崩溃最多丢 1 秒。本项目只允许 everysec 或 always。
noeviction — Redis 内存满时的策略:拒绝新写入而不是悄悄删旧数据。业务数据存 Redis 必须用它——被「淘汰」掉一个提交事实等于静默丢数据。
evalsha / Lua 脚本 — 在 Redis 服务端原子执行一段脚本(按脚本哈希调用即 EVALSHA)。价值是把「读-判断-写」多步变成一步原子操作;代价是脚本执行占用 Redis 唯一的线程。
pipeline — 把多条 Redis 命令攒一批发出,省网络往返。与事务不同:pipeline 只省往返,不保证原子。
rehash — 哈希表装满后翻倍扩容并搬迁所有条目。Redis 的增量 rehash 会在扩容期间给每个操作加一点税;键数量到 2^20、2^21 这类翻倍点时历史上曾把 P99 推高数倍。
墓碑(tombstone) — LSM 类存储(Scylla)里「删除」不是真删,而是写一条删除标记,真正清理要等压实。大量墓碑会拖慢读。本项目因此禁止显式 DELETE,删除走 TTL。
TWCS(Time Window Compaction Strategy) — Scylla 按时间窗口分文件的压实策略,配 TTL 数据最高效(整个过期窗口直接扔),但范围删除会破坏它。
LWT(Lightweight Transaction) — Scylla 的 CAS:INSERT ... IF NOT EXISTS 这类带条件的写,付出额外一轮共识的代价换取「只有一个赢家」。
LOCAL_QUORUM / RF — RF(replication factor)= 每份数据存几个副本;LOCAL_QUORUM = 本机房过半数副本确认才算写成功。本项目 Scylla 生产门禁 RF=3 + LOCAL_QUORUM。
TTL(time to live) — 数据的自动过期时间,到点由存储引擎自己清掉。
COW(copy-on-write) — fork 子进程时不复制内存,谁写谁才复制那一页。Redis bgsave 靠它,但写入越猛复制越多。
lag / backlog(滞后 / 积压) — 生产速度超过消费速度时堆下的未处理量。「Outbox lag 15ms」= fanout 落后 writer 15 毫秒的量。
5. 路由与分片¶
虚拟桶(virtual bucket) — 用户和物理节点之间的固定中间层:用户 → 桶(数量固定不变,如 65,536)→ 节点。扩容时只改「桶→节点」这张小表,用户归属永不变。禁止跳过它直接 用户 % 节点数——节点数一变全体用户重新洗牌。
一致性哈希(consistent hashing) — 一类「节点增减时只挪最少量数据」的映射算法的统称。
会合哈希(rendezvous / HRW hashing) — 一致性哈希的一种简单实现:对每个候选节点算 hash(key, 节点),取分数最高者。加一个节点只抢走约 1/N 的 key,删一个节点只有它自己的 key 需要挪。本项目 gateway 选 writer 用它。
亲和路由(affinity routing) — 尽量把同一个会话的请求送到同一个节点(提高缓存命中、天然串行化),但只是优化:送错节点也必须正确。
6. 本项目自造词¶
门禁(gate) — 必须通过才能进入下一阶段的自动检查(CI 契约检查、验收脚本、性能阈值)。「混沌门禁」= 故意杀进程、断日志的故障注入测试。
泳道(lane,压测语境) — 压测流量里的一个独立分组。「SDK 回归泳道」= 5% 的压测连接用真实 SDK 驱动,其余走轻量帧层。(注意与邮箱的 lane_id 无关——后者是目标可见性隔离通道;当前只落地映射/存储格式,独立推进尚未实现,见 glossary-modules.md。)
顶号(session replacement) — 同一账号在别处登录,旧连接被踢下线(KICKED{REPLACED})。
冒烟(smoke test) — 最基本的「跑起来没冒烟」级功能验证,覆盖主链路 15 项。
隔离项目(isolated project) — 集成测试用的独立 docker compose 项目(qim-it-* 前缀、独立端口/卷/网络),跑完整体删除,绝不碰共享的 6379 或别人的容器。
号段(ordinal segment) — 客户端一次性预留一段连续序号(如 100 个),用完再领,避免每条消息都做一次持久化分配。
假设-证伪 — 本项目性能工作的纪律用语:每个性能假设必须实测验证;ADR-0014 记录了同一问题上连续四次推断被实测推翻的完整过程。
证据链 / 工件(artifacts) — 压测和验收产出的原始数据文件(指标快照、日志、perf 记录),结论必须能回溯到工件。