2026-05-15 10:00:43 +08:00
..
2026-05-15 10:00:43 +08:00
2026-05-14 09:57:01 +08:00
2026-05-13 16:07:25 +08:00
2026-05-14 09:57:01 +08:00
2026-05-13 16:07:25 +08:00
2026-05-14 09:57:01 +08:00

多机部署与无感发布

本文定义不使用 Kubernetes 时的生产部署边界。目标是让 gateway 和内部微服务可以多机滚动发布,并让 room-service 在普通内网负载均衡命中非 owner 节点时自动转发到当前 Room Cell owner。

网络拓扑

推荐拓扑:

公网 CLB
  -> gateway-service 多实例
      -> room-service 内网入口
      -> user-service 内网入口
      -> wallet-service 内网入口
      -> activity-service 内网入口
      -> game-service 内网入口
      -> notice-service 内网入口

room-service 实例之间通过 Redis owner route 和 node registry 直连转发。

规则:

  • 公网只暴露 gateway-service
  • 内部 gRPC 服务只监听私网,安全组只允许服务网段访问 13xxx 端口。
  • user-servicewallet-serviceactivity-servicegame-servicenotice-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
后台平台 https://hyapp-platform.global-interaction.com 香港 Lighthouse 上 nginx 终止 HTTPS反代本机 admin-server:13100
room/user/activity/game/notice 内网入口 10.2.1.16:13001/13005/13006/13008/13009 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 维护两个短租约:

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 配置

每个实例必须有唯一配置:

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-serviceadvertise_addr 写成内网 CLB。
  • 不要多个实例复用同一个 node_id
  • 数据库变更要先执行兼容当前发布窗口的迁移,再滚动服务。

配置拆分

不用统一配置中心也可以上线。推荐发布系统渲染最终 config.yaml

  • 公共环境配置:腾讯云 MySQL/Redis/MQ 私网地址、日志等级、腾讯云 endpoint。
  • 服务配置端口、worker 周期、批量大小、超时。
  • 实例配置:node_idadvertise_addruser-service.id_generator.node_id
  • SecretMySQL/Redis 密码、JWT secret、腾讯 IM/RTC/COS secret、callback token。

只有当需要动态配置、灰度下发、审计回滚时,再引入 Consul/Nacos/Apollo。当前代码的启动配置仍以单服务 YAML 为边界。

线上字段契约以 services/<service>/configs/config.tencent.example.yaml 为准。开发时新增配置字段的动作必须同时更新三处:

  1. services/<service>/configs/config.yaml:本地直跑默认配置。
  2. services/<service>/configs/config.docker.yaml:本地 Docker/Compose 配置。
  3. services/<service>/configs/config.tencent.example.yaml:线上渲染契约。

部署前运行:

python3 deploy/tencent-tat/validate_rendered_configs.py \
  --inventory deploy/tencent-tat/inventory.prod.example.json \
  --rendered-dir /tmp/hyapp-rendered-prod

这个检查只比较 YAML 字段路径,不比较也不输出配置值。只要本地新增字段没有进入线上契约,或者最终渲染 YAML 漏字段,部署前会直接失败。

托管依赖边界:

  • 腾讯云 MySQL 是业务事实和恢复来源DSN 必须保留 loc=UTC
  • 腾讯云 Redis 保存 owner route、node registry、登录风控和短 TTL 缓存Redis 不能作为业务恢复事实来源。
  • 腾讯云 MQ 只作为 outbox/event bus 的托管消息通道;创建 topic、consumer group 和权限变更走云产品流程,不混进服务滚动发布。

真实数据验证

本地真实 MySQL/Redis 验证:

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 调 JoinRoomCheckSpeakPermissionGetRoomSnapshot,验证请求会转发到当前 owner。