今日后端系统发生一次严重的redis cpu 100%异常问题。期初是怀疑系统硬件问题,因为后端代码已经好几天没有发不过,理应是不会突然故障。于是一步步排查,首先从redis本身入手,其本身redis 占用率过高网上的说法也已经概括的比较全面了,不外乎bgsave,keys模糊查找和滥用排序导致redis性能下降。但是从我们本身的系统架构而言,redis的cpu使用率一直都稳定在3%左右,数据大小本身也不高,rdb文件本身也不大只有几百兆,占用的系统内存页也只有整个硬件内存的20%。磁盘io,cpu都很稳定,
首先通过strace定位的方法监控php-fpm和redis进程是否有异样,发现php-fpm都卡在和redis的通信上,而redis进程都卡在频繁读写上。info查看redis的total_commands_processed数值,尽然每秒高达20w次左右,我们知道通过redis的benchmark测试redis的性能通常每秒不走pipleline也差不多这个数值了,业务突然能将redis的吞吐能力消耗干净那肯定不外乎受外部大规模的压力攻击或系统内部的死循环导致。
攻击肯定就谈不上了,nginx上访问日志很正常,通过redis的monitor命令查看redis的实时执行命令,发现全部都是对同一个key进行写入和查询。接着通过get这个key输出的值发现了端倪。继而定位到业务代码中的执行片断。
问题排查到现在就好解决了,代码级定位发现了原先设计代码中不合理的设计,对于第三方外部接口调用没有采取异常情况的最终返回,导致不停的递归调用了第三方接口,但是第三方接口又因为查询条件的改变,导致始终执行不成功。这样无法缓存正常数据,就导致不停的查缓存和写缓存操作。
通过这次问题排查和解决,总结了如何使用第三方接口服务的设计原则:
1. 不管如何设计接口切记尽量避免使用递归来请求接口,如果没有捕获到极端异常,有可能导致程序死循环。
2. 所有外部接口都要设计一个最终失败的逻辑。
3. 对第三方接口增加缓存逻辑的同时,一定要对失败的结果也做一份缓存,避免频繁请求第三方接口而导致同步io延时的问题。
4. 千万不要认为大厂的API接口都是极其稳定的,是的!今天遇到的就是百度的云API提供的接口,虽然大厂通常对自己提供的服务接口都有做API自动化测试,但是故障恢复能力并不一定每个业务团队都能及时响应。更不要以为大厂的开发们都是各个经验丰富,知道如何保证API的可用性和稳定性。如果哪天他们升级了代码将接口的传参做了调整,这种对调用方的伤害也是巨大的。一个设计合理的接口一定遵循一点,同版本接口输入和输出必须保证确定性,做了调整的接口一定得区分版本号。