216 lines
6.0 KiB
Markdown
216 lines
6.0 KiB
Markdown
# HY Voice Room
|
||
|
||
HY Voice Room 是一个语音房后端 monorepo。当前重点不是做通用 IM 平台,而是把语音房的实时业务闭环跑起来:HTTP 命令入口、房间 Room Cell、WebSocket 消息投递、MySQL 状态恢复、Redis 房间路由。
|
||
|
||
## Architecture
|
||
|
||
核心边界:
|
||
|
||
- `gateway-service` 是客户端 HTTP JSON 入口,负责鉴权、协议转换和调用 `room-service`。它不持有房间状态,也不终止 WebSocket。
|
||
- `room-service` 是房间业务状态 owner,负责 Room Cell、麦位、房间 presence、本地礼物榜、送礼落房间、snapshot、command log、outbox、Redis lease 路由。
|
||
- `im-service` 是连接和消息 owner,负责 WebSocket、房间频道订阅、公屏、房间系统消息、单聊、ACK、最近房间历史。
|
||
- `wallet-service` 当前提供送礼扣费 stub,`room-service` 的 `SendGift` 同步调用它。
|
||
- `user-service` 和 `activity-service` 当前是独立进程骨架,后续分别承接用户主数据和活动消费。
|
||
|
||
外部协议和内部协议:
|
||
|
||
- 客户端命令面:`gateway-service` 暴露 HTTP JSON。
|
||
- 客户端实时面:`im-service` 暴露 WebSocket JSON。
|
||
- 服务内部调用:gRPC + protobuf,契约在 `api/proto`。
|
||
- 房间外事件:protobuf outbox,当前先落 MySQL。
|
||
|
||
## Ports
|
||
|
||
本地和 Docker 编排统一使用 `13xxx` 端口:
|
||
|
||
| Component | Port | Purpose |
|
||
| --- | ---: | --- |
|
||
| `gateway-service` | `13000` | HTTP JSON |
|
||
| `room-service` | `13001` | gRPC |
|
||
| `im-service` | `13002` | HTTP/WebSocket |
|
||
| `im-service` | `13003` | gRPC |
|
||
| `wallet-service` | `13004` | gRPC |
|
||
| `user-service` | `13005` | gRPC |
|
||
| `activity-service` | `13006` | gRPC |
|
||
| MySQL | `13306 -> 3306` | local Docker only |
|
||
| Redis | `13379 -> 6379` | local Docker only |
|
||
|
||
## Run Locally
|
||
|
||
Use Docker for local and test environments:
|
||
|
||
```bash
|
||
make up
|
||
```
|
||
|
||
`make up` starts the stack in detached mode and prints host-local, host-LAN, and Docker-network addresses for every service port.
|
||
|
||
Follow logs:
|
||
|
||
```bash
|
||
make logs
|
||
```
|
||
|
||
Print addresses again without restarting services:
|
||
|
||
```bash
|
||
make addr
|
||
```
|
||
|
||
Operate one service:
|
||
|
||
```bash
|
||
make up rs
|
||
make rebuild rs
|
||
make restart rs
|
||
make stop rs
|
||
make logs rs
|
||
make ps rs
|
||
```
|
||
|
||
Service aliases: `gs/gateway`, `rs/room`, `is/im`, `ws/wallet`, `us/user`, `as/activity`, `mysql`, `redis`.
|
||
|
||
Stop services:
|
||
|
||
```bash
|
||
make down
|
||
```
|
||
|
||
The compose stack starts MySQL and Redis. MySQL schema is initialized from:
|
||
|
||
```text
|
||
services/room-service/deploy/mysql/initdb/001_room_service.sql
|
||
```
|
||
|
||
For direct host execution, each service reads its own `services/<service>/configs/config.yaml`.
|
||
|
||
## Development Commands
|
||
|
||
```bash
|
||
make proto
|
||
make test
|
||
make build
|
||
make up
|
||
make addr
|
||
make logs
|
||
make ps
|
||
make up rs
|
||
make rebuild rs
|
||
make restart rs
|
||
make stop rs
|
||
make logs rs
|
||
make ps rs
|
||
make down
|
||
```
|
||
|
||
`make proto` requires `protoc`, `protoc-gen-go`, and `protoc-gen-go-grpc`.
|
||
|
||
## Storage Model
|
||
|
||
`room-service` uses MySQL as the durable source:
|
||
|
||
- `rooms`: room metadata.
|
||
- `room_snapshots`: latest room state snapshot.
|
||
- `room_command_log`: replayable command log for recovery.
|
||
- `room_outbox`: async event records.
|
||
|
||
`room-service` uses Redis for current room ownership:
|
||
|
||
```text
|
||
room:route:{room_id}
|
||
```
|
||
|
||
The Redis value stores `room_id`, `node_id`, `lease_token`, and `expires_at_ms`. Lease acquisition is atomic through Lua in `internal/router/redis_directory.go`.
|
||
|
||
## Room Recovery
|
||
|
||
A room is not permanently bound to one process. A node is only the current executor while its Redis lease is valid.
|
||
|
||
Recovery path:
|
||
|
||
1. A request reaches a node that can own the room.
|
||
2. The node acquires or renews `room:route:{room_id}`.
|
||
3. If the local Room Cell does not exist, it loads the latest MySQL snapshot.
|
||
4. It replays command log records after the snapshot version.
|
||
5. It installs a local Room Cell and continues processing.
|
||
|
||
This is single-active recovery, not hot standby. Short retry/reconnect windows are expected during node failure.
|
||
|
||
## Cloud Deployment
|
||
|
||
Local Docker uses:
|
||
|
||
```text
|
||
services/room-service/configs/config.docker.yaml
|
||
```
|
||
|
||
Tencent Cloud deployment should follow:
|
||
|
||
```text
|
||
services/room-service/configs/config.tencent.example.yaml
|
||
```
|
||
|
||
Set the real Tencent Cloud MySQL DSN, Redis address, Redis password, and internal service DNS names. Keep `mysql_auto_migrate: false` online; schema changes should run through a controlled migration process.
|
||
|
||
Deployment lifecycle requirements are documented in:
|
||
|
||
```text
|
||
docs/health-checks.md
|
||
docs/graceful-shutdown.md
|
||
```
|
||
|
||
## API Entry Points
|
||
|
||
Gateway HTTP routes:
|
||
|
||
- `POST /api/v1/rooms/create`
|
||
- `POST /api/v1/rooms/join`
|
||
- `POST /api/v1/rooms/leave`
|
||
- `POST /api/v1/rooms/mic/up`
|
||
- `POST /api/v1/rooms/mic/down`
|
||
- `POST /api/v1/rooms/mic/change`
|
||
- `POST /api/v1/rooms/user/mute`
|
||
- `POST /api/v1/rooms/user/kick`
|
||
- `POST /api/v1/rooms/gift/send`
|
||
|
||
Gateway business responses use one envelope:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
`request_id` is generated by gateway unless the client sends `X-Request-ID`. The same value is propagated to internal gRPC `RequestMeta.request_id`.
|
||
|
||
IM WebSocket path:
|
||
|
||
```text
|
||
ws://127.0.0.1:13002/ws
|
||
```
|
||
|
||
The first WS frame must be `auth`; later frames include `subscribe_room`, `unsubscribe_room`, `room_chat_send`, `peer_send`, `ack`, and `pull_history`.
|
||
|
||
## Project Layout
|
||
|
||
```text
|
||
api/proto/ protobuf contracts
|
||
pkg/ small shared packages
|
||
services/gateway-service/ HTTP entry service
|
||
services/room-service/ Room Cell and durable room state
|
||
services/im-service/ WebSocket and message delivery
|
||
services/wallet-service/ wallet stub
|
||
services/user-service/ user stub
|
||
services/activity-service/ activity stub
|
||
```
|
||
|
||
## Current Limits
|
||
|
||
- `wallet-service`, `user-service`, and `activity-service` are still stubs.
|
||
- `room-service` outbox is durable, but external activity consumption is not implemented yet.
|
||
- `im-service` keeps message/session state in memory; scaling it across nodes still needs shared online routing or a delivery bus.
|
||
- JWT secrets in local config are development values only.
|