Day 190 图解:调度、幂等、告警漏斗与部署拓扑
所有图都是文本(Mermaid / ASCII),可直接在 GitHub 渲染,不需要图片文件。
1. 三种调度模型的时间轴
`@text 时间 ──────────────────────────────────────────────────────►
固定延迟(跑完 + interval): [任务1 2s][等15m][任务2 2s][等15m][任务3 2s]... ▲ 每次起点都晚 2s,一天累积 187s
锚点网格(anchor + n*interval): |---------|---------|---------|---------|---------| ▲ ▲ ▲ ▲ ▲ ▲ s0 s1 s2 s3 s4 s5 ← 与耗时无关 `@
@mermaid
flowchart LR
A[anchor 锚点] --> B[slot n = floor(now-anchor / interval)]
B --> C{fire_at 已到?}
C -- 否 --> D[睡到 fire_at]
C -- 是 --> E{slot 已执行?}
E -- 是 --> D
E -- 否 --> F[写入 slot 幂等键]
F --> G[执行巡检]
G --> H[记录结果/告警]
H --> D@
2. 停机恢复:catch-up storm 与 grace
`@text 正常: s1 s2 s3 s4 s5 s6 [✓] [✓] [✓] [✓] [✓] [✓]
宕机 6h: s1 s2 s3 ........................ s24 ← 24 个格子没跑 ▲ 恢复 无策略: ████████████████████████████████████████ 24 次同时补跑 → 打爆目标 有策略: [✓] 只补 1 次 跳过 23 次,写 audit 表:skipped=23 `@
判定公式:
@text
可补跑条件 = (now - fire_at) <= grace_s
实际补跑数 = min(count(可补跑), max_catchup)@
3. 任务状态机(租约 + 重试)
@mermaid
stateDiagram-v2
[*] --> pending: enqueue(idem_key 唯一)
pending --> running: claim(BEGIN IMMEDIATE)
running --> done: 成功 + effects 幂等写
running --> pending: 临时错误(退避 + jitter)
running --> dead: 永久错误 / 超过最大次数
running --> pending: 租约过期(崩溃恢复)
done --> [*]
dead --> [*]@
@text
claim finish
pending ───────► running ─────────────────► done
▲ │
│ 退避到期 │ 崩溃(不 finish)
└───────────────┘
│ lease_until < now
▼
可被其他 worker 重新 claim@
4. 告警五级漏斗
@text
500 events
│
├─① 归一化 ─────────────── 统一字段/词表
│
├─② 去重 fingerprint ───── 444 条被拦(同资产同规则 30 分钟内重复)
│
├─③ 静默 ───────────────── 维护窗口内只计数
│
├─④ 分组 ───────────────── 一个时间窗 = 一条消息
│
└─⑤ 限流令牌桶 ─────────── 超出的进 overflow,下一条合并
│
▼
2 条消息(critical 20/20 全达)@
@mermaid
flowchart TD
E[Alert] --> S{静默窗口?}
S -- 是 --> X[计数丢弃]
S -- 否 --> D{fingerprint 在冷却中?}
D -- 是且无升级 --> X
D -- 否/升级 --> B[进分组缓冲]
B --> W{窗口满 或 批量满?}
W -- 否 --> B
W -- 是 --> T{令牌桶有令牌?}
T -- 是 --> M[合并成一条消息]
T -- 否 --> O[overflow 队列]
O --> M
M --> H[HTTP 投递 + 重试]@
5. 指纹设计三种结局
`@text v1 fingerprint = rule info(10.0.0.1) ─┐ info(10.0.0.2) ─┼─► 同一个键 ─► 只发 1 条 ─► critical 被吞(丢 4 条)
v2 fingerprint = rule + asset + title("第 N 次") 每次抖动都是新键 ─► 125 条消息 ─► 收件人静音
v3 fingerprint = rule + asset,severity 升级放行 12 个资产 = 12 条 + 5 条 critical = 17 条 ─► 零丢失、可读 `@
6. 部署拓扑
@text
host: 127.0.0.1:18090
│
┌────────────▼─────────────┐
│ d190-sink-1 │ GET /health, /counts
│ python sink.py │ POST /alert → /data/sink/alerts.jsonl
│ healthcheck: HTTP 200 │
└────────────▲─────────────┘
│ http://sink:8080/alert(compose 内部 DNS)
┌────────────┴─────────────┐
│ d190-worker-1 │ TARGETS=sink:8080,sink:8081,127.0.0.1:1
│ python worker.py │ interval=5s, jitter=15%
│ healthcheck: 心跳文件 │ SQLite: /data/worker/state.db
└──────────────────────────┘
│
volume d190-data(sink 与 worker 各自子目录)@
7. 一键部署 / 撤收时序
`@mermaid sequenceDiagram participant U as deploy.sh participant D as docker compose participant S as sink participant W as worker
U->>U: preflight(docker / compose / 端口 18090)
U->>D: build
U->>D: up -d --wait
D->>S: start
S-->>D: healthy(/health 200)
D->>W: start(depends_on: service_healthy)
W->>S: POST /alert(首次变更)
S-->>W: 200 {"ok":true}
U->>S: GET /counts
S-->>U: {"alerts":1}
U->>U: 验收通过
U->>D: down -v
D->>S: stop + remove
D->>W: stop + remove
U->>U: 校验端口已释放、容器为 0
`@
8. 真实故障:cron 每分钟触发 + 单次跑 75 秒
`@text 06:03:01 cron 触发 #1 ── BEGIN IMMEDIATE ── 持有写锁 ──────────────┐ 06:04:01 cron 触发 #2 ── BEGIN IMMEDIATE ── 等锁 5s ── ✗ locked │ 06:04:16 #1 COMMIT ─────┘
修复:flock -n 包住整个脚本 06:05:01 cron 触发 #1 ── flock 拿到锁 ── 执行 ── DONE 06:06:01 cron 触发 #2 ── flock -n 失败 ── SKIP(立即退出,不排队) `@