push-gateway 推送网关服务
版本:1.0(仲裁回写)
日期:2026-07-10
状态:P5 已交付
关联文档:
1. 模块职责
push-gateway 是实时推送基础设施服务(Go 实现),管理 WebSocket 长连接(仅 WebSocket,不支持 SSE — ISSUE-001)。
接收 msg 服务的推送请求(HTTP /internal/* + Kafka 事件),维护用户在线连接池,将消息实时投递到浏览器/移动端。
支持连接鉴权(JWT RS256)、RFC 6455 控制帧心跳、断线重连、多设备同步、连接数监控、跨实例 Pub/Sub 广播。
无 DB(ISSUE-005):消息审计、离线落库、重试队列均由 msg 服务负责。
2. 技术栈
| 类别 |
技术 |
版本 |
用途 |
| 语言 |
Go |
1.25+ |
高并发长连接 |
| Web 框架 |
Gin |
1.12+ |
HTTP 升级 WebSocket + 内部 API |
| WebSocket |
gorilla/websocket |
1.5+ |
WebSocket 协议 |
| 缓存 |
Redis |
7.x |
在线连接表、pub/sub 跨实例 fanout |
| Kafka |
segmentio/kafka-go |
0.4+ |
消费 edu.notification.requested(纯 Go,无 cgo) |
| JWT |
golang-jwt/jwt/v5 |
5.2+ |
RS256 token 校验(via shared-go/jwks) |
| 配置 |
shared-go/env |
— |
环境变量加载(替代 viper) |
| 日志 |
log/slog |
stdlib |
结构化日志(JSON prod / text dev) |
| 追踪 |
OpenTelemetry |
1.44+ |
分布式追踪(via shared-go/tracer) |
| 指标 |
prometheus/client_golang |
1.23+ |
Prometheus 指标 |
| 共享包 |
shared-go |
— |
tracer/logger/env/jwks 复用(ARB-015 §17.5) |
| 编排 |
Kubernetes |
1.28+ |
生产部署 |
3. 架构图
完整分层图、跨实例同步图、失败模式矩阵见 02 架构设计 §1 / §8 / §16。
4. 核心流程图
5. 对外契约
5.1 HTTP 端点
| 方法 |
路径 |
鉴权 |
用途 |
| GET |
/ws |
JWT RS256(query ?token= 或 Authorization: Bearer) |
WebSocket 升级 |
| POST |
/internal/push |
X-Internal-Token 头(ISSUE-002) |
定向推送 |
| POST |
/internal/broadcast |
X-Internal-Token 头 |
广播 |
| GET |
/internal/online/<userID> |
X-Internal-Token 头 |
查在线状态 |
| GET |
/healthz |
无 |
liveness 探针 |
| GET |
/readyz |
无 |
readiness 探针(软失败,ISSUE-058) |
| GET |
/metrics |
无 |
Prometheus 指标 |
5.2 Kafka 消费
| Topic |
Consumer Group |
Message |
说明 |
edu.notification.requested |
push-gateway |
NotificationRequested |
ISSUE-053 重命名(原 edu.notification.events) |
- 至少一次 + Redis SETNX 幂等去重(event_id,TTL 24h)
- 失败重试 3 次后入 DLQ(
edu.notification.requested.dlq)
5.3 Redis 数据结构
| Key 模式 |
类型 |
TTL |
用途 |
edu:push:online:<userID> |
SET(instanceID) |
60s |
在线用户所在实例集合 |
edu:push:channel:user:<userID> |
Pub/Sub |
— |
跨实例定向推送 |
edu:push:channel:broadcast |
Pub/Sub |
— |
跨实例广播 |
edu:push:idempotent:<event_id> |
SETNX |
24h |
Kafka 幂等去重 |
无 gRPC 服务(ISSUE-007):push-gateway 仅暴露 HTTP + WebSocket,不实现 gRPC 服务,packages/shared-proto/proto/ 无 push-gateway.proto。
6. 依赖关系
- 上游:msg 服务(HTTP
/internal/* + Kafka 事件)
- 下游:Redis(在线 SET + pub/sub 跨实例同步)、iam(JWKS 公钥)
- 客户端:teacher-portal、student-portal、parent-portal、admin-portal(WebSocket 连接)
- 共享包:shared-go(tracer/logger/env/jwks)
7. 架构约束
- 无业务逻辑,仅做连接管理与消息转发
- 无 DB(ISSUE-005):审计/落库/重试由 msg 负责
- 仅 WebSocket(ISSUE-001):不支持 SSE
- 无 gRPC(ISSUE-007):仅 HTTP + WebSocket
- 连接鉴权必须校验 JWT RS256(DevMode 接受
dev-token)
- RFC 6455 控制帧心跳(Ping/Pong 30s,60s 无帧断开)
- 单用户连接数上限 5(默认,可配
MAX_CONNS_PER_USER)
- 单实例容量 10w+(ISSUE-003,原 50k 已提升)
- Redis 故障软失败(ISSUE-058/006):
/readyz 返 200 + degraded:true,不断本地 WebSocket
- Redis online SET 启动重建(ISSUE-058):先 SREM 旧 instanceID,再基于内存重建
8. 关键 ADR(完整见 02 §14)
| ADR |
决策 |
来源 |
| ADR-001 |
仅 WebSocket,不支持 SSE |
ISSUE-001 |
| ADR-002 |
X-Internal-Token 命名 |
ISSUE-002 |
| ADR-003 |
单实例 10w+ 连接 |
ISSUE-003 |
| ADR-004 |
无 DB,审计由 msg 负责 |
ISSUE-005 |
| ADR-005 |
Kafka topic edu.notification.requested |
ISSUE-053 |
| ADR-006 |
/readyz 软失败 |
ISSUE-058/006 |
| ADR-007 |
Redis SET 启动重建 |
ISSUE-058 |
| ADR-008 |
Redis 必需但软失败 |
ISSUE-055 vs 058 |
| ADR-009 |
纯 HTTP + WebSocket,无 gRPC |
ISSUE-007 |
| ADR-010 |
shared-go 复用 |
ARB-015 §17.5 |
9. 环境变量
| 变量 |
默认值 |
说明 |
PUSH_GATEWAY_PORT |
8081 |
HTTP 监听端口 |
DEV_MODE |
false |
开发模式(跳过内部鉴权,接受 dev-token) |
JWT_SECRET |
(DevMode 有默认) |
JWT 密钥(生产必填) |
JWKS_URL |
http://localhost:50052/.well-known/jwks.json |
iam JWKS 端点 |
INTERNAL_API_TOKEN |
(生产必填) |
/internal/* 鉴权 token(ISSUE-002) |
REDIS_URL |
redis://localhost:6379/0 |
Redis 连接 URL |
KAFKA_BROKERS |
localhost:9092 |
Kafka broker 列表(逗号分隔) |
KAFKA_NOTIFICATION_TOPIC |
edu.notification.requested |
消费 topic(ISSUE-053) |
KAFKA_CONSUMER_GROUP |
push-gateway |
consumer group |
WS_ALLOWED_ORIGINS |
(空) |
WebSocket Origin 白名单(逗号分隔) |
MAX_CONNS_PER_USER |
5 |
单用户最大连接数 |
HEARTBEAT_INTERVAL_SECONDS |
30 |
心跳间隔 |
INSTANCE_ID |
(hostname) |
实例标识(Redis SET 成员) |
OTEL_EXPORTER_OTLP_ENDPOINT |
localhost:4318 |
OTLP 端点 |