Files
Edu/docs/architecture/issues/worklines/push-gateway_workline.md

20 KiB
Raw Permalink Blame History

push-gateway 工作排期

负责人ai02 关联:workline.mdcoord.mdcontracts/push-gateway_contract.mdobjections/push-gateway_issue.md 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试) 依据:ai-allocation.md §7.2、02-architecture-design.md §12 实施优先级、president-final-rulings.md §3.4 回写义务


§1 总览

push-gateway 是 Go 实现的实时推送基础设施服务L3 网关层),管理 WebSocket 长连接,接收 msg 服务的推送请求投递到在线客户端。无业务状态,仅持有连接池 + Redis 跨实例广播。

全阶段目标

  • 批次 0等待期:复审 02 文档 + 回写 ISSUE-053/055/056/058 裁决 + 提请 ISSUE-001~007 仲裁
  • 批次 4P513 天):完整实现 /internal/push + WebSocket 生命周期 + Redis Pub/Sub + Kafka 消费 + /readyz + /metrics + OTel + 多阶段 Dockerfile + slog + shared-go 接入 + 集成测试
  • 批次 5P6 硬化5 天)Reconnect 协议 + Redis Stream 持久化 + 测试覆盖率 ≥ 80% + ADR/非功能性需求/失败模式章节补全

关键路径依赖

  • 批次 0.14shared-go 包骨架coord—— tracer/logger/jwks/env 4 模块
  • 批次 1iam P2.1JWT RS256 + JWKS 端点)—— WebSocket 鉴权前置
  • 批次 4msg gRPC 50056 + edu.notification.requested topic —— 推送事件来源

§2 全阶段甘特图(批次 0 + 批次 4 + 批次 5

gantt
    title ai02 push-gateway 全阶段排期(批次 0 + 批次 4 P5 + 批次 5 P6
    dateFormat YYYY-MM-DD
    axisFormat %m-%d

    section 批次0 等待期 2天
    0.1 复审01/02文档+核查已有仲裁 :crit, a0a, 2026-07-10, 1d
    0.2 回写ISSUE-053/055/056/058到02文档 :crit, a0b, after a0a, 1d
    0.3 提请ISSUE-001~007待coord仲裁 :a0c, after a0a, 1d

    section 批次4 P5-P0 安全加固 3天
    4.1 Origin校验+CheckOrigin白名单 :crit, a4a, after b3a, 1d
    4.2 /internal/*鉴权对齐X-Internal-Token :crit, a4b, after a4a, 1d
    4.3 心跳改WebSocket控制帧+SetReadDeadline :crit, a4c, after a4a, 1d
    4.4 单用户连接数限制MaxConn=5 :crit, a4d, after a4c, 1d
    4.5 Send通道满时指标+日志 :a4e, after a4d, 1d

    section 批次4 P5-P0 基础设施 2天
    4.6 Dockerfile重构多阶段+非root+healthcheck+ldflags :crit, a4f, after b3a, 1d
    4.7 引入log/slog替换标准log :a4g, after a4f, 1d
    4.8 接入shared-go tracer/logger/jwks/env :a4h, after a4g, 1d

    section 批次4 P5-P1 横向扩展 3天
    4.9 Redis Pub/Sub跨实例广播 :crit, a4i, after a4h, 2d
    4.10 Redis SET在线状态+启动重建(ISSUE-058) :crit, a4j, after a4i, 1d
    4.11 /metrics自定义指标+/readyz软失败(ISSUE-055) :a4k, after a4j, 1d
    4.12 优雅关闭所有WebSocket连接 :a4l, after a4k, 1d

    section 批次4 P5-P2 高级功能 3天
    4.13 Kafka消费edu.notification.requested(ISSUE-053) :a4m, after a4j, 2d
    4.14 JWT RS256升级+JWKS fetcher :a4n, after a4m, 1d
    4.15 设计决策记录章节回写(ISSUE-056) :a4o, after a4n, 1d

    section 批次4 P5-P2 集成 2天
    4.16 与msg联调/internal/push双通道 :crit, a4p, after a4o, 1d
    4.17 集成测试+端到端验证 :crit, a4q, after a4p, 1d

    section 批次5 P6 硬化 5天
    5.1 Reconnect协议session_id+last_seq :a5a, after a4q, 2d
    5.2 Redis Stream替代Pub/Sub持久化 :a5b, after a5a, 2d
    5.3 测试覆盖率≥80% :crit, a5c, after a4q, 3d
    5.4 ADR+非功能性需求+失败模式章节(ISSUE-007) :a5d, after a4q, 2d

依赖锚点b3a = 批次 3 完成信号content + data-ana 就绪,见 workline.md §1 批次 3 总工期:批次 02 天)+ 批次 413 天)+ 批次 55 天)= 20 天


§3 详细任务

3.1 批次 0等待期2 天)

任务 0.1:复审 01/02 文档 + 核查已有仲裁

  • 负责人ai02
  • 依赖:无
  • 交付物
  • 验收标准
    • 5 项已有仲裁ISSUE-053/055/056/058 + ARB /internal/push核查完成
    • 01 文档 7 项偏差登记
    • 02 文档 4 项未回写 + 3 项规范缺失登记

任务 0.2:回写 ISSUE-053/055/056/058 到 02 文档

  • 负责人ai02
  • 依赖:任务 0.1
  • 交付物02-architecture-design.md 修订
    • §5.1 topic 改为 edu.notification.requestedISSUE-053
    • §6.7 增 Kafka 软失败逻辑 + Redis 软失败ISSUE-055/058待 ISSUE-006 仲裁最终策略)
    • 新增 §5.4"设计决策记录gRPC vs HTTP 协议选型coord 已采纳 P1"ISSUE-056
    • §3.1/§8.4 补 Hub 启动 Redis SET 重建 + 60s 不一致窗口文档化 + push_gateway_redis_set_rebuild_total 指标ISSUE-058
  • 验收标准4 项裁决全部回写,objections/push-gateway_issue.md §0 核查矩阵状态更新为

任务 0.3:提请 ISSUE-001~007 待 coord 仲裁

3.2 批次 4P5完整实现13 天)

任务 4.1Origin 校验 + CheckOrigin 白名单P01 天)

  • 负责人ai02
  • 依赖:批次 3 完成信号 b3a
  • 交付物internal/ws/handler.go 修订
    • upgrader.CheckOriginreturn true 改为读 WS_ALLOWED_ORIGINS 环境变量白名单
    • 无 Origin 头拒绝
  • 验收标准
    • 非白名单 Origin 返 403
    • 白名单来源teacher/student/parent portal 域名)通过

任务 4.2/internal/* 鉴权对齐 X-Internal-TokenP01 天)

  • 负责人ai02
  • 依赖:任务 4.1 + ISSUE-002 仲裁结果
  • 交付物
  • 验收标准:无 token/错 token 返 401 + PUSH_UNAUTHORIZEDDevMode 跳过

任务 4.3:心跳改用 WebSocket 控制帧 + SetReadDeadlineP01 天)

  • 负责人ai02
  • 依赖:任务 4.1
  • 交付物internal/ws/handler.go 重构
    • 移除文本消息 ping/pong 逻辑(handler.go#L82-L84
    • 改用 conn.SetReadDeadline(60s) + conn.SetPongHandler
    • 客户端 Ping 控制帧 → gorilla 自动回 Pong
    • 60s 无任何消息则关闭连接
  • 验收标准
    • 僵尸连接 60s 后自动清理
    • 心跳走 RFC 6455 控制帧,不再走文本消息

任务 4.4:单用户连接数限制 MaxConn=5P01 天)

  • 负责人ai02
  • 依赖:任务 4.3
  • 交付物internal/hub/hub.go 修订
    • Hub 增加 counters map[string]int02 文档 §2
    • Register 检查 counters[userID] >= 5ErrTooManyConnections
    • 超限返 close 帧code=1008 policy violation
  • 验收标准:第 6 个连接被拒,错误码 PUSH_TOO_MANY_CONNECTIONS 429

任务 4.5Send 通道满时指标 + 日志P01 天)

  • 负责人ai02
  • 依赖:任务 4.4
  • 交付物internal/hub/hub.go Send 方法修订
    • 通道满时 slog.Warn + messages_dropped_total Counter
  • 验收标准/metrics 暴露 push_gateway_messages_dropped_total{reason="channel_full"}

任务 4.6Dockerfile 重构P01 天)

  • 负责人ai02
  • 依赖:批次 3 完成信号 b3a
  • 交付物Dockerfile 重构
    • 多阶段(已有,保留)
    • 非 root 用户(adduser -D appuser + USER appuser
    • healthcheckwget --spider http://localhost:8081/healthz
    • ldflags 优化(-ldflags="-s -w -X main.Version=$(git rev-parse --short HEAD)"
    • go.mod 与 Dockerfile 版本对齐1.25.0 vs golang:1.22-alpine
  • 验收标准docker build 通过,容器以非 root 运行healthcheck 工作

任务 4.7:引入 log/slog 替换标准 logP01 天)

  • 负责人ai02
  • 依赖:任务 4.6
  • 交付物:新增 internal/observability/logger.go
    • slog.NewJSONHandler + slog.SetDefault
    • 日志字段:timestamp level service=push-gateway request_id trace_id user_id conn_id event
    • main.go / handler.go / hub.go 替换所有 log.Printfslog
  • 验收标准:日志输出 JSON 格式,包含 trace_id 字段

任务 4.8:接入 shared-goP01 天)

  • 负责人ai02
  • 依赖:任务 4.7 + 批次 0.14shared-go 骨架)
  • 交付物
    • go.mod 引入 github.com/edu-cloud/shared-go
    • 替换本地 observability/tracer.goshared-go/observability/tracer
    • 引入 shared-go/observability/logger(替换任务 4.7 本地实现)
    • 引入 shared-go/config/env(替换 config.go getEnv
    • 引入 shared-go/auth/jwks(任务 4.14 使用)
  • 验收标准:本地 tracer.go/logger.go 删除,统一从 shared-go import

任务 4.9Redis Pub/Sub 跨实例广播P12 天)

  • 负责人ai02
  • 依赖:任务 4.8
  • 交付物:新增 internal/redis/pubsub.go + Hub 改造
    • 订阅 edu.push.channel.user.* + edu.push.channel.broadcast
    • 本实例无目标用户时 PUBLISH 到对应 channel
    • 持有该用户的实例订阅后投递到本地连接
    • 引入 github.com/redis/go-redis/v9 依赖
  • 验收标准
    • 双实例部署msg 调实例 A /internal/push user=B,实例 B 持有 B → 收到推送
    • 广播 PUBLISH 一次,所有实例投递本地连接

任务 4.10Redis SET 在线状态 + 启动重建P11 天)

  • 负责人ai02
  • 依赖:任务 4.9
  • 交付物Hub 改造(对齐 ISSUE-058
    • 连接建立:SADD edu:push:online:<userID> <instanceID> + EXPIRE 60s
    • 心跳续期:EXPIRE 60s
    • 连接断开:SREM + 空 SET 则 DEL
    • Hub 启动重建:遍历内存连接 SADD + EXPIRE先清空 Redis 中本 instanceID 旧成员(避免幽灵)
    • 实例崩溃 SET 自然过期60s
  • 验收标准
    • 实例重启后 60s 内 Redis SET 重建完成
    • push_gateway_redis_set_rebuild_total 指标暴露

任务 4.11/metrics 自定义指标 + /readyz 软失败P11 天)

  • 负责人ai02
  • 依赖:任务 4.10
  • 交付物:新增 internal/observability/metrics.go + /readyz 重构
    • 指标清单02 文档 §6.4active_connections messages_pushed_total messages_dropped_total heartbeat_total disconnect_total redis_pubsub_latency_seconds kafka_consumed_total redis_set_rebuild_total
    • /readyz 检查 Redis PING + Kafka consumer lag
    • Redis/Kafka 软失败ISSUE-055/058待 ISSUE-006 仲裁):返 200 + degraded: true
  • 验收标准
    • /metrics 暴露 8+ 自定义指标
    • Redis 故障时 /readyz 返 200 + degraded: true(不返 503

任务 4.12:优雅关闭所有 WebSocket 连接P11 天)

  • 负责人ai02
  • 依赖:任务 4.11
  • 交付物main.go + Hub 改造
    • Hub 新增 CloseAll() 方法,向所有连接发 close 帧code=1001 going away
    • SIGTERM → 标记 Hub closing拒新连接→ CloseAll → 等 10s → srv.Shutdown → 关 Kafka consumer → 关 Redis subscriber → tracerShutdown
  • 验收标准SIGTERM 后所有连接收到 close 帧,无连接泄漏

任务 4.13Kafka 消费 edu.notification.requestedP22 天)

  • 负责人ai02
  • 依赖:任务 4.10 + msg 就绪信号
  • 交付物:新增 internal/kafka/consumer.go
    • Consumer Group push-gateway
    • 订阅 edu.notification.requestedISSUE-053 裁决的 topic 名)
    • 至少一次 + 重试 3 次入 DLQ
    • 幂等:event_id Redis SETNX TTL 24h
    • 消费 → 调 Hub.SendToUser / Broadcast
  • 验收标准
    • msg 发布 NotificationRequested → push-gateway 消费 → 推送到在线客户端
    • 重复 event_id 不重投

任务 4.14JWT RS256 升级 + JWKS fetcherP21 天)

  • 负责人ai02
  • 依赖:任务 4.8shared-go/jwks+ iam 就绪信号
  • 交付物internal/ws/handler.go authenticate 重构
    • 移除 HS256 共享密钥校验
    • 改用 RS256通过 shared-go/auth/jwks 拉取 iam /.well-known/jwks.json 公钥
    • 缓存公钥 + 定期刷新5 分钟)
  • 验收标准
    • iam 签发的 RS256 JWT 通过校验
    • 公钥轮换后 5 分钟内生效

任务 4.15设计决策记录章节回写P21 天)

  • 负责人ai02
  • 依赖:任务 4.13
  • 交付物02-architecture-design.md 新增 §5.4ISSUE-056
    • 标题:"设计决策记录gRPC vs HTTP 协议选型coord 已采纳 P1"
    • 正文标注"coord 已采纳,见 coord-final-decisions P1/P5/P6"
    • 记录决策背景、方案对比、采纳理由
  • 验收标准:章节存在且标注正确

任务 4.16:与 msg 联调 /internal/push 双通道P21 天)

  • 负责人ai02
  • 依赖:任务 4.13 + 任务 4.14 + msg 就绪
  • 交付物:联调测试报告
    • 定向推送msg → HTTP /internal/push → push-gateway → WebSocket
    • 广播msg → Kafka NotificationRequested → push-gateway → 全在线客户端
    • 离线场景:delivered: false, online: false → msg 走 SMS/邮件
  • 验收标准matrix.md §9.5 推送链路检查清单全通过

任务 4.17:集成测试 + 端到端验证P21 天)

  • 负责人ai02
  • 依赖:任务 4.16
  • 交付物:集成测试用例 + 端到端验证报告
    • 单实例 1000 连接压测
    • 双实例跨实例推送验证
    • Redis 故障降级验证
    • Kafka 消费积压验证
  • 验收标准

3.3 批次 5P6硬化5 天)

任务 5.1Reconnect 协议2 天)

  • 负责人ai02
  • 依赖:批次 4 完成
  • 交付物02 文档 §7.3 落地
    • 首次连接返 {type:"hello", session_id, seq:0}
    • 每条推送带递增 seq
    • 重连 /ws?token=&session_id=&last_seq= → 从 msg 拉取 last_seq+1 到当前补推
  • 验收标准:客户端断线 30s 内重连,未送达消息补推成功

任务 5.2Redis Stream 替代 Pub/Sub2 天)

  • 负责人ai02
  • 依赖:任务 5.1
  • 交付物internal/redis/stream.go
    • Pub/Sub → Redis Stream持久化
    • Consumer Group push-gateway
    • ACK 机制:投递成功后 XACK
    • 崩溃恢复:未 ACK 消息重新投递
  • 验收标准实例崩溃时未投递消息不丢失msg 落库兜底 + Stream 持久化双保险)

任务 5.3:测试覆盖率 ≥ 80%3 天,可与 5.1/5.2 并行)

  • 负责人ai02
  • 依赖:批次 4 完成
  • 交付物
    • hub_test.go:注册/注销/推送/广播/连接数限制
    • ws_test.go:鉴权/心跳/Origin 校验
    • config_test.go:环境变量加载
    • redis_test.goPub/Sub + SET 重建
    • kafka_test.go:消费 + 幂等
  • 验收标准go test -cover ./... ≥ 80%

任务 5.4ADR + 非功能性需求 + 失败模式章节补全2 天,可与 5.1/5.2 并行)

  • 负责人ai02
  • 依赖:批次 4 完成 + ISSUE-007 仲裁
  • 交付物02-architecture-design.md 新增章节
    • §14 ADRArchitecture Decision Records
      • ADR-001gorilla/websocket 选型vs nhooyr/websocket
      • ADR-002Redis Pub/Sub vs StreamP5 Pub/Sub → P6 Stream 演进)
      • ADR-003心跳间隔 30s/60s 选型依据
      • ADR-00410w 容量依据goroutine-per-connection 内存估算)
      • ADR-005HTTP + Kafka 双通道vs 单通道)
    • §15 非功能性需求(可用性 SLO 99.9% / 安全合规 / 容量 SLA
    • §16 失败模式(实例崩溃 / Redis 故障 / 网络分区 / Kafka 积压降级)
    • §17 容量估算10w 连接内存/CPU/带宽)
  • 验收标准4 章节齐全,符合 arc42 规范

§4 依赖与就绪信号

4.1 我依赖的上游就绪标志

依赖项 提供方 就绪信号 当前状态 影响任务
shared-go 包骨架 coord批次 0.14 packages/shared-go 含 tracer/logger/jwks/env 4 模块 任务 4.8
iam JWT RS256 + JWKS 端点 ai06批次 1 iam gRPC 50052 + /.well-known/jwks.json 可访问 任务 4.14
msg gRPC + Kafka topic ai10批次 4 msg gRPC 50056 + edu.notification.requested topic 有事件 任务 4.13/4.16
Redis 基础设施 coordP1 Redis 7.x 可访问 P1 已就绪 任务 4.9/4.10
Kafka 基础设施 coordP1 Kafka 可访问 P1 已就绪 任务 4.13
ISSUE-001~007 仲裁 coord coord.md 追加 ARB-003+ 任务 4.2/4.11/4.15/5.4

4.2 我的就绪信号(供下游消费)

就绪标志 验证方式 供消费方
push-gateway HTTP :8081 启用 GET /healthz 返 200 k8s 探针 / 监控
/readyz 返 200含 Redis/Kafka 软失败检查) GET /readyz 返 200 + degraded 字段 k8s 探针
WebSocket /ws 端点可升级JWT RS256 鉴权) 客户端 ws://host:8081/ws?token=JWT 建立连接 teacher-portal / student-portal / parent-portal
/internal/push + /internal/broadcast 接收 msg 推送 msg 调用返 {success:true, delivered:true} msg (ai10)
/internal/online/ 查在线状态 {online:bool, instances:[]} msg (ai10)
Kafka consumer edu.notification.requested 订阅成功 Consumer Group push-gateway lag=0 msg (ai10)
/metrics 暴露 8+ 自定义指标 GET /metricspush_gateway_* 指标 Prometheus

4.3 Mock 策略(开发期间)

我提供的 mockpush-gateway 未就绪前,供前端 portal

  • WebSocket mock前端用 mock-socket 库模拟 WS 连接,每 30s 推 1 条 mock 通知
  • HTTP mock/internal/* 返 200 success

我消费的 mock(上游未就绪前):

  • NotificationEvent mockmsg 未就绪前push-gateway 内置定时器每 30s 生成 mock 事件推所有在线客户端
  • JWT 验签 mockiam 未就绪前使用本地固定 RS256 公钥(或 DevMode dev-token
  • Kafka 订阅 mockmsg 未就绪前不启动 Kafka consumer用本地定时器替代

§5 风险与缓解

风险 概率 影响 缓解措施
ISSUE-001~007 仲裁延迟 阻塞批次 4 启动 ai02 先按建议方案推进,仲裁结果出来后调整
msg 就绪延迟 阻塞任务 4.13/4.16 用 mock NotificationEvent 先完成 Kafka 消费逻辑
10w 连接压测不达标 容量目标降级 P6 阶段压测,若不达标则 horizontal scaling 兜底
Redis Pub/Sub 消息丢失 跨实例推送丢失 msg 落库兜底 + P6 升级 Redis Stream
shared-go 接口变更 任务 4.8 返工 紧跟 coord 0.14 任务,接口冻结后立即对接