张松然,京东商城,商家研发部架构师。丰富的构建高性能高可用大规模分布式系统的研发、架构经验。2013年加入京东,目前负责京麦服务市场的系统研发工作。
本文是记录2015年的一次线上事故,引发的对系统架构和性能的思考,以及如果考虑系统容灾和降级的问题。在大流量高并发下的互联网环境下,如何做到系统稳定,不被异常流量击垮,是我等软件人需要思考和不断优化改造的目标。
问题来了
记得是一个周六的早上,手机就开始不停的震动,拿起一开,是洪水般的报警短信,大概印象记录如下:
网关系统报出性能报警,各种接口超时
网关系统报出方法可用率报警,统计网关调用量峰值 190w+/min
Jimdb(redis)出现 failover 主从切换报警,而且相当频繁
Jimdb 出现 Connection Lost 报警
联系 dba 对 Jimidb 进行扩容,并调大连接数
Jimdb 恢复
网关系统 CPU 使用率飙升,服务器出现宕机
对 Jstack 进行分析,发现 youngGc 异常频繁
通过对 Jmap 进行分析,发现有 sql 会对 mysql 进行全表扫库
…
网关系统是对外提供 API 服务调用的系统,对使用资源进行监控发现:
网关日志 JMQ 积压 8亿+
网关日志 ES 容量 2T
网关日调用量 8亿+
网关数据库 CPU 使用率 85%
通过 UMP 统一监控平台,可以看到网关调用量,按小时维度统计如下图:
按日维度统计如下图:
当日和时段的调用量已经远高于平时调用量,这种异常的调用量对网关系统造成了极大的压力,对数据库 CPU 使用率情况,按小时维度监控发现(如下图),CPU 使用率随着系统问题恶化,逐步趋近 100%。
去找原因
因为网关系统主要是对外提供 API 服务调用的系统,所以问题极有可能是某个接口的异常调用量引发整个网关出现的问题,通过对报警的排查,最终定位是订单查询接口调用量异常猛增,引发的整体网关系统的问题,接口调用量数据也对得上,见下图:
但这种连带问题是如何发生的?
下图是对网关系统出现问题的分析:
网关系统分为网关接入层、网关应用层和网关监控共三个部分,网关接入层又包括网关防御校验和网关接入分发两块。分析问题原因,大致过程如下:
问题一、网关调用需要对调用进行实时监控,监控包括网关性能和网关错误,而这些数据都是通过 jimdb 进行存储的。而当网关调用量异常猛增时,造成了 jimdb 的分片热点,由于 jimdb 的资源不足,造成 jimdb 不停 failover,很快恶化服务不可用。
问题二、网关调用日志是网关监控的重要部分,日志是通过 jmq 发送到 es 中存储。当网关调用量异常猛增时,请求 jmq 的连接不足,而发送给 jmq 的消息也开始挤压,最终发送日志的线程造成服务器线程数高,线程切换,以及内存回收等,形成 jvm 频繁的 young gc,并且 jmq 也近乎不可用。
问题三、网关接入层中防御校验逻辑中校验 token 和 pin 的业务依赖于 jimdb,而当 jimdb 不可用时,代码会穿透 jimdb 进行查询 mysql,进而对大量请求访问 msyql,造成 mysql 巨大压力,最终 mysql 恶化成服务宕机。
问题四、jimdb 穿透造成 mysql 宕机,网关应用层查询数据库中的 sql 大量超时,再次对网关系统形成压力,而网关防御层的一些代码 bug,造成参数丢失,形成查询数据库进行全库扫描,系统最后一颗稻草断了,网关系统崩塌了。
快速抢救
通过问题看到网关系统的架构存在着不合理设计,异常流量下,重启服务器已经无济于事。需要迅速进行止损,既然分析出了问题点,就针对问题点进行补丁式的抢救,大概思路如下:
解决问题一、对网关性能监控功能进行降级,毕竟不是核心主流程,没有该功能网关还是可以调用,降级之后不再进行 jimdb 数据存储。后期也需要考虑改造其他方案。
解决问题二、对网关日志功能也可以进行降级,同样不是核心主流程,由于是单个接口的引发的灾害,可以针对网关接口的日志进行黑名单处理,对调用量大的接口调用日志不存储离线日志。
解决问题三、由于不可理设计形成了 jimdb 热 key,暂时无法解决,所以改造在调用 jimdb 之前增加 jvm cache 防御热点,同时修复代码 bug,以及对 jimdb 进行拆离,将网关日志的 jimdb 和网关校验使用的 jimdb 进行隔离部署,实现互不影响。
解决问题四、网关系统是需要对异常流量可以进行自动降级和熔断的,设计时不能因为个别接口把整个网关系统打挂,所以在网关接入层,增加了流量控制逻辑,对并发数进行限流,通过令牌桶实现,进而让网关拥有自我保护能力。
解决问题五、网关应用层检查 sql,对可能全面扫库的代码修改,有效保证数据库安全。后期将网关应用层和接入层进行系统隔离部署,真正有效解耦网关接入和处理业务逻辑,完整方案见下图。
想一想
异常流量是怎么造成的?
因为网关是面向客户端提供 API 服务的,客户端发新版,有个逻辑是 5 秒查询一次订单接口,想要做到实时,而客户端升级数量上万后,这种调用量就直接将网关打死了。
这些异常又是怎么消退的?
因为当时客户端与服务端还是 http 无状态请求,客户端登录也是基于该网关,当时尝试通过 http 长链接通知客户端下线,但是效果甚微,网络带宽基本被打满了,是否有效触达客户端都是未知,最终是通过重启服务器,迫使客户端超时重新登录,在登录逻辑中禁止该版本客户端进行访问网关,逐渐实现泄洪的。
这一过程是艰难而漫长的。
而也是这一次事故,促成了对网关系统的改造,实现了客户端 TCP 长链接网关,以及业务服务 HTTP 网关的剥离和构建,也因此深入理解了 Netty、NIO 等技术,并成功应用在业务中。现在 2018年,客户端 TCP 长连接同时在线量进几十万,感触颇多。
想来,每一次问题都可能是一次技术飞跃和成功的机会。