perf: expand gateway capacity and prevent receipt replay
This commit is contained in:
@@ -0,0 +1,493 @@
|
||||
# CMPP Gateway 容量扩展与回调降载方案
|
||||
|
||||
更新日期:2026-08-25
|
||||
状态:已实施并完成测试环境验证
|
||||
适用范围:发送 Worker/Submit Outbox 到供应商 CMPP Submit、SubmitResp、状态报告、上行和计费结算链路
|
||||
|
||||
## 1. 已确认的供应商能力边界
|
||||
|
||||
本方案按以下已确认条件设计:
|
||||
|
||||
- 同一个供应商账号支持建立多条 CMPP 长连接;
|
||||
- 单通道最多支持 8 条连接;
|
||||
- 单连接滑动窗口最大支持 64;
|
||||
- 连接数和窗口上限表示技术许可,不等于供应商账号承诺 TPS;
|
||||
- 实际配置仍必须服从账号总 TPS、通道限速、供应商网关稳定性和生产合同限制。
|
||||
|
||||
CMPP 规范建议窗口值为 16;本项目允许使用 32/64,是基于供应商明确支持的扩展能力。禁止未经阶梯验证直接启用 `8 连接 × 64 窗口`。
|
||||
|
||||
单通道理论最大在途 Submit 数量为:
|
||||
|
||||
```text
|
||||
连接数 × 单连接窗口 = 8 × 64 = 512
|
||||
```
|
||||
|
||||
512 是未收到 SubmitResp 时允许并行等待的最大请求数,不代表可以稳定达到 512 TPS。稳定 TPS 还取决于 SubmitResp 延迟和供应商账号限速,例如平均响应时间为 200ms 时,窗口理论容量不是首要限制;如果平均响应时间接近 1 秒,窗口容量才会直接限制吞吐。
|
||||
|
||||
## 2. 改造目标与边界
|
||||
|
||||
目标:
|
||||
|
||||
1. 逐步提高每个供应商通道可承载的并发 Submit 数量;
|
||||
2. 在单 Gateway 内完成多连接和窗口扩容,多个 Gateway 水平扩展降为 P2 后续可选项;
|
||||
3. 将协议日志从发送和回调热路径移出;
|
||||
4. 将逐事件 HTTP 回调改为可重放、逐事件幂等的批量回调;
|
||||
5. 在扩容过程中保持消息、Submit、回执和计费不重不漏;
|
||||
6. 使用非零单价验证真实计费并发,不以零计费压测结果作为容量依据。
|
||||
|
||||
本阶段不改变:
|
||||
|
||||
- 对外 CMPP 协议和企业账号;
|
||||
- 签名、模板、余额、频控和企业/应用状态拦截语义;
|
||||
- 通道组主备补发规则;
|
||||
- `messageId`、`submitId`、结果事件和计费幂等口径;
|
||||
- PostgreSQL Submit Outbox 作为 Gateway 命令事实来源的设计。
|
||||
|
||||
## 3. 改造一:单通道多连接与窗口扩容
|
||||
|
||||
### 3.1 连接池模型
|
||||
|
||||
每个启用通道维护独立连接池,最多创建 8 条连接。每条连接独立维护:
|
||||
|
||||
- `connectionId` 和 Gateway 实例标识;
|
||||
- CMPP CONNECT/CONNECT_RESP 认证状态;
|
||||
- 独立递增并循环使用的 SequenceId;
|
||||
- 当前未确认 Submit 数量;
|
||||
- `sequenceId -> submitId` 在途映射;
|
||||
- 当前窗口大小和剩余窗口;
|
||||
- SubmitResp 延迟、超时和连续失败次数;
|
||||
- ACTIVE_TEST 心跳、断链和重连状态;
|
||||
- 平滑启用、排空和关闭状态。
|
||||
|
||||
连接选择采用“可用窗口优先 + 最少在途 + 健康度”策略:
|
||||
|
||||
1. 排除未认证、正在重连、正在排空的连接;
|
||||
2. 排除窗口已满的连接;
|
||||
3. 优先选择未确认请求最少的连接;
|
||||
4. 数量相同时优先选择近期 SubmitResp P95 更低的连接;
|
||||
5. 连续超时连接进入冷却期,不再分配新 Submit。
|
||||
|
||||
### 3.2 动态配置
|
||||
|
||||
建议新增或明确以下配置:
|
||||
|
||||
```text
|
||||
desiredConnections: 1..8
|
||||
windowSize: 1..64
|
||||
connectionWarmupSeconds: 30
|
||||
connectionDrainTimeoutSeconds: 60
|
||||
submitResponseTimeoutSeconds: 60
|
||||
connectionFailureCooldownSeconds: 30
|
||||
```
|
||||
|
||||
配置校验必须同时存在于管理 API 和 Gateway:
|
||||
|
||||
- `desiredConnections < 1` 或 `> 8`:拒绝保存/拒绝启动;
|
||||
- `windowSize < 1` 或 `> 64`:拒绝保存/拒绝启动;
|
||||
- 扩容时逐条建立连接,不能同时突发 8 次登录;
|
||||
- 缩容时先停止向目标连接分配新 Submit,等待在途请求完成后断开;
|
||||
- 修改窗口时只影响新请求,不能丢弃已有在途映射。
|
||||
|
||||
### 3.3 阶梯扩容参数
|
||||
|
||||
| 阶段 | 每通道连接 | 单连接窗口 | 理论在途/通道 | 用途 |
|
||||
|---|---:|---:|---:|---|
|
||||
| M0 | 1 | 16 | 16 | 当前兼容基线 |
|
||||
| M1 | 2 | 16 | 32 | 优先验证多连接正确性 |
|
||||
| M2 | 2 | 32 | 64 | 验证窗口扩容 |
|
||||
| M3 | 4 | 32 | 128 | 中等容量目标 |
|
||||
| M4 | 4 | 64 | 256 | 高容量目标 |
|
||||
| M5 | 8 | 64 | 512 | 上限验证,默认不直接用于生产 |
|
||||
|
||||
每一级必须单独通过正价 smoke、阶梯压测、故障注入和队列排空后才能进入下一级。
|
||||
|
||||
### 3.4 异常与幂等处理
|
||||
|
||||
- 收到 SubmitResp 后必须先用连接和 SequenceId 找到稳定 `submitId`,再释放窗口;
|
||||
- 未找到在途映射的迟到 SubmitResp 进入异常对账,不能静默丢弃;
|
||||
- TCP 写入前失败可安全重新分配连接;
|
||||
- TCP 写入成功但未收到 SubmitResp时标记 `unknown`,禁止直接补发;
|
||||
- 连接断开后只处理该连接的在途请求,不能把整个通道的消息统一重发;
|
||||
- `submitId` 必须跨连接、跨实例保持唯一;
|
||||
- 主备通道补发必须使用新的 `submitId`,并通过 `retryOfSubmitRecordId` 关联原提交。
|
||||
|
||||
## 4. P2 后续项:Gateway 水平扩展(当前不实施)
|
||||
|
||||
多个 Gateway 会引入通道连接所有权、分布式连接配额、租约、fencing token、脑裂、跨实例 pending 接管和“已写入供应商但未收到响应”的 unknown 判定,改造风险明显高于单 Gateway 内扩容。
|
||||
|
||||
因此本项调整为**优先级 P2 的后续可选改造**:
|
||||
|
||||
- 不纳入当前开发和压测范围;
|
||||
- 不作为本轮达到目标 TPS 的前置条件;
|
||||
- 当前先使用单 Gateway 管理同账号多连接,单通道仍遵守最多 8 条连接、单连接窗口最多 64;
|
||||
- 只有单 Gateway 的 CPU、网络、文件描述符或事件循环成为经验证的主要瓶颈,且单实例优化无法继续提升时,才重新评审本项;
|
||||
- 未完成独立设计评审、故障模型验证和隔离环境演练前,不得直接部署双 Gateway。
|
||||
|
||||
以下内容仅作为 P2 预研设计保留,不代表当前实施计划。
|
||||
|
||||
### 4.1 部署模型
|
||||
|
||||
部署至少两个 Gateway 实例,每个实例具有唯一的:
|
||||
|
||||
- `gatewayInstanceId`;
|
||||
- Redis Stream Consumer Name;
|
||||
- 指标端口和健康检查地址;
|
||||
- 通道连接所有权和连接标识。
|
||||
|
||||
所有实例加入同一个 Submit Stream Consumer Group,共享:
|
||||
|
||||
- Submit 命令队列;
|
||||
- 通道全局 TPS 限流;
|
||||
- Submit 发布和处理幂等键;
|
||||
- 结果/回执 Outbox;
|
||||
- 通道启停配置版本;
|
||||
- 待恢复和死信状态。
|
||||
|
||||
### 4.2 推荐的首期分片方式
|
||||
|
||||
如果以后启动 P2,首期采用“通道分片 + 故障接管”:
|
||||
|
||||
- 正常状态下,一个通道只由一个 Gateway 实例持有连接;
|
||||
- 不同运营商、主通道和备通道分散到不同实例;
|
||||
- 实例失联后,备用实例经过租约超时再接管通道;
|
||||
- 同一个通道不能被两个实例无租约地同时建立全部连接。
|
||||
|
||||
后续如果需要同账号跨实例并发连接,必须增加分布式连接配额:同一通道所有 Gateway 实例的连接总数不得超过 8。
|
||||
|
||||
### 4.3 接管与脑裂保护
|
||||
|
||||
- Redis 或 PostgreSQL 中保存带过期时间的通道所有权租约;
|
||||
- 租约包含 `channelId`、`gatewayInstanceId`、配置版本和 fencing token;
|
||||
- 只有持有最新 fencing token 的实例可以领取该通道的新 Submit;
|
||||
- 旧实例恢复后必须先重新竞选租约,不能直接恢复发送;
|
||||
- Stream 未确认消息通过 `XAUTOCLAIM` 接管;
|
||||
- 对“可能已写入供应商 TCP”的请求不得盲目重发,应进入 unknown 对账。
|
||||
|
||||
### 4.4 负载均衡边界
|
||||
|
||||
Gateway 水平扩展解决进程 CPU、网络、连接和单机故障问题,但不能突破:
|
||||
|
||||
- 单通道最多 8 条连接;
|
||||
- 单连接窗口最多 64;
|
||||
- 供应商账号总 TPS;
|
||||
- PostgreSQL、Redis和回调服务的实际容量。
|
||||
|
||||
## 5. 改造三:协议日志降载
|
||||
|
||||
### 5.1 日志分级
|
||||
|
||||
| 日志等级 | 示例 | 处理方式 |
|
||||
|---|---|---|
|
||||
| 必留异常 | 拒绝、超时、断链、认证失败、重复/未知响应 | 100%保留,异步优先写入 |
|
||||
| 业务摘要 | Submit、SubmitResp、回执、上行、下行应答 | 异步批量写入 |
|
||||
| 原始协议包 | 完整请求/响应字段或二进制包 | 正常流量采样,异常全量短期保存 |
|
||||
|
||||
### 5.2 实现方案
|
||||
|
||||
- Gateway 不在 Submit/SubmitResp 热路径同步写协议日志数据库;
|
||||
- 日志事件写入独立 Redis Stream;
|
||||
- 独立日志 Worker 每批领取 100~500 条;
|
||||
- 使用批量插入和批量状态回写;
|
||||
- 正常成功事件按通道配置 1%~10% 采样;
|
||||
- 错误、超时、拒绝和断链事件保持 100%;
|
||||
- 原始包写滚动文件或对象存储,主库只保存检索摘要和对象地址;
|
||||
- 正常原始包建议保留 3~7 天,异常日志按审计策略保留;
|
||||
- Redis 日志 Stream 设置最大长度和积压告警。
|
||||
|
||||
日志积压不得阻塞 Submit。积压超过门槛时允许降低正常成功日志采样率,但禁止丢弃错误、安全和账务相关事件。
|
||||
|
||||
### 5.3 必须保留的追踪字段
|
||||
|
||||
- `messageId`、`submitId`、`eventId`;
|
||||
- `channelId`、`gatewayInstanceId`、`connectionId`;
|
||||
- SequenceId 和供应商 Msg_Id;
|
||||
- 事件类型、方向、结果码和耗时;
|
||||
- 重试关联和原始日志对象地址。
|
||||
|
||||
## 6. 改造四:Gateway 批量回调协议
|
||||
|
||||
### 6.1 接口设计
|
||||
|
||||
新增内部接口:
|
||||
|
||||
```text
|
||||
POST /api/gateway/events/batch
|
||||
```
|
||||
|
||||
批次请求包含:
|
||||
|
||||
- `batchId`;
|
||||
- `gatewayInstanceId`;
|
||||
- `createdAt`;
|
||||
- 1~100 个事件;
|
||||
- 每个事件独立的 `eventId`、`type` 和 `payload`。
|
||||
|
||||
首期支持事件类型:
|
||||
|
||||
- `submit_segment_result`;
|
||||
- `submit_result`;
|
||||
- `receipt_intake`;
|
||||
- `uplink`;
|
||||
- `dead_letter`。
|
||||
|
||||
计费不由 Gateway直接生成。计费由 Submit结果/回执状态机依据稳定业务键执行,批量回调只负责可靠传递触发事实。
|
||||
|
||||
### 6.2 成批策略
|
||||
|
||||
采用“数量或时间先到先发”:
|
||||
|
||||
- 默认最大 50 个事件;
|
||||
- 可配置上限 100;
|
||||
- 默认最大等待 10ms;
|
||||
- 最大请求体 1MB;
|
||||
- Submit结果优先级高于普通协议日志;
|
||||
- 回执和上行可以使用独立批次,避免不同慢操作互相阻塞。
|
||||
|
||||
### 6.3 响应与重试
|
||||
|
||||
API 必须返回逐事件结果,而不是只有整批成功或失败:
|
||||
|
||||
```json
|
||||
{
|
||||
"batchId": "CB-001",
|
||||
"results": [
|
||||
{ "eventId": "EV-001", "accepted": true },
|
||||
{ "eventId": "EV-002", "accepted": false, "retryable": true, "errorCode": "DB_BUSY" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- Gateway 只 ACK `accepted=true` 的事件;
|
||||
- 可重试失败使用指数退避;
|
||||
- 不可重试失败进入死信并告警;
|
||||
- HTTP 超时后允许重发整个批次,API 依靠 `eventId` 幂等;
|
||||
- API 可以按事件类型及每 20~50 条拆成短事务;
|
||||
- 单个坏事件不能造成整批回滚。
|
||||
|
||||
### 6.4 数据库批量处理
|
||||
|
||||
- `eventId` 建唯一索引或使用既有稳定幂等键;
|
||||
- Submit结果批量更新 `SmsSubmitRecord`;
|
||||
- 回执先批量写入耐久 Receipt Inbox,再异步完成聚合;
|
||||
- 计费使用稳定计费键批量创建,重复事件不得重复扣费;
|
||||
- 消息和任务进度使用集合更新,避免逐事件 `GROUP BY`;
|
||||
- 事务中禁止调用外部 HTTP、Redis或供应商接口。
|
||||
|
||||
### 6.5 兼容与回退
|
||||
|
||||
- 保留原单事件回调接口;
|
||||
- 使用开关选择单事件或批量接口,同一事件不能双发;
|
||||
- 新旧入口共享 `eventId` 幂等逻辑;
|
||||
- 回退只切换发送方式,不删除回调 Outbox 或幂等记录;
|
||||
- 回退前必须等待在途批次完成并核对 Redis PEL。
|
||||
|
||||
## 7. 当前实施顺序与 P2 后续项
|
||||
|
||||
### 当前步骤 1:可观测性和协议日志降载
|
||||
|
||||
先补齐连接、窗口、供应商响应延迟和日志积压指标,再将成功协议日志异步批量化。该阶段不提高连接数。
|
||||
|
||||
### 当前步骤 2:单实例多连接
|
||||
|
||||
实现连接池、窗口隔离、平滑扩缩容和断链恢复,依次验证 `2×16`、`2×32`、`4×32`。
|
||||
|
||||
### 当前步骤 3:批量回调协议
|
||||
|
||||
上线批量接口和逐事件幂等,先影子对账,再切换 Submit结果,最后切换回执和上行。
|
||||
|
||||
### 当前步骤 4:单 Gateway 容量上限验证
|
||||
|
||||
只有前述阶段稳定后,才在单 Gateway 内测试 `4×64`、`8×64`。生产建议值依据压测结果确定,不以供应商允许的最大参数直接作为生产配置。
|
||||
|
||||
### 优先级 P2:多 Gateway 水平扩展
|
||||
|
||||
当前不实施、不部署、不纳入本轮压测。将来只有证据表明单 Gateway 自身成为主要瓶颈时,才单独立项,执行通道分片、租约防脑裂、故障接管和 unknown 对账验证。
|
||||
|
||||
## 8. 正价压测方案
|
||||
|
||||
### 8.1 前置条件
|
||||
|
||||
- 仅在隔离测试环境执行;
|
||||
- 压测前完成数据库、环境文件、部署目录和 systemd 配置备份;
|
||||
- 测试应用单价设置为非零值,并记录压测前余额;
|
||||
- 使用可明确识别的移动、联通、电信混合号码;
|
||||
- 主备六通道均处于预期状态;
|
||||
- Submit、结果、回执、日志 Stream 和数据库 Outbox 初始无积压;
|
||||
- 压测前记录 Gateway 实例、连接数、窗口和供应商限速配置。
|
||||
|
||||
### 8.2 阶梯
|
||||
|
||||
每个连接/窗口档位分别执行:
|
||||
|
||||
1. 9 条正价全链路 smoke;
|
||||
2. 20 TPS;
|
||||
3. 30 TPS;
|
||||
4. 50 TPS;
|
||||
5. 70 TPS;
|
||||
6. 100 TPS;
|
||||
7. 达标且无停止条件时再测试 150/200 TPS。
|
||||
|
||||
每档结束后等待完整排空并对账,不能只检查入口 SubmitResp。
|
||||
|
||||
### 8.3 验收指标
|
||||
|
||||
- 客户 SubmitResp 接收数和唯一 MessageId 数;
|
||||
- Inbox 完成数;
|
||||
- 首次供应商 Submit 数和每秒吞吐;
|
||||
- 各通道、各连接的提交数和在途峰值;
|
||||
- SubmitResp P50/P95/P99;
|
||||
- 供应商接受、拒绝、超时和 unknown 数;
|
||||
- 主备补发关联完整性;
|
||||
- 最终 delivered/failed/submitted/unknown 数;
|
||||
- 回调批次大小、请求数、重试数和积压;
|
||||
- 数据库连接池等待、锁等待和慢 SQL;
|
||||
- 协议日志 Stream 积压和丢弃/采样计数;
|
||||
- 计费记录数、计费单位、扣费金额与余额变化。
|
||||
|
||||
正价计费恒等式至少满足:
|
||||
|
||||
```text
|
||||
压测前余额 - 压测后余额
|
||||
= 有效计费记录金额合计
|
||||
= 各消息 billingUnits × 应用单价之和
|
||||
```
|
||||
|
||||
### 8.4 停止条件
|
||||
|
||||
出现任一情况立即停止升档:
|
||||
|
||||
- SubmitResp 缺失或 MessageId 重复;
|
||||
- `submitId` 重复提交;
|
||||
- 重复计费、漏计费或余额不平;
|
||||
- 供应商超时/unknown 持续增加;
|
||||
- Redis Stream、Inbox、Outbox或回调持续积压且无法在规定时间排空;
|
||||
- 数据库连接等待、锁等待或 idle transaction 异常;
|
||||
- Gateway 频繁断链或连接数不能恢复;
|
||||
- 通道禁用、主备补发或发送拦截语义异常;
|
||||
- 日志降载导致错误、安全或账务事件丢失。
|
||||
|
||||
## 9. 故障回归
|
||||
|
||||
必须覆盖:
|
||||
|
||||
- 单连接断开,其余连接继续发送;
|
||||
- 同一通道从 4 条连接缩到 2 条连接,在途请求不丢失;
|
||||
- 单 Gateway 进程重启后恢复连接、在途状态和 Stream pending;
|
||||
- Redis 短暂不可用;
|
||||
- 批量回调 HTTP 超时后整批重放;
|
||||
- 回调批次中单个非法事件不拖累其他事件;
|
||||
- PostgreSQL 回调连接池耗尽时不阻塞 Submit Outbox;
|
||||
- 主通道 Submit拒绝触发备通道关联补发;
|
||||
- 主通道禁用后只使用备通道;
|
||||
- 主备同时禁用时发送在供应商前失败;
|
||||
- 签名/模板未报备、余额不足、企业/应用删除或禁用、号码频控仍能正确拦截;
|
||||
- 重启和事件重放不造成重复提交、重复回执或重复计费。
|
||||
|
||||
多 Gateway 的实例宕机接管、租约、fencing token 和脑裂测试属于 P2 后续项目,不纳入当前故障回归范围。
|
||||
|
||||
## 10. 容量预期
|
||||
|
||||
容量提升不能简单相加。最终吞吐由最慢环节决定:
|
||||
|
||||
```text
|
||||
实际 TPS = min(
|
||||
入口能力,
|
||||
Worker/Outbox能力,
|
||||
Gateway连接与窗口能力,
|
||||
供应商账号总TPS,
|
||||
回调与计费能力,
|
||||
PostgreSQL/Redis能力
|
||||
)
|
||||
```
|
||||
|
||||
以最近一次测试记录的完整供应商首提约 33 TPS 为历史参考,执行前必须重新验证当前基线。工程预期区间如下:
|
||||
|
||||
| 完成范围 | 预期稳定首提吞吐 |
|
||||
|---|---:|
|
||||
| 独立回调 + 批量路由/持久化 | 50~70 TPS |
|
||||
| 加协议日志降载 + `2×16`/`2×32` | 60~100 TPS |
|
||||
| 加 `4×32` 或 `4×64` | 80~150 TPS |
|
||||
| 单 Gateway `8×64` 上限验证 | 由供应商总TPS及单机实测决定,不预先承诺 |
|
||||
| 多 Gateway 水平扩展(P2) | 当前不实施,另行立项后评估 |
|
||||
| 300~500 TPS | 不作为当前承诺;未来可能需要供应商总TPS、通道数量和基础设施共同扩展 |
|
||||
|
||||
上述数字是容量规划目标,不是承诺值。即使单通道理论支持 512 个在途请求,如果供应商账号总限速为 50 TPS,平台稳定吞吐仍不会超过约 50 TPS。
|
||||
|
||||
## 11. 生产推荐原则
|
||||
|
||||
- 生产参数使用“最低但足够”的连接数和窗口,不长期顶格运行;
|
||||
- 初始推荐从 `2 连接 × 16 窗口` 开始;
|
||||
- 只有监控证明窗口经常耗尽且供应商响应稳定,才提高到 32/64;
|
||||
- 当前不增加 Gateway 实例;只有单实例 CPU、网络、文件描述符或事件循环成为经验证且无法继续优化的瓶颈,才启动 P2 水平扩展评审;
|
||||
- 保留连接数、窗口、批量回调、日志采样和 Gateway分片的独立回退开关;
|
||||
- 每次只改变一个主要容量变量,保留可比较的正价压测基线。
|
||||
|
||||
## 12. 2026-08-25 实施与测试结论
|
||||
|
||||
本轮已实施本方案中除“多 Gateway 水平扩展(P2)”外的改造,并仅发布到测试环境 `100.93.204.60`。发布前恢复点为 `/opt/cmpp-platform-backups/phase5-capacity-20260825T041622Z`,数据库、运行源码和 systemd/环境配置归档均已校验;预生产环境未操作。
|
||||
|
||||
已交付能力:
|
||||
|
||||
- Gateway 协议摘要日志改为 Redis Stream 异步写入,成功事件确定性采样 10%,拒绝、超时、断链和非零结果 100% 保留;独立 `cmpp-protocol-log-worker` 使用独立 4 槽 PostgreSQL 连接池批量落库,成功后 ACK/XDEL,失败重放,格式错误进入死信 Stream。
|
||||
- 单通道支持 1~8 条供应商连接、每连接 1~64 窗口;连接选择按在途数量和近期 RTT,连续失败进入冷却。扩容串行预热;缩容先停止分配、等待在途结束或排水超时后关闭。
|
||||
- Gateway 结果 Outbox 支持最多 50 条、最长等待 10ms 的批量 HTTP 回调;Submit 结果、分片结果、回执、上行和死信均返回逐事件结果,整批 HTTP 失败留在 PEL 重放,单个非法事件不拖累同批其他事件。
|
||||
- API、Gateway 控制入口和实际运行状态完成三层校验。运行矩阵覆盖 `1x1`、`2x16`、`4x32`、`8x64`、`4x64`、`2x32`,越界 `0/9` 连接及 `0/65` 窗口均被拒绝。
|
||||
- 上行增加 `eventId` 唯一幂等键;同一批量上行事件重放两次只生成一条 `SmsUplinkMessage`。
|
||||
|
||||
发布后曾发现协议日志发布器在仅配置 `REDIS_HOST/REDIS_PORT` 的环境中没有对空 `REDIS_URL` 回退。该问题触发停止条件,未进入阶梯;修正为与其他 Gateway Redis 组件一致的本机回退、增加测试并重发后,冒烟显示发布 16、采样丢弃 47、错误 0,日志 Stream 最终 `pending=0/lag=0`。
|
||||
|
||||
### 12.1 正价阶梯结果
|
||||
|
||||
测试单价为 325 分/计费条,三运营商混合号码,六个模拟供应商账号;压测连接配置为每通道 `2x32`。容量按非补发的首次供应商 Submit 统计。
|
||||
|
||||
| 目标档 | 客户 SubmitResp | 首次供应商 Submit | 首提跨度/实际速率 | 客户 P50/P95/P99 | 计费 | 结论 |
|
||||
|---:|---:|---:|---:|---:|---:|---|
|
||||
| smoke | 9/9 | 9 | 约 8.1 秒/低速冒烟 | 17/1727/1727 ms | 9 笔/2925 | 冷启动尾延迟后链路闭合 |
|
||||
| 20 TPS | 199/199 | 199 | 约 9.8 秒/约 20 TPS | 14/21/29 ms | 199 笔/64675 | 通过 |
|
||||
| 30 TPS | 299/299 | 299 | 9.840 秒/30.39 TPS | 16/33/61 ms | 299 笔/97175 | 通过 |
|
||||
| 50 TPS | 499/499 | 499 | 10.086 秒/49.47 TPS | 22/62/89 ms | 499 笔/162175 | 通过 |
|
||||
| 70 TPS | 699/699 | 690 | 10.351 秒/66.66 TPS | 94/4099/5323 ms | 690 笔/224250 | 触发停止线,不再升档 |
|
||||
| 100/150/200 TPS | 未执行 | - | - | - | - | 按停止条件立即停止 |
|
||||
|
||||
70 TPS 档少 9 条首次 Submit 和计费并非丢失:9 条均为重复使用的测试号码命中“单号码 24 小时最多 10 条”,最终 `failed/RISK`,供应商提交和计费均为 0。该结果证明频控拦截有效,但 70 TPS 的 P95/P99 已从 50 档的 `62/89ms` 跃升到 `4099/5323ms`,目标 70 TPS 也只完成 66.66 TPS,因此本版本稳定上限定为 **50 TPS**;70 TPS 只能视作非稳定冲击档。
|
||||
|
||||
### 12.2 完整回归与恢复
|
||||
|
||||
- Submit/回执:各通过档客户 SubmitResp 无缺失,MessageId、submitId 和供应商 MessageId 无重复;模拟器 DELIVRD/失败/无回执比例按配置落库,三条 Stream 最终均 `pending=0/lag=0`。
|
||||
- 计费:20/30/50 档及 70 档未被风控拒绝的消息,计费笔数均等于有效消息数,单价全部 325,金额满足 `条数 x 325`;风控、余额不足和路由失败不计费。
|
||||
- 主备:随机结果码 8 产生带 `retryOfSubmitRecordId` 的备通道补发;仅主通道停用时 9 条全部走 `LGST-M-B`,主备六通道全停时 9/9 `failed/ROUTE`、供应商提交 0。
|
||||
- 上行/批量回调:有效上行与非法事件同批时互不影响,非法项为不可重试;同一 eventId 重放两次仅落一条上行记录。
|
||||
- 拦截:签名未审核、模板强制匹配但未审核、余额不足分别为 9/9 `SIGNATURE/TEMPLATE/BALANCE` 且供应商提交 0;应用或企业停用时客户连接不能鉴权;频控在 70 档真实命中;所有临时状态均恢复。
|
||||
- 平滑扩缩容:`LGST-M-P` 从 1 扩到 4,再在 20 TPS、20 秒发送期间缩到 1;399/399 SubmitResp,399 条首次 Submit,无重复 ID,P95/P99=`49/142ms`,最终连接恢复 6/6。
|
||||
- 故障:Gateway 重启后由 API 恢复连接;Redis 在队列排空时短暂停止,Gateway 健康进程保持,Redis 恢复后重启 Gateway/API 并恢复 6/6;整批 HTTP 超时重放和单事件隔离另有自动化测试覆盖。
|
||||
- 环境恢复:10 个测试应用单价恢复 0,企业/应用/签名/模板/通道均恢复原状态,三条临时号段规则删除,六通道连接恢复 6/6。真实消息、回执和账务事实保留审计,不回写余额。
|
||||
|
||||
## 13. 2026-08-25 回执回放风暴修复与正价复压
|
||||
|
||||
旧结论中70 TPS的4秒级尾延迟不是前几轮Worker、Outbox或计费优化失效,而是Gateway在每条SubmitResp后并发扫描同账号pending回执;API仅查询pending,直到异步sent回调才改状态,因此多个扫描会读到同一批。旧版100/150冲击档客户回执重复曾达847/5101条,Gateway goroutine在150档峰值超过1.2万。
|
||||
|
||||
修复内容:
|
||||
|
||||
- Gateway同账号flushPending单飞,SubmitResp触发改为25ms尾随合并、250ms最长等待,登录和周期恢复保留;
|
||||
- API使用FOR UPDATE SKIP LOCKED原子将pending领取为dispatching,绑定claimId和5~120秒可控租约;当前Gateway默认30秒;
|
||||
- 客户离线或SubmitResp屏障时用claim_released无损释放,不消耗业务重试预算;过期dispatching自动恢复为pending;
|
||||
- API新建回执后的直推也必须先原子取得dispatching + claimId,从根源消除直推与Gateway恢复各发一次的竞态;
|
||||
- ACK超时定时器在ACK registry锁内完成指针赋值,Linux竞态检查不再报告该竞态。
|
||||
|
||||
发布仅到测试机100.93.204.60,预生产未操作。恢复点为/opt/cmpp-platform-backups/phase5-replay-hotfix-20260825T070815Z,PostgreSQL custom dump、运行源码、systemd/环境配置和SHA-256均已校验。Linux go test -race ./internal/inbound、Gateway全包测试/go vet、API 44套520项和TypeScript构建通过。最终运行标记为761c123b65f09093bc379096188f3ee9ccb2e618+workspace.phase5.replayhotfix2.directclaim。
|
||||
|
||||
正价阶梯(单价325):
|
||||
|
||||
| 目标档 | 客户SubmitResp | 客户P50/P95/P99 | 首次供应商Submit跨度/速率 | 结论 |
|
||||
|---:|---:|---:|---:|---|
|
||||
| 20 | 199/199 | 5/10/16ms | 9.930s / 19.84 TPS | 通过 |
|
||||
| 30 | 299/299 | 5/10/18ms | 9.854s / 30.34 TPS | 通过 |
|
||||
| 50 | 499/499 | 7/35/47ms | 10.067s / 49.47 TPS | 通过 |
|
||||
| 70 | 699/699 | 14/44/60ms | 10.666s / 65.54 TPS | 入口稳定,首提接近70档 |
|
||||
| 100 | 999/999 | 24/51/72ms | 13.345s / 74.86 TPS | 入口通过,端到端未达100 |
|
||||
| 150 | 1498/1498 | 49/87/114ms | 15.781s / 94.86 TPS | 冲击档,显示端到端天花板 |
|
||||
| 200 | 1998/1998 | 50/84/110ms | 21.661s / 92.24 TPS | 冲击档,端到端不再增长 |
|
||||
|
||||
直推领取修复后另执行100 TPS正价复验:999/999,P50/P95/P99=54/102/179ms;999个唯一MessageId、999次非补发首提在12.488秒完成(80.00 TPS)。972条已产生最终回执的投递全部ACK,生命周期尝试972、重复0、最大尝试1。计费998条:995 charged、3条供应商最终失败后refunded;1条RISK在供应商前失败且未计费。27条模拟器no-receipt保持submitted,不是队列丢失。Inbox、Submit Outbox、下游pending/dispatching/awaiting_ack、未授权锁和idle transaction最终均为0。
|
||||
|
||||
最终结论分两种口径:客户入口SubmitResp受理已通过200 TPS冲击;完整供应商首提天花板约95 TPS。测试环境建议稳定限速70 TPS,保留约25%以上端到端余量;不将入口200 TPS宣称为供应商200 TPS。按用户要求,7个测试应用单价保持325,^1380028/^1300028/^1890028三条临时号段规则保留,原100.91.249.119/32和新增127.0.0.1/32白名单均保留,未做测试后删除或归零。
|
||||
Reference in New Issue
Block a user