我在本地进行压测,这是一个短信业务,地下截图是模拟测试者,它会向平台发送 1 万条消息,并发 1000。
一万条消息 p99 是 2317 毫秒,发送用时 13 秒。
平台收到消息,在处理完成任务后,将结果进行回调到压测程序,从截图可以看到接收延迟 p99 是 14 秒。1 个消息发出去 14 秒后才能收到结果,想想办法怎么优化。
在程序中,最容易出现瓶颈的就是数据库读写,根据日志找到慢 SQL。

数据库相关业务优化
插入优化
如果出现插入的慢 SQL,检查是不是大量一条数据一条数据插入的,可以合并为一次请求,批量插入。
重要的不可丢失的数据,先入消息队列,批量接收一定量或达到超时时间,就入库。
允许存在丢失的,用 channel 接收处理入库即可,达到 100 条或 10 秒入库一次,这样最大丢失也就 100 条。
在多轮测试中,批次大小也不是越大越好,随着批次越大,越靠近收益边际,大概在 BatchSize=200 左右。
读取优化
采用缓存机制,可启动时预热缓存,读取 redis 效率比读取 pg 更高。
优化效果
发送耗时减少了一倍,主要原因是回调接收对数据库的读写减轻,机器的性能也就提高了,所以发送耗时 间接 受益。
接收降低 50%,得益于读取优化,减少读的瓶颈。
优化前
总发送用时 21秒,接收 p99 20秒
优化后
总发送用时 12 秒,接收 p99 11秒,发送和接收提升了 50%
发送的 p99 是 2 秒,这 2 秒是本地的写消息队列涉及到持久化到磁盘,可采用分布式消息队列或异步落盘优化,接收的 p99 是 11 秒,11-2=8,平台执行任务花了 8 秒,说明还有优化空间。

更新优化
每收到一条回调,都需要更新数据库中该条记录的状态,如果改成批量更新呢? 消息先入消息队列,批量获取数据,一次性几百条更新到数据库。
先说结果,在高并发的情况下,每条独立更新,耗时 300 毫秒左右,批量百条更新,耗时 250 毫秒左右,从耗时上看差距不大,但是数据确实百倍处理了。
从压测的结果来看,p99 提升了 17%,p50 升高了,因为批量入队需要凑满一定数据才会一次性更新到数据库。
如果尝试将 batchSize 改成 1000 呢? 得到 P99: 12484ms,效率反而下降了,批量 update 的尾部效应。

从火焰图看消耗
Redis / PostgreSQL / Service / 消息队列全都部署在本地机器,同时读写磁盘,以及平台在 debug 的状态下会打印大量日志,通过 pprof 拿到的火焰图,可以看出数据库相关还可以优化。主要是 “插入优化”,这里是一个事务级的连表插入。
在正式环境中,采用更高级别的日志,防止 debug 输出太多内容,以及拆分中间件服务器,和多副本部署,测试结果会更好。


最佳配置
在上面的测试中,涉及到 batchSize 等配置,到底怎样是最佳配置?
以下内容基于本地电脑环境测试。
我们写一点 benchmark,用于测试 3 个问题:
- 事务插入2 表 与无事务插入2 表
- 批量插入, batchSize 在 100,300,500,700,1000 下的性能
- 批量更新,batchSize 在 100,300,500,700,1000 下的性能

压测结果汇总
测试 1:事务 vs 无事务 INSERT(sms_sessions + sms_messages)
| Benchmark | N | ns/op |
|---|---|---|
| TxInsert_Serial | 956 | 3.31 ms |
| NoTxInsert_Serial | 1840 | 3.25 ms |
| TxInsert_Parallel500 | 6987 | 632 µs |
| NoTxInsert_Parallel500 | 23041 | 138 µs |
串行差距小,并发下无事务吞吐量高出 ~4.5 倍。如果有兜底机制比如消息队列,可以关闭事务插入。
测试 2:批量插入 channel_http_logs
| Batch Size | N | ms/op | rows/s |
|---|---|---|---|
| 100 | 100 | 31.43 | 3,181 |
| 300 | 814 | 4.64 | 64,647 |
| 500 | 583 | 6.31 | 79,215 |
| 700 | 396 | 14.37 | 48,710 |
| 1000 | 186 | 21.55 | 46,397 |
rows/s 在 300~500 达到峰值,500 最优(79k rows/s)。700/1000 反而下降。
测试 3:批量更新 channel_http_logs
| Batch Size | N | ms/op | rows/s |
|---|---|---|---|
| 100 | 2376 | 1.52 | 65,623 |
| 300 | 758 | 4.58 | 65,492 |
| 500 | 564 | 7.12 | 70,252 |
| 700 | 324 | 10.52 | 66,521 |
| 1000 | 246 | 13.07 | 76,497 |
更新场景 rows/s 各 size 差距不大(6.5~7.6 万),吞吐量基本持平。
最终结论,期间还延长了 benchtime 多测了几轮,batchSize 不是越大越好,300 左右是个稳定点,实际可以根据不同的项目进一步确认
这三步的优化,降低了 p50 延迟 2s 左右,对 p90 的改善不显著。