hyapp-server/docs/多机部署与无感发布.md
2026-05-13 16:07:25 +08:00

130 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多机部署与无感发布
本文定义不使用 Kubernetes 时的生产部署边界。目标是让 gateway 和内部微服务可以多机滚动发布,并让 `room-service` 在普通内网负载均衡命中非 owner 节点时自动转发到当前 Room Cell owner。
## 网络拓扑
推荐拓扑:
```text
公网 CLB
-> gateway-service 多实例
-> room-service 内网入口
-> user-service 内网入口
-> wallet-service 内网入口
-> activity-service 内网入口
-> game-service 内网入口
room-service 实例之间通过 Redis owner route 和 node registry 直连转发。
```
规则:
- 公网只暴露 `gateway-service`
- 内部 gRPC 服务只监听私网,安全组只允许服务网段访问 `13xxx` 端口。
- `user-service``wallet-service``activity-service``game-service` 可以直接挂内网 CLB 或 DNS。
- `room-service` 可以挂内网 CLB/DNS 给 gateway 使用,但 owner 转发必须使用实例级 `advertise_addr`,不能使用 CLB 地址。
- MySQL、Redis、MQ 使用腾讯云托管资源,不在业务 CVM 上部署;服务只通过内网 endpoint 访问这些托管依赖。
当前生产入口:
| 层级 | 地址 | 说明 |
| --- | --- | --- |
| 公网 API | `https://api.global-interaction.com` | HTTPS 443 由公网 CLB 转发到两台 `gateway-service:13000` |
| room/user/activity/game 内网入口 | `10.2.1.16:13001/13005/13006/13008` | `lb-epvnr4o0` 的 Go 服务监听器 |
| wallet 内网入口 | `10.2.1.15:13004` | `lb-4f5xi6p0` 的 Go 服务监听器 |
当前生产镜像仓库前缀是 `10.2.1.3:18082/hyapp`。部署脚本和 systemd env 只允许使用 `hyapp` 项目路径。
## Room Owner 路由
`room-service` 使用 Redis 维护两个短租约:
```text
room:route:{app_code + "\0" + room_id} -> owner node_id / lease_token / expires_at_ms
room:node:{node_id} -> 实例 advertise_addr / expires_at_ms
```
写命令、IM guard 和完整房间快照查询的处理顺序:
1. 请求先到任意 `room-service` 节点。
2. 节点按 `app_code + "\0" + room_id` 查询 Redis owner route。
3. 如果当前节点就是 owner直接进入本地 Room Cell。
4. 如果 owner 是其他节点,按 `room:node:{owner_node_id}` 找到实例私网地址并转发 gRPC。
5. 如果 route 不存在或已过期,当前节点按原有 `EnsureOwner` 逻辑接管并从 MySQL snapshot + command log 恢复。
转发只做一跳。`advertise_addr` 写错成 CLB 地址时,请求会在一跳后停止递归,并暴露真实 owner 冲突或 unavailable。
## room-service 配置
每个实例必须有唯一配置:
```yaml
node_id: "room-node-prod-1"
grpc_addr: ":13001"
advertise_addr: "10.0.1.12:13001"
health_http_addr: ":13101"
node_registry_ttl: "30s"
node_registry_heartbeat_interval: "10s"
owner_forward_timeout: "2s"
lease_ttl: "10s"
```
配置边界:
- `grpc_addr` 是本进程监听地址,可以是 `:13001`
- `advertise_addr` 是其他 room 节点能直连的实例私网地址,必须是单实例地址。
- `node_id` 参与 room owner lease、节点注册和日志排障不能在多个实例间复用。
- `node_registry_ttl` 必须大于心跳周期;默认 `30s/10s`
- `lease_ttl` 是节点故障后的自动接管窗口;正常滚动发布会在 gRPC drain 后主动释放本节点已加载房间 lease减少等待 TTL 的窗口。
## 无感发布步骤
单个服务滚动发布顺序:
1. 在新机器或新端口启动新版本实例。
2.`health_http_addr``/readyz` 或 gRPC health 通过。
3. 把新实例加入对应 CLB 后端或内部 DNS。
4. 观察错误率、延迟和日志。
5. 从 CLB 摘除一个旧实例,等待 CLB 健康检查和连接排空周期。
6. 对旧实例发送 `SIGTERM`
7. 旧实例进入 draininggRPC 完成 in-flight 请求后释放已加载房间 lease并删除 `room:node:{node_id}`
8. 新请求命中新节点后,如果 Redis route 已释放,会立即接管并从 MySQL 恢复;如果旧实例异常退出,则等待 `lease_ttl` 到期后接管。
发布约束:
- 不要先杀进程再摘 CLB。
- 不要把 `room-service``advertise_addr` 写成内网 CLB。
- 不要多个实例复用同一个 `node_id`
- 数据库变更要先执行兼容当前发布窗口的迁移,再滚动服务。
## 配置拆分
不用统一配置中心也可以上线。推荐发布系统渲染最终 `config.yaml`
- 公共环境配置:腾讯云 MySQL/Redis/MQ 私网地址、日志等级、腾讯云 endpoint。
- 服务配置端口、worker 周期、批量大小、超时。
- 实例配置:`node_id``advertise_addr``user-service.id_generator.node_id`
- SecretMySQL/Redis 密码、JWT secret、腾讯 IM/RTC/COS secret、callback token。
只有当需要动态配置、灰度下发、审计回滚时,再引入 Consul/Nacos/Apollo。当前代码的启动配置仍以单服务 YAML 为边界。
托管依赖边界:
- 腾讯云 MySQL 是业务事实和恢复来源DSN 必须保留 `loc=UTC`
- 腾讯云 Redis 保存 owner route、node registry、登录风控和短 TTL 缓存Redis 不能作为业务恢复事实来源。
- 腾讯云 MQ 只作为 outbox/event bus 的托管消息通道;创建 topic、consumer group 和权限变更走云产品流程,不混进服务滚动发布。
## 真实数据验证
本地真实 MySQL/Redis 验证:
```bash
docker compose up -d mysql redis
ROOM_SERVICE_MYSQL_TEST_DSN='hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=UTC' \
ROOM_SERVICE_REDIS_TEST_ADDR='127.0.0.1:13379' \
go test -count=1 ./services/room-service/internal/transport/grpc -run TestOwnerForwardingUsesRedisRouteAndNodeRegistry
```
该测试会启动两个真实 gRPC `room-service` 实例,共用真实 MySQL schema 和 Redis route/node registry在 node A 创建房间,通过 node B 调 `JoinRoom``CheckSpeakPermission``GetRoomSnapshot`,验证请求会转发到当前 owner。