下面讲讲我在工作中踩过的坑和经验总结。
众所周知,搜索功能是很多电商网站的重要流量入口,用户在网站上购买东西时,都会先通过搜索来查找自己想要购买的商品。所以,搜索功能往往是电商网站至关重要的功能。
某跨境电商网站曾出现一次搜索功能挂掉的情况。某天,该电商网站突然接到大量的用户反馈,称该电商网站的主搜功能无法使用,通过关键字搜索并没有结果展现,导致交易量急剧下跌。同时,监控告警提示该电商网站的交易量下跌、搜索展现下跌等。
该电商网站的开发人员第一时间排查问题,发现在几分钟前有一次代码发布,在快速回滚后功能恢复。他们通过代码审查,发现在本地发布中只有一行代码改动,即在数据的分页查询中,某开发人员将pageSize从10改成了50。按理说这种小的改动不会影响到搜索功能,但一位有经验的开发人员第一时间指出了问题:由于某种原因,在代码中有一个默认设置:当搜索结果大于等于50的时候,就默认不返回任何结果。
导致该故障的因素有很多,下面进行简单分析。
◎ 这个默认设置当初是一个“临时方案”,但是一直没被改过。这就是技术债的问题。
◎ 如果不仔细阅读代码,就无法发现这个默认设置,因为这个故障在任意文档中都没有记载,并且没有被其他开发人员提到,更重要的是,在上线前未经过代码审查。这就是人、流程与文档之间的博弈问题。
◎ 在代码上线之前未经过测试。开发人员认为这只是一个很小的改动,不会有什么问题,所以就直接发布了。这就是质量意识淡薄的问题,也恰巧印证了墨菲定律:未经过测试的功能往往存在问题。
在众多线上故障中,有一类故障比较特殊,故障的起因可能来源于外部,也很可能是我们依赖的某个不重要的系统出现了异常,导致应用出现线上故障。这就是典型的“蝴蝶效应”。
例如,在某个故障中,某网站的用户反馈网站响应变慢,甚至出现响应码为500的服务器异常错误,线上机器大量告警,出现频繁的CMS GC。开发人员第一时间对机器的堆栈进行Dump(转储),发现ThreadLocal中的一个对象占用了大量的内存。在分析代码时,他们发现在ThreadLocal中保存了一个Map作为本地缓存,在这个Map中保存了很多缓存对象,每次在读取缓存数据时,都会先从 ThreadLocal 中获取这个对象实例。在需要向缓存中保存数据时,就直接向这个ThreadLocal中的Map保存数据,导致这个Map越来越大。
这其实并不是导致频繁CMS GC的关键,因为在这个故障发生之前,线上环境平稳运行了很长一段时间。那么,到底是什么导致了频繁的CMS GC呢?
经过开发团队的辛苦排查,结果很是让人出乎意料:竟然是在应用中依赖的一个外部系统突然变慢,导致本应用的功能出现异常。由于外部系统响应变慢,请求外部系统的时间就会变长,所以在同一时刻存活的线程数变多了。由于每个线程都复制了一份ThreadLocal的数据,所以,ThreadLocal占用的内存也变多了,这就发生了CMS GC;又由于外部系统一直没有好转,线程数越来越多,所以发生了频繁的CMSGC。
这就是典型的“蝴蝶效应”导致的故障:一个不重要的系统性能变差,触发了另一个系统发生Bug,从而导致严重的线上故障。
广大Java工程师对HashMap再熟悉不过了,HashMap是非线程安全的,也是Java面试中的必考题目。但是,HashMap在并发场景下出现的问题在很多公司和技术团队中都发生过,甚至在淘宝早期就发生过因为在并发场景下使用HashMap导致CPU被打满的线上故障。
阿里巴巴的研究员毕玄在自己的博客中曾经记录过这样一个案例:某天中午,开发人员收到大量的线上告警,提示某些机器的负载超过了500。开发人员便赶紧登录线上机器,使用top命令查看了CPU的情况,发现CPU消耗接近100%,于是又通过shift h命令查看线程的情况,发现并没有CPU消耗较高的线程,消耗量基本在0.7%左右,但线程总数看起来很多。于是,开发人员通过ps-eLf|grep java-c命令统计了线程总数,发现一共有700多个线程,所以又连续执行了几个jstack命令查看线程堆栈,发现其中有600个线程处于RUNNABLE状态,并且都在执行HashMap的get方法。根据经验,基本可以断定问题是由于在并发场景下错误使用HashMap导致的。
在该案例中,开发人员根据堆栈信息找到了相应的出问题的类名,然后找到了相应的jar,用jd-gui反编译看了下相应的代码,发现在代码中使用此HashMap的方式如下:
程序员架构修炼:踩过的坑和经验总结、故障复盘流程及模板
堆栈中的信息显示有 600 个线程在执行 caches.get(key)。之前之所以在这个地方使用HashMap,并且没有加锁,是认为load方法只会在Spring初始化当前这个bean的时候执行一次,但不幸的是随着业务的发展,这个load方法在数据变更时会被触发,可能出现向Cache里放东西的时候,而其他线程在取东西,这就直接导致了并发问题。关于HashMap的死循环问题,可以搜索相关资料了解细节。
当然,出现这个CPU消耗高的现象不是HashMap的问题,还是使用的问题,因此对这个问题的修复也很容易,就是用ConcurrentHashMap来替换HashMap。
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
“high cpu usage in hashmap.get” 是 在 Google 上 搜 索“hashmap”时出现的热词,可见这个错误很常见,比如 Velocity、Hessian 都出现过类似的错误。Hessian 老版本中的类似Bug可以参见http:
//bugs.caucho.com/view.php?id=1588;Velocity老版本中的类似Bug可以参见https://issues.apache.org/jira/browse/VELOCITY-718。
在以上案例中之所以出现故障,主要还是由于技术债导致的。由于在方案设计初期没有考虑到业务发展可能带来的变化,在无意中引入了“临时方案”,就欠下了技术债,进而导致故障的发生。
在软件行业,一次线上故障的处理流程主要分为三个大方面:故障发现、故障处理及故障复盘,如下图所示。
故障发现和故障处理是很多公司都比较重视的两个环节,故障复盘却被经常忽视,本节就来聊聊和故障复盘有关的事情。
“复盘”原是围棋术语,本意是对弈者在下完一盘棋之后,重新在棋盘上把对弈过程摆一遍,看看哪些地方下得好,哪些地方下得不好,哪些地方可以有不同甚至更好的下法等。这种把对弈过程还原并且进行研讨、分析的过程,就是复盘。“故障复盘”也是如上所述的过程,只不过不是针对棋局,而是针对一次“血淋淋”的线上故障。故障复盘会对故障处理的整个流程做一个“回放”,有利于我们深入分析和总结在故障处理的过程中哪里做得好,哪里做得不好,有哪些地方可以有更好的做法等。
很多公司和团队都不重视故障复盘,究其原因主要有两点:对故障复盘有误解;不知道故障复盘的作用及重要性。
说到故障,就一定会有定责,不管在什么样的公司,这都是比较敏感的话题。发生一次线上故障,小则影响开发人员的绩效考核,大则可能导致高层引咎辞职,所以很多人在解决故障后都会下意识地抗拒重新提起发生故障的细节,尤其是和本次故障有直接关系的人或者团队。这其中的最主要原因是很多人对故障复盘存在误解,认为故障复盘的目的就是“分锅”和“甩锅”。不可否认,自我反思与相互批评是故障复盘中很重要的一个环节,但是,这并不是故障复盘的最终目的。故障复盘的最终目的其实是通过对已经产生的故障的反思,进行总结及优化。
笔者认为,故障复盘主要有如下目的。
◎ 对已发生的故障进行总结,避免再次出现同样和类似的问题。
◎ 找出团队的强弱项,优化人员分工和办事流程。
◎ 尝试找到更好的问题解决办法。
◎ 进行经验总结,分享沉淀,引起大家的广泛重视。
前面介绍过故障复盘的目的和重要性,接下来看看如何进行一次有效的线上故障复盘,主要通过故障复盘的要求及流程进行展开。
1.故障复盘的要求
故障复盘是故障处理中至关重要的环节,我们对其自然不能应付了事。一次规范的故障复盘需要满足如下6个要求。
◎ 故障复盘要及时,复盘时间距离故障解决的时间最好不要超过一周。
◎ 在故障复盘之前,相关人员应对本次故障的背景有基本了解。
◎ 在复盘过程中不批评、不表扬,就事论事,只陈述事实。
◎ 在故障复盘时要有完整的过程回溯,例如为什么会发生、第一时间如何解决等。
◎ 在故障复盘时对故障进行深层次的剖析,例如是否是流程存在问题、是否有更好的解决方案等。
◎ 在复盘后要有内容产出,例如故障复盘文档、复盘通知邮件、切实可行的行动(后续行动点)及行动点验收方案等。
阿里巴巴高级运维专家胡杨曾经总结过一套“RASA”理论,RASA即 Review ( 回 顾 ) 、 Analyze ( 分 析 ) 、 Summary ( 总 结 ) 和Action(行动),如下图所示。该理论和以上介绍的故障复盘的要求不谋而合,都关注过程的回溯、深层次的剖析、经验总结及后续行动点的跟进。
2 故障复盘的流程
前面介绍了一次规范的故障复盘需要满足的几点要求,那么在一次完整的故障复盘过程中都需要做哪些事情呢?
故障复盘一般包含如下几个流程:故障记录、故障分析、复盘会议、行动点制定和行动点验收等,如下图所示。
1)故障记录
故障记录一般由责任开发或者测试人员负责,有些公司可能由专门处理故障的服务支持人员负责。不管是谁来负责记录故障,都需要保证信息尽可能客观、准确,这样才能在后续的复盘过程中提供准确的信息。
故障记录主要包含如下内容:故障发生时间、故障描述、影响范围、主要模块、相关责任人、故障处理人员、故障处理时效、故障的当前状态、故障解决时间、故障解决方案、故障原因、故障定级等。
在故障记录过程中,有一个比较关键的步骤就是进行故障定级。
故障定级根据不同的业务有着不同的指标,通常可以参考的指标有:
影响用户数、故障恢复时间、用户投诉数、资金损失数、模块重要性等。
不同的公司对故障的级别有不同的定义,一般会有 P1、P2、P3、P4等几类故障,这里的P是Priority Level的缩写。如下表所示是钉钉公开的开发文档中关于《应用故障定级标准》中故障等级的描述(参见https://open
doc.dingtalk.com/microapp/operations/hn94hh)。
本文给大家讲解的内容是程序员架构修炼: 踩过的坑和经验总结、故障复盘流程及模板;
可以看出,对于一个核心模块的功能,可根据用户操作失败率、每分钟失败量及影响时间等条件划分出4个等级。
有了故障定级,就可以针对不同级别的故障确定需要哪些人关注此次故障、故障规定处理时效,以及后续复盘会议参与人员及在故障定责时确定奖罚等级等。
2)故障分析
故障分析主要由故障相关责任人、故障处理人员等负责,旨在深入分析故障发生的根本原因、在故障解决过程中是否有可优化的步骤等。
在这个过程中,分析人员需要不断提出5W1H等相关问题,例如:
◎ 是什么原因导致了本次故障(Why)?
◎ 故障发生在什么业务、模块和功能点中(What)?
◎ 故障是在什么时间发生的、开发人员在什么时间开始接入的,
以及在什么时候恢复的(When)?
◎ 故 障 发 生 在 什 么 环 境 下 、 对 哪 些 地 区 的 用 户 有 影 响(Where)?
◎ 本次故障是谁发现的、谁解决的,以及谁该负主要责任(Who)?◎ 故 障 是 怎 么 定 位 的 、 怎 么 解 决 的 , 有 没 有 更 好 的 办 法(How)?
通过不断追问,抽丝剥茧,洞察故障本质。
3)复盘会议
故障复盘会议通常由故障台工作人员或者故障责任团队牵头,邀请相关的开发负责人、质量负责人、产品负责人甚至运营负责人一起进行。在复盘会议上需要对本次故障及故障处理过程进行总结、讨论及反思。在某些情况下还会进行故障定性、故障定责等。
根据故障发生的模块、发生的原因等可以对故障进行定性和定责,即确定本次故障的性质、需要对本次故障负责的相关人员或者团队等。在定性和定责时需要明确出哪些个人(团队)对本次故障承担主要责任,哪些个人(团队)对本次故障承担次要责任,定责过程要公平、公正、公开,做到权责一致、边界清晰。
4)行动点制定
在故障复盘会议中,除了要对故障进行分析和总结,一件必不可少的事情就是执行后续行动点。根据具体的故障,行动点可能有多种,比如代码优化、流程优化甚至组织架构优化等。但无论具体的行动点是什么,制定的行动点都需要满足如下条件。
◎ 必须是明确的项,要有明确的行动点,不能是大而空的规划。
◎ 必须可落地,能够切实改善既有问题。
◎ 必须可验收,有明确的验收标准。
◎ 必须承诺验收时间,并且要在原则上按照承诺的时间完成。
5)行动点验收在执行行动点之后,需要进行定期验收,还要对行动点周期性地跟进和确认。在验收时,要严格对照验收标准,确保行动点真实、可靠地落地。
前面介绍了什么是故障复盘、为什么做故障复盘,以及如何做故障复盘等内容,相信读者已经对故障复盘有了一些基本了解。接下来会给出一份故障复盘模板,如下表所示。故障复盘模板,其实就是在故障复盘会议上需要大家分析、讨论及总结的内容模板,也可以称之为故障复盘记录模板。
需要注意的是,故障复盘记录和前面介绍过的故障记录并不是一回事,故障记录指的是在故障发生之后对该故障跟进内容的相关记录,故障复盘记录则指的是复盘会议中的会议记录。
给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。
文档地址:一篇神文就把java多线程,锁,JMM,JUC和高并发设计模式讲明白了
码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!