一次高峰期系统问题复盘
今年国庆的放假日期是9月29号,因此 15 号就是抢票的高峰期,而且今年是自20年以后,出行人数最多的一个节假日,12306直接瘫痪了十几分钟,导致系统的一些问题被暴露出来。
Redis不是万能的
在高峰期间,我们的单独的 Redis 集群QPS是 16W/s,期间依靠不断的扩容读节点才勉强扛住了这一波流量,Redis 读服务器的 CPU 一段时间几乎全部被打满。
在代码中看到很多逻辑都是将一些无用的字段也放在了 Redis 中,这样不仅会增加 Redis 的内存,而且在高峰期,极有可能由于一些大 key 导致 Redis 读写变慢,从而拖垮整个服务。
如果对于一些变动不是很频繁的 Key,尝试做本地缓存是一个不错的选择。一些大 Key 做好拆分,实在无法避免的,最好是进行压缩存入 Redis。
合理的评估DB查询
在这一次的高峰期还涉及到 DB 性能的问题,DB 的服务器长期保持着高负载运行,查看代码以后,发现里面的很多方法设计上存在许多不合理的地方。
例如在一个地方,已经将所需要的订单信息查询出来了,后续在调用其他方法的时候,还是只传订单号,然后再次查询一次DB。由于查询的是订单全表,所以每一次的查询对应 DB 都是一个 IO 操作。
重视主从延迟
这个问题也是这一次突然出现的,在读取写入的订单的时候,由于主库的CPU一直很高,导致主从延迟远远大于之前的 99 线,后续出现各种一致性问题,最常见的就是差额退款了。
在发起差额退的时候,接口一定需要做好幂等条件,而在查询的时候,如果 DB 服务器性能比较强,那可以直接查询主库。如果不能查询主库,那么一定需要数据库的唯一键对退款的流水号做幂等,否则很容易造成损失。
和其他接口打交道,做好并发控制
在和其他系统打交道的时候,尽量保证自己的系统不要并发调用,尤其涉及到一些订单结果的上报,和一些支付、退款的接口,优先保证自己的系统不会对其他系统产生负担,其次再是推动对方做一个幂等。
举一个例子:
现在我们的业务系统(老系统)中,会用 JOB 来扫描待推送第三方接口的订单信息,然后做补偿,这里面就会涉及到一个问题。
如果推送记录表此时新增一个记录,然后准备上报,而此时 JOB 也扫描到了这个记录,也准备上报,就会上报两次。虽然说此时没什么大问题,但是如果第一次上报以后,其他系统由于某些策略,再次将订单推过来重试下单,那么 JOB 扫描的那一次记录,很可能一下子就会导致该订单瞬间结束了。
当然,之前的开发人员也采用 Redis 的 setnxex 来处理并发调用,但是该方法明显存在缺陷。如果 JOB 由于一些原因导致该线程卡住了一段时间,redis 的 key 过期了,那么还是会出现此类问题
所以最好就是通过 CAS 来做一个并发控制,增加推送中的状态。