本文讲述如何被一秒钟1000次的QPS逼入绝境,又如何用两行代码完成自救
第一幕:
连续两天早上被运营电话叫醒,系统被打爆了,起初只是以为流量突增导致,简单扩了资源,指导第二天早上被打爆才引起重视,数据库监控到数据库实例cpu打满,反查sql 发现
UPDATE webhook_endpoint SET success_num=success_num+?,total_num=total_num+? WHERE point_id= 更新量很大
最初的设计简单粗暴。在MySQL里建了一张表,每次请求进来,就开启事务,UPDATE webhook_endpoint SET success_num=success_num+?,total_num=total_num+? WHERE point_id=?
之前交易量不大,数据回写并发不大,一直没有问题,知道业务流量翻倍发现数据库CPU瞬间飙升至98%。行锁(Row Lock)在1000次/秒的冲击下变成了单行道上的大堵车。连接池耗尽,pod内存瞬间翻倍,所有业务pod全部被拖死
第二幕:缓存击穿的至暗时刻
痛定思痛,我引入了Redis。这回总该行了吧?内存操作,纳秒级别!我用了
"INCR"命令,确实快了不少。但我犯了一个致命的错误——强一致性强迫症。
为了保证数据绝对不丢,我开启了Redis的AOF持久化,并且配置为
"always"。同时,为了双保险,我还在代码里写了定时任务,每隔一秒将Redis数据刷回MySQL。
结果,Redis的单线程模型在高频
"fsync"面前跪了,而MySQL再次因为频繁的UPSERT操作崩了。那天夜里,我删库重跑脚本三次,咖啡喝到反胃。我终于承认:在超高并发面前,任何试图同步写入磁盘的行为都是自杀。
第三幕:顿悟与重生
盯着"INCR"和异步队列的文档发呆。突然,一道灵光劈开了混沌:为什么一定要实时落盘?
我重构了方案:
1. 客户端只做一件事:利用Redis Cluster的分片能力,将数据按Hash Key分散,执行
"INCR"。
2. 剥离持久化:关闭不必要的AOF,让Redis飞一会儿。
3. 异步归拢:启动一个独立的Worker进程,通过Lua脚本原子性地读取Redis数据,批量汇总(Batch Insert)写入MySQL,然后删除已统计的Key。
这一改,世界安静了。MySQL的写入量从1000次/秒骤降到10次/秒(取决于批次大小)。Redis的QPS虽然没变,但CPU负载极低。
晚上10点多,我看着监控面板上那条平滑的曲线,泪水差点掉下来。那不是数据,那是我逝去的脑细胞。
尾声:
现在回头看,这不仅仅是一个技术优化,更是一次认知的蜕变。实时计算不等于实时存储,高频累加必须遵循“冷热分离”与“异步解耦”。
那个一秒1000次的怪物,最终被两行代码驯服。活下来的,是一个懂得敬畏并发的工程师。技术的成长往往伴随着系统的崩溃。这次经历让我深刻理解到,架构设计的本质是在取舍,而高并发的秘诀在于“拆”与“缓”。