Initial LisgloSIPS V2 implementation
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# CDR Redis Stream Runbook
|
||||
|
||||
> 任务:S22 - CDR Redis Stream
|
||||
> 完成时间:2026-06-21 19:34 +08:00
|
||||
|
||||
## 1. 目标
|
||||
|
||||
S22 建立原始 CDR 的 Redis Stream 契约和可靠消费基础,不做计费、不扣余额、不写正式 rated CDR。计费与余额扣减留给 S23。
|
||||
|
||||
## 2. Redis Key
|
||||
|
||||
| Key | 用途 |
|
||||
| --- | --- |
|
||||
| `stream:cdr_payload` | OpenSIPS 写入的原始 CDR Stream |
|
||||
| `stream:cdr_deadletter` | CDR Worker 校验或处理失败后的死信 Stream |
|
||||
| `billing-workers` | `stream:cdr_payload` 的 Consumer Group |
|
||||
| `lock:cdr:{event_id}` | Worker 处理幂等占位,避免同一 `event_id` 重复处理 |
|
||||
|
||||
## 3. 事件字段
|
||||
|
||||
S22 事件 schema version 为 `1`。必填字段:
|
||||
|
||||
```text
|
||||
schema_version
|
||||
event_id
|
||||
idempotency_key
|
||||
call_id
|
||||
node_id
|
||||
opensips_instance
|
||||
ingress_a_ip
|
||||
rtpengine_node
|
||||
source_ip
|
||||
sip_code
|
||||
hangup_reason
|
||||
created_at
|
||||
```
|
||||
|
||||
多 A 预留字段已经纳入契约:
|
||||
|
||||
- `node_id`:A 节点 ID,当前单 A 为 `a1`。
|
||||
- `opensips_instance`:OpenSIPS 实例名,当前为 `opensips-a1`。
|
||||
- `ingress_a_ip`:接收 SIP 的 A 节点地址,当前为 `100.90.90.90`。
|
||||
- `rtpengine_node`:媒体节点,当前每台 A 绑定本机 RTPEngine,因此为 `a1`。
|
||||
|
||||
S22 阶段 A 端尚未进入真实落地路由,因此失败 CDR 的供应商、录音、应答时间等字段使用 `none` 或 `0` 占位。S23/S24 后续在不破坏 schema 的前提下补充真实计费和录音字段。
|
||||
|
||||
## 4. Producer
|
||||
|
||||
Server A OpenSIPS 在 `route[S20_CDR_XADD]` 中执行:
|
||||
|
||||
```text
|
||||
XADD stream:cdr_payload * schema_version 1 ... created_at <unix>
|
||||
```
|
||||
|
||||
当前 A 端字段已从 S20 冒烟格式升级为 S22 正式契约。A 端配置备份:
|
||||
|
||||
```text
|
||||
/var/backups/lisglosips-s22/20260621T113157Z/opensips.cfg
|
||||
```
|
||||
|
||||
## 5. Consumer
|
||||
|
||||
`packages/redis/src/cdr-stream.ts` 提供:
|
||||
|
||||
- `ensureCdrConsumerGroup`
|
||||
- `publishCdrEvent`
|
||||
- `parseCdrStreamEvent`
|
||||
- `processCdrBatch`
|
||||
- `processPendingCdrBatch`
|
||||
|
||||
`apps/worker-cdr` 已从骨架改为 Redis Stream Worker:
|
||||
|
||||
1. 读取 `REDIS_URL`。
|
||||
2. 确认 `billing-workers` Consumer Group。
|
||||
3. 先用 `XAUTOCLAIM` 重领 Pending,再用 `XREADGROUP` 读取新消息。
|
||||
4. 用 `lock:cdr:{event_id}` 做幂等占位。
|
||||
5. 处理成功或重复消息后 `XACK`。
|
||||
6. 校验失败或处理异常时写入 `stream:cdr_deadletter` 并 `XACK` 原消息。
|
||||
|
||||
当前 handler 只记录并接受原始 CDR,不执行计费和余额扣减。
|
||||
|
||||
## 6. 验证结果
|
||||
|
||||
- 本地 `packages/redis/src/cdr-stream.spec.ts` 6 条测试通过,覆盖发布、解析、Consumer Group 幂等创建、`XREADGROUP`/`XACK`、重复 `event_id`、死信和 `XAUTOCLAIM` Pending 重领。
|
||||
- `corepack pnpm@10.33.0 lint` 通过。
|
||||
- `corepack pnpm@10.33.0 typecheck` 通过。
|
||||
- `corepack pnpm@10.33.0 build` 通过。
|
||||
- A `opensips -C -f /etc/opensips/opensips.cfg` 通过,`opensips.service` active。
|
||||
- Redis `XGROUP CREATE stream:cdr_payload billing-workers 0 MKSTREAM` 返回 `OK`。
|
||||
- Redis `XINFO GROUPS stream:cdr_payload` 显示 `billing-workers`,pending 为 `0`。
|
||||
- T 发起 INVITE 后,A 返回 `SIP/2.0 503 Config Missing`,Redis Stream 最新事件包含 `schema_version=1`、`idempotency_key`、`node_id=a1`、`opensips_instance=opensips-a1`、`ingress_a_ip=100.90.90.90`、`rtpengine_node=a1`。
|
||||
|
||||
## 7. 回滚
|
||||
|
||||
代码回滚:
|
||||
|
||||
- 恢复 `packages/redis/src/index.ts`、`packages/redis/src/cdr-stream.ts`、`packages/redis/src/cdr-stream.spec.ts`、`apps/worker-cdr/src/main.ts`。
|
||||
- 重新执行 `corepack pnpm@10.33.0 build`。
|
||||
|
||||
Server A 回滚:
|
||||
|
||||
```bash
|
||||
sudo cp -a /var/backups/lisglosips-s22/20260621T113157Z/opensips.cfg /etc/opensips/opensips.cfg
|
||||
sudo opensips -C -f /etc/opensips/opensips.cfg
|
||||
sudo systemctl restart opensips
|
||||
```
|
||||
|
||||
Redis Consumer Group 可保留,不影响 Producer;如必须移除,应先确认没有未处理 Pending,再使用受控 Redis 维护窗口删除 group。S22 不删除 Stream 数据。
|
||||
Reference in New Issue
Block a user