每年 618 的大促都是一场技术团队大练兵的时候。作为技术研发人员,在这场战斗中,加深了对线上系统的敬畏之心,通过系统的备战,在技术上也得到了提升。大战在即,如何保障系统稳定,我们的备战思路是什么?
首先确定自己的备战思路,梳理核心流程、找出薄弱点,对薄弱点进行优化,针对业务进行资源隔离,同时协调压测对优化结果进行验证,并针对场景准备预案,比如降级开关在什么场景下开启,以及降级之后的影响有哪些等等,后面还要实际演练,防止实际情况发生时准备好的预案不可用。
我们知道,618 期间发生的问题都不是小问题,所以,如何做到更周密的筹备,我们可以秉承如下几个原则:
• 墨菲定律:任何事都没有表面看起来那么简单;所有的事都会比你预计的时间长;会出错的事总会出错;如果你担心某种情况发生,那么它就更有可能发生。
• 二八原则:19 世纪末意大利经济学家巴莱多认为,在任何一组东西中,最重要的只占其中一小部分,约 20%,其余 80%尽管是多数,却是次要的,这就是二八法则。二八法则告诉我们,不要平均地分析、处理和看待问题,要把主要精力花在解决主要问题、抓主要项目上。
我们可以利用墨菲定律来梳理系统薄弱点,那些总是小问题不断的地方,一定会在某天酿成大的灾难,必须尽早解决。还可以利用二八原则梳理黄金流程,那些出了问题可能极大影响公司或影响用户的地方,就是不容有失的黄金流程。
我们以此为思路,从四个方面进行逐一介绍。
梳理薄弱点
梳理系统架构
备战第一步,就是要对系统做一次全面的梳理诊断,其目的就是要甄别出系统的薄弱点。而梳理的第一步,就是要梳理出系统架构。通过梳理系统架构,梳理系统的层次结构和调用关系,由此排查系统调用的瓶颈和薄弱点。如是否有服务节点是热点或单点服务,是否有过长的调用链路,是否存在业务耦合相互影响等等。
以我现在负责的京东服务市场为例,服务市场采用 SOA 微服务化的架构设计理念,将服务市场设计为模块化和层次化的架构风格,高层次模块调用低层次模块,低层次模块通过接口向上提供服务。而复杂的调用链路,必然造成业务之间的相互影响,所以,通过对服务市场从部署、数据库访问、服务 PRC 调用、消息接收等进行了纵向垂直部署隔离,实现即使任一垂直域无论因为服务器还是数据库问题,影响不会扩散到其他业务上。这我会在后面详细讲述如何实现纵向垂直部署隔离。
梳理系统薄弱点
无论系统架构是以哪种形式展示,我们都希望能够通过梳理把系统的薄弱点甄别出来。那系统薄弱点都有哪些特征呢?通过哪些手段能检查出系统薄弱点?
我们想一下,系统薄弱点可能引发哪些灾难,严重的有系统宕机,服务不可用,性能下降,404,CPU 100%,OOM 等等,所以,可能产生上述隐患的点就是我们要关注的系统薄弱点。对于系统薄弱点,我们可以通过以下几点进行排查:
• 检查没有脱库的功能:我们知道大促时的流量可能是平时的 2~10 倍,所以一些平时性能还好的查询接口,极有可能因调用量猛增,造成性能的极剧下降,如果其中的某些接口还是查询数据库的,那可能就会给数据库产生极大的压力,极端情况可能造成数据库 CPU 100%,出现服务不可用,因为数据库本身的架构设计就不是抗量的,所以对没有必要查库的功能进行脱库改造,以保证数据库的稳定,是非常重要的,尤其是那些严重依赖数据库的系统。
• 检查慢 sql:不是所有的功能都能实现脱库的,所以慢 sql 的检查就是为了保证数据库的稳定,因为一个慢 sql 就有可能把数据库的 CPU 打到 100%,一是由于慢 sql 最容易出现的是没有索引,二是由于慢 sql 大多是关联查询、嵌套查询,以及使用聚合函数等造成的,还有就是慢 sql 大多需要回源查询,大量的请求会造成查询能力特别慢,还会造成其他请求无法获取连接,影响正常服务。所以,对于那些特别依赖数据库的系统,要每日订阅慢 sql,进行优化。优化就是优化数据库索引,将慢 sql 变成快 sql。
• 检查 ump 和 log:人常说,研发人员有两只眼睛,一只是监控报警,另一只就是日志,所以无论什么情况监控报警日志一定不能少。因为有了监控就能做到即时发现问题,有了日志就能做到迅速定位问题。否则,出了问题,就是两眼一抹黑,一通胡猜。我经常看到,当有些问题从客服那里投诉过来,才发现,要么该有的监控没有,要么有监控却没有报警,以及在解决问题时总是找不到关键的日志,只能干着急。
• 检查 jsf、mysql、jmq 等 timeout:设置 timeout 实际上是一种快速失败策略,使系统具备自我保护的能力。检查超时,一是检查该有的设置有没有,二是检查设置的时间是否过长。没有超时设置,则系统就没有自我保护的能力,这自不必说,当大量请求连接因为长时间运行而无法得到释放时,系统的资源很快就会被耗尽,从而造成了服务的不可用。而过长的设置,同样在访问量大的时候,就会导致请求连接积压,这也会导致系统的 CPU 快速飙升。那超时应该设置多少合适,这需要具体情况具体分析,但一个查询请求设置 10s 超时肯定是跟没设置一样的。
• 检查 redis 热 key、大 key、慢日志:热 key 和 大 key 是不同的,热 key 是某些 key 可能会被突然暴增的流量大量访问,这些 key 又碰巧集中存储在同一分片上,从而使得该分片的 IO、CPU 等资源吃紧,极端情况下迫使 redis 进行 failover 主从切换,但切换后的瞬时流量可能又一下子击穿 redis,迫使 redis 再次 failover,如此反复,服务几乎就是不可用的。而大 key 则是某些 key 存储的 value 很大,或者结果集很多,也是在可能暴增的流量下,大 key 不合理的查询,使得 value 在 IO 传输中阻塞,这情况和慢 sql 很像,又因为 redis 是单线程,所以大 key 查询造成 IO 问题就会很容易凸显出来。所以,如果检查出热 key 或大 key,就建议进行修改。
• 检查 master-slave 同步延迟:master-slave 同步延迟很大可能造成数据不一致性,这有可能影响业务正常运营,如创建订单写到 master,然后从 slaver 查询订单,但由于延迟太大,就可能影响用户查不到,你可能会说,这还好吧,慢 1~2s 不会有太大影响,但这个逻辑如果放在创建订单之后马上查询订单,根据查到的订单创建结算,在这个场景下,如何查询 slave 找不订单,就是不可接受的,这可能导致流程代码出错。所以,需要评估这种延迟是否对业务产生影响。
• 检查定时 worker 全量扫描任务:定时 worker 大多数是在运行数据订正的任务,而数据订正往往又需要查询大量数据进行比对,其实,这都还好,不好的就是几次深分页的查询,可能造成数据库性能的极剧下降。所以,检查定时 worker,尤其要避免慢 sql 和大数据集的查询,否则会造成人为的流量洪峰。还有,检查定时 worker 的执行频率,检查不同 worker 的执行时间,尽量避免不同 worker 同时执行,检查定时 worker 执行的服务器是否单独部署。
与运营沟通 618 运营节奏,应战调用量高峰:提前与运营沟通运营节奏,可以做到知己知彼,并可以预知流量可能流向哪里,这可以有针对性的进行备战。
梳理黄金流程
黄金流程也称核心流程,什么是黄金流程,就是那些出了问题可能极大影响公司或影响用户的地方,就是不容有失的黄金流程。以服务市场为例,服务市场包括服务发布、服务审核、服务订购、服务结算、服务使用共五个环节,那其中哪些是重中之重的核心环节呢?服务市场是面向供需双方的双边市场,以需求方为大头,所以服务订购和服务使用是服务市场的核心环节。
梳理黄金流程,我们要整理出它的大致流向和关键节点,通过系统流向和关键节点发现系统设计不合理的地方,以服务市场交易流程问题,我们看到在履约中心订单完成后,通过观察者模式发送消息、创建订购、推送结算的设计是存在风险的,因为每个观察者失败了就失败了,且每个观察者的业务逻辑都不是幂等逻辑。这种设计的不合理,就必须通过梳理黄金流程来发现。
资源隔离
梳理依赖资源(docker、vip、mysql、jimdb、es、jsf、jmq),及目前资源的服务器状况是否健康(cpu、memory、dish 等),进行纵向垂直部署隔离,实现即使任一垂直域无论因为服务器还是数据库问题,影响不会扩散到其他业务上,而这也是所谓的分离技术。
docker 资源
梳理系统 docker 资源部署情况,设定 x 轴是各个业务线,y 轴是业务系统,整理出一张二维表,根据之前梳理的系统架构图,可以清晰的看到各业务线下各个业务系统在不同机房的部署情况,如是否双机房部署,是否有热备机房,是否部署资源不合理等。机房 1 和机房 2 之间是两个不同的地域部署,所以通过地域可以实现容灾。还有,虽然系统都是一套代码,但可以根据各业务线的具体情况,某些业务线不需要的某些系统就不部署,因为代码走不到那里,并可根据业务调用量,合理配置资源情况。
梳理资源,一是梳理自己的资源部署情况,二是需要对上下游依赖系统,做到资源对齐(为了效率考虑,目前很多应用正常情况是同机房调用)譬如:如果上游部署在 3 个机房,机房 A、机房 B、机房 C,流量配比是 5.3.2,那我们也应该按照该比例部署。
数据仅为示例,不是真实数据;表格形式仅供参考
vip 资源
检查 vip 配置,避免因为 docker 扩容或缩容,导致的 vip 配置错误,将扩容的 docker 加到 vip 中,将缩容的 docker 从 vip 中去掉。检查运营商的 IP 解析情况,譬如,需要支持电信、联通、移动、教育网等。当然,并不是所有业务系统都提供对外服务。热备机房不挂 vip,否则就会有线上流量了。当主力机房出问题的时候,通过将问题机房的 vip 飘到热备机房,从而实现流量切换。
数据仅为示例,不是真实数据;表格形式仅供参考
MySQL 隔离
在 docker 资源的部署背景下,我们绘制 mysql 资源的部署情况,其中,有些业务线下的机房虽然有部署系统,但该业务线的流程是已经脱库的,所以就可以不配置数据库。还有某些流程在某些业务线下只有读操作,没有写操作的,所以,数据库只配置读库,而不配置写库。以及最重要的,不同业务线的应该使用不同的从库,并根据查询数据库的业务量,合理配置从库资源规格。mysql 与 docker 尽可能在同机房部署,这也可以有效的实现系统容灾。
数据仅为示例,不是真实数据;表格形式仅供参考
Redis / Solr / ES 隔离
绘制 redis、solr、es 部署情况与绘制 mysql 思路一样。特别说下,服务市场部署 redis 与 mysql 不同之处在于,redis 是一主一从架构,各业务线共享同一 redis 集群,但不同系统之间使用不同的 redis 集群,所以,如果 redis 挂了,还是会影响所有业务线的,而避免 redis 宕机,则就要注意前面提到的,检查 redis 的大 key 和热 key。
数据仅为示例,不是真实数据;表格形式仅供参考
JSF 业务隔离
梳理业务依赖关系,调整 jsf 调用链路,通过 jsf 分组进行业务隔离,这其实是最重要的,也是最难的,也只有把这个做好,才能实现各业务线出了问题不会相互影响。我们知道,一个系统里,既有 provider 也有 consumer,我们要做的就是保证在某一个业务线内,该业务线内 consumer 一定调用该业务线内的 provider 的,如服务市场的业务线 B,那一定是大前台在业务线 B 下只能调用业务线 B 的商品中心、交易中心、服务引擎等,而这实现就是通过 jsf 的分组别名,这没有啥技术含量,就是要了解业务依赖关系,认真仔细。由于系统是一套代码,对于那些有部署系统,但不提供 jsf 的服务分组,我们可以设置为 disable,以此区分和隔离。
在进行 jsf 分组隔离时,因为工作量很大,很是耗神,还有就会想到一个问题,一个系统提供几十个甚至上百个 jsf 接口出去,为什么分组命名会千奇百怪,几乎是每个 jsf 分组都有自己的命名,但仔细想来 jsf 接口可以做到细粒度隔离,因为 jsf 可以实现进程隔离和线程隔离,这使得即使是同一系统的 provider,也可以实现进一步的细分业务,来隔离各业务间的相互影响。
数据仅为示例,不是真实数据;表格形式仅供参考
JMQ 业务隔离
jmq 大量应用在系统解耦的场景中,而 jmq 同样有 provider 和 consumer,它不像 jsf 那样灵活,可以支持多个分组,jmq 只能控制哪些系统生产消息,哪些系统可消费消息。因为 jmq 是通过 topic 进行消息传递的,我们不能给每个业务线申请一个 topic,所以 jmq 的隔离更多的是,梳理业务依赖关系,对那些不会生产或消费 jmq 的业务线,设置 topic 为 disable,确定消息会被哪些系统生产,又会被哪些系统消费。
数据仅为示例,不是真实数据;表格形式仅供参考
压测
压测不是为了把系统压挂,是否需要测出峰值,需按照业务场景决定。很多业务,可能就需要知道自己的峰值。压测一定是根据当前调用量进行评估,以 2~10 倍为预期值进行压测,压测还要选在掉用量少的时间,并逐步加量。压测尽量做到服务器及依赖资源的隔离,如果无法做到,需要控制好量。尤其当压测会查询数据库的服务,一定不要把数据库压出问题来,如果压出问题,就违背了压测的初衷,压测是为了把系统的瓶颈压出来,而不是把系统压出问题来。即使是完全读取缓存的服务,也会因压测造成系统性能的下降,因为数据在传输过程中的序列化和反序列化,以及对多线程的切换,都会造成 CPU 的飙升,还有注意磁盘空间是否会被日志打满,诸如以上等等问题,不要因为压测造成一场线上事故。
压测我们要关注哪些是结果指标,有并发数、TPS、TP99、成功率,这些结果指标能有效反应服务的好坏,以及关注被压测服务的 docker 的 CPU、内存、load 等系统性能。所以,压测策略一定是逐步加量,从并发 10 - 50 - 100 - 200 - 500,观察服务的 TPS、TP99、成功率是否有降低,还有被压测机从 1 - 5 容器数增加,对比单机服务性能是否有所提升。所以,压测的目的是为了能检验服务能力是否支持可水平扩展,即加机器就可以抵抗洪峰。
最后,压测一定要制定计划,今天压什么,明天压什么,还有跟兄弟团队打好招呼,因为别的团队也会压测,如果他的服务会调用到你这里,而这是你也在压测这个接口,结果超出预期的流量就可能造成意想不到的麻烦。
预案
预案准备至关重要,它能保证系统在出问题时,进行及时止损,避免大出血。止损主要以降级开关来实现有损降级,以保证核心黄金流程不受到影响。因为系统业务演进多是混沌的,所以需要对系统的降级开关进行有效的梳理,哪些是有用的,那些是无效的,还要协调兄弟团队,一起沟通 618 备战方案,确定演练方案,联合备战。
对降级开关的梳理,不能只停留在知道有这个开关的基础上,还要知道这个降级开关在什么场景下开启,以及降级之后的影响有哪些等等,降级逻辑必须场景化,否则出了问题,开关开还是不开,都没有一个准则。
总结
当然,在备战 618 的过程中,有很多事要做,不仅仅是业务隔离和压测,还要在梳理系统薄弱点过程中发现潜藏的问题,进行针对性的优化改造。这里有个题外话要说,备战过程中,我偶尔会听到一些声音,让我非常生气。
一是说,”之前没出问题,所以觉得就没问题“。我见过多少次线上事故,就是因为思想上的怠慢,问题并不是不知道,就是本着侥幸的思想,觉得之前没问题,觉得现在就没有问题。这种想法我认为就是错的,之前没问题,不代表它不是问题,是问题就是问题。我犯过错一个错误,就是大上周上线报警出一个问题,排查有个慢 sql 查库,然后进行了修改,不急不慢改一周准备第二天晚上上线,结果第二天早上就被人刷了,数据库 CPU 直接干到 80%,好在这个功能有降级,且业务之间进行隔离部署,没有造成很大的影响。所以,发现问题一定要即时修改。
二是说,”之前的逻辑不是我写的,我就改了这些,那些代码我不太清楚“。这种说辞更不可接受,你连你改的代码上下文都不了解,你改什么代码,你能不改错么。所以,我要求大家,之前的代码不管谁写的,我不管了,但现在代码在你手上,你就必须负责把它写好,不能总是个临时方案,开个分支出去,最后代码一团浆糊。举个例子,上线新代码为了能实现快速降级,通常使用开关进行切换,这时系统里就会有新老两套代码,那老代码什么时候删除,如果当时写代码的人不负起责任,代码就那么放着,也不删,那后面不仅系统代码会越来越臃肿,而且代码的可读性也会越来越差。甚至不小心弄错了开关,还会出现意外的彩蛋什么的。
来到京东,今年(2019)是我的第 6 个 618,和往年一样,必须保障系统稳定,最后,以曾国藩的一句话作为文本的总结,”结硬寨,打呆仗“。
最后,感谢王洪涛对本文的校订。
评论 2 条评论