Files
banban/docs/local-mqtt-stability-concurrency-test-plan.md

508 lines
11 KiB
Markdown
Raw Permalink 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.
# 本地 MQTT 并发与稳定性测试方案
更新日期:2026-07-14
本文档用于说明 Banban / TalkingQ 后端在不调用真实外部服务的前提下,如何做本地全流程并发和稳定性测试。测试重点是 MQTT 设备接入、消息解析、服务端回包、GPS 查询响应、告警处理、数据库落库、后端健康状态和资源变化。
## 1. 测试目标
### 1.1 并发度
并发度用于回答系统同时承载多少设备连接和消息流量。
本项目里主要看:
- 同时在线的 MQTT 设备客户端数量。
- 设备状态上报 `msg_id=000` 的吞吐。
- 告警事件 `msg_id=010` 的吞吐和落库。
- 服务端 GPS 查询 `msg_id=001` 的下发和设备响应。
- 服务端事件响应 `event_resp` 是否完整返回。
### 1.2 稳定性
稳定性用于回答系统在持续运行一段时间后是否保持正确和健康。
本项目里主要看:
- 设备连接是否持续保持。
- 上行消息和服务端回包是否丢失。
- 数据库写入是否持续成功。
- 后端健康检查是否持续返回 `200`。
- 错误日志是否新增。
- CPU、内存、数据库连接数、MQTT 连接数是否持续爬升或堆积。
## 2. 测试边界
### 2.1 本次覆盖
- 本地 FastAPI 后端。
- 本地 MQTT broker。
- 本地 MySQL。
- 真实 MQTT 客户端连接,每个模拟设备一个 MQTT client。
- MQTT 主流程:
- `device/{device_id}/event` 上行。
- `device/{device_id}/event_resp` 下行响应。
- `device/{device_id}/command` 服务端命令。
- `device/{device_id}/response` 设备响应。
- 状态上报、告警、GPS 查询、异步持久化、告警落库。
### 2.2 本次不覆盖
- 线上 MQTT broker。
- 线上服务器网络和 TLS。
- 真实高德逆地理编码调用。
- 真实短信发送。
- 真实设备固件网络抖动。
- 断线重连风暴。
- 慢数据库、数据库断连、外部服务长超时。
这些场景需要单独做线上灰度压测或故障注入测试。
## 3. 外部服务隔离策略
本地全流程测试要验证主流程,但不能依赖真实外部服务。
推荐策略:
- GPS 响应使用无效坐标 `lat=0,lng=0`。
- 后端应忽略无效 MQTT 定位,不调用高德解析。
- 告警测试账号不配置可用家长手机号。
- 后端短信链路应走“无手机号跳过”,不调用短信服务。
- 不使用真实 COS 上传、微信模板消息或其他公网依赖作为通过条件。
通过标准不是“外部服务成功”,而是“外部服务不可用或被跳过时,MQTT 主流程、回包和落库不受影响”。
## 4. 测试环境
### 4.1 本地服务
在 `talkingq-url` 目录运行:
```bash
.venv/bin/amqtt
```
启动后本地 MQTT broker 监听:
```text
127.0.0.1:1883
```
启动后端:
```bash
.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8080
```
健康检查:
```bash
curl -sS -o /dev/null -w 'openapi_http=%{http_code} time=%{time_total}\n' \
http://127.0.0.1:8080/openapi.json
```
预期:
```text
openapi_http=200
```
### 4.2 配置要求
`talkingq-url/.env` 应指向本地服务:
```text
DB_HOST=127.0.0.1
DB_PORT=3306
TALKINGQ_MQTT_BROKER=127.0.0.1
TALKINGQ_MQTT_PORT=1883
```
### 4.3 测试设备数据
建议使用专门的压测设备 ID,避免污染真实设备:
```text
TalkingQ_LOAD000001
TalkingQ_LOAD000002
...
TalkingQ_LOAD000500
```
测试前确认:
- `device_auth` 有对应设备。
- `device_bindings` 有对应绑定。
- 告警落库使用的 `owner_user_id`、`child_id` 不超过当前数据库字段范围。
检查示例:
```sql
SELECT COUNT(*) FROM device_auth WHERE device_id LIKE 'TalkingQ_LOAD%';
SELECT COUNT(*) FROM device_bindings WHERE device_id LIKE 'TalkingQ_LOAD%';
```
## 5. 压测脚本要求
压测脚本应满足以下行为:
- 每个设备使用一个独立 MQTT client。
- 每个 client 订阅:
- `device/{device_id}/event_resp`
- `device/{device_id}/command`
- 每个 client 周期性发布 `msg_id=000` 设备信息。
- 按固定比例发布 `msg_id=010` 告警。
- 收到服务端 `msg_id=001` GPS 查询后,回复 `device/{device_id}/response`。
- 默认 GPS 模式使用 `invalid`,返回 `0,0`,避免真实高德调用。
- 统计发送数、回包数、GPS 命令数、GPS 响应数、发布延迟。
状态上报 payload 示例:
```json
{
"msg_id": "000",
"data": {
"id": "TalkingQ_LOAD000001",
"power": 79,
"signal": 4,
"version": "load-client-1.0.0",
"voice": 60,
"imei": "990000000000001",
"device_type": 2,
"latitude": 0,
"longitude": 0,
"coord_type": "gcj02"
}
}
```
告警 payload 示例:
```json
{
"msg_id": "010"
}
```
GPS 响应 payload 示例:
```json
{
"msg_id": "001",
"status": "success",
"data": {
"latitude": 0,
"longitude": 0,
"coord_type": "gcj02",
"accuracy_m": 10,
"source": 1,
"battery_pct": 80
}
}
```
## 6. 推荐测试阶段
### 6.1 冒烟测试
目的:确认脚本、broker、后端、DB 基本可用。
建议参数:
```text
20 个 MQTT client
1 轮状态上报
每 10 台设备触发 1 次告警
settle 5 秒
```
通过标准:
- client 连接成功数等于请求数。
- `sent_total == event_resp_total`。
- `resp_000 == sent_000`。
- `resp_010 == sent_010`。
- `command_001 == gps_response_sent`。
### 6.2 短时并发测试
目的:验证较高设备规模下的瞬时吞吐。
建议参数:
```text
500 个 device_id
3 轮状态上报
每 10 台设备触发 1 次告警
settle 20 秒
```
通过标准:
- 所有事件收到 `event_resp`。
- 告警落库数量与发送数量一致。
- 健康检查仍返回 `200`。
- 新增错误日志为 `0`。
### 6.3 真实连接稳定性测试
目的:验证 500 个独立 MQTT client 持续在线时,主流程是否稳定。
建议参数:
```text
500 个 MQTT client
持续 30 分钟
每 15 秒每台设备上报一次 msg_id=000
每 60 秒每 20 台设备触发一次 msg_id=010
GPS mode = invalid
settle 20 秒
```
通过标准:
- 500 个 client 全部连接成功。
- 连接数在测试期间保持稳定。
- `sent_total == event_resp_total`。
- `sent_000 == resp_000`。
- `sent_010 == resp_010`。
- `command_001 == gps_response_sent`。
- 30 分钟内后端健康检查持续返回 `200`。
- 新增错误日志为 `0`。
- MySQL 连接数没有持续堆积。
- 后端和 broker RSS 没有持续单调增长。
### 6.4 长时稳定性测试
目的:把 30 分钟结论提升为更可靠的上线前参考。
建议参数:
```text
500 个 MQTT client
持续 2 小时
或过夜 8-12 小时
保持 15 秒状态上报和 60 秒告警节奏
```
增加观察项:
- 日志滚动后是否还能正常写入。
- 定时任务是否干扰 MQTT 主流程。
- 数据库表增长后查询是否变慢。
- 后端进程 RSS 是否长期爬升。
- broker 是否出现连接残留。
## 7. 监控指标
建议每 60 秒记录一次:
```text
sample
elapsed_s
backend_cpu
backend_mem
backend_rss_kb
broker_cpu
broker_mem
broker_rss_kb
mqtt_established
mysql_threads
mysql_processes
http_code
http_time_s
new_error_lines
```
采集方式示例:
```bash
ps -o %cpu=,%mem=,rss= -p <backend_pid>
ps -o %cpu=,%mem=,rss= -p <broker_pid>
lsof -nP -iTCP:1883 -sTCP:ESTABLISHED
curl -sS -o /dev/null -w '%{http_code} %{time_total}' http://127.0.0.1:8080/openapi.json
```
MySQL 连接数:
```sql
SHOW STATUS LIKE 'Threads_connected';
SHOW PROCESSLIST;
```
错误日志过滤:
```bash
rg -n "(ERROR|failed|DataError|Out of range|告警地址解析失败|当前位置地址解析失败|amap|高德)" \
talkingq-url/logs/talkingq_YYYY-MM-DD.log
```
## 8. 本次执行结果
执行日期:2026-07-14
### 8.1 单元测试
```text
86 passed
```
### 8.2 500 客户端 30 分钟稳定性测试
测试参数:
```text
devices_requested=500
duration_seconds=1800
info_interval_seconds=15
alarm_every=20
alarm_interval_seconds=60
gps_mode=invalid
```
结果:
```text
elapsed_seconds=1820.053
client_ready_success=500
client_ready_failed=0
sent_total=60750
event_resp_total=60750
sent_000=60000
resp_000=60000
sent_010=750
resp_010=750
command_001=750
gps_response_sent=750
publish_latency_ms_avg=29.301
publish_latency_ms_p95=48.727
```
### 8.3 数据库验证
```text
device_settings_load=500
device_alarm_events_load_total=877
device_alarm_events_load_last_40m=750
child_location_current_load=0
mysql_threads_connected=6
```
说明:
- `device_alarm_events_load_last_40m=750` 与本轮告警发送数一致。
- `child_location_current_load=0` 是预期结果,因为 GPS 使用 `0,0`,后端忽略无效定位。
### 8.4 资源变化
后端:
```text
压测前 RSS 约 129.9MB
500 客户端连接后 RSS 约 131-132MB
中途回落到约 120-121MB
结束后约 121.6MB
CPU 基本 0.1%-0.2%
```
MQTT broker:
```text
压测前 RSS 约 43.9MB
500 客户端连接后 RSS 约 73-75MB
后半程稳定在约 69-70MB
结束后约 70.1MB
CPU 基本 0.0%-0.3%
```
连接数:
```text
压测前 mqtt_established=2
压测中 mqtt_established=1002
压测后 mqtt_established=2
```
说明:本机 loopback 下 `lsof` 会看到连接两端,`1002` 约等于 `501` 条 MQTT 连接,包括 500 个设备客户端和 1 条后端服务连接。
### 8.5 日志验证
测试窗口内新增错误日志:
```text
0
```
未发现:
- `ERROR`
- `failed`
- `DataError`
- `Out of range`
- `告警地址解析失败`
- `当前位置地址解析失败`
- `amap`
- `高德`
## 9. 本次结论
本地环境下,当前项目已验证可以稳定承载:
```text
500 个独立 MQTT 客户端
持续 30 分钟
60750 条事件完整回包
750 条告警完整落库
750 次 GPS 查询完整响应
```
测试期间:
- 后端健康检查持续 `200`。
- 新增错误日志为 `0`。
- MySQL 连接没有堆积。
- 后端和 broker 内存没有持续爬升。
- 外部服务未调用时,MQTT 主流程不受影响。
因此可以判断:本地单实例下,500 台设备级 MQTT 连接和当前消息节奏的 30 分钟稳定性通过。
## 10. 风险和下一步
### 10.1 当前结论不能直接等同线上容量
原因:
- 本地 broker 是 `amqtt`,不是线上 broker。
- 本地网络没有公网延迟、TLS、丢包、移动网络抖动。
- 高德和短信没有真实调用。
- 测试时长是 30 分钟,不是 24 小时。
- 没有模拟断线重连风暴。
### 10.2 建议补充测试
上线前建议继续做:
- 500 客户端持续 2 小时。
- 500 客户端过夜 8-12 小时。
- 断线重连测试:一次性断开 20%、50%、100% 客户端后重连。
- 慢 DB 测试:模拟 DB 写入变慢,确认 MQTT 回包和主流程是否可控。
- 高德失败测试:模拟超时、HTTP 500、无效 Key,确认地址解析失败不影响告警落库。
- 短信失败测试:模拟短信服务超时或失败,确认告警主流程不被拖垮。
- 线上灰度测试:在真实 broker 和服务器规格下从 50、100、300、500 逐步提升。
## 11. 通过标准汇总
一次测试可判定通过,需要同时满足:
- 客户端连接成功数等于目标数。
- `sent_total == event_resp_total`。
- `sent_000 == resp_000`。
- `sent_010 == resp_010`。
- `command_001 == gps_response_sent`。
- 告警新增落库数量等于 `sent_010`。
- 健康检查全程 `200`。
- 新增错误日志为 `0`。
- MySQL 连接数没有持续增长。
- 后端和 broker RSS 没有持续单调增长。
- 测试结束后 MQTT 连接数回落到基线。