引言
前段时间公司发生了一次故障给公司造成重大损失,所在部门多人受到严重处罚。通过后来故障分析发现如果稳定性工作到位的话,这个故障在多个环节都可以发现避免的。思来想去就将一些经验和曾经踩过的坑分享给大家。
本文主要分故障发生前期准备、故障发生时的问题发现定位、故障恢复三个部分,重点从稳定性方面来阐述,有些观点会比较偏激,请轻喷。同时也欢迎大家一起来完善《不挨刀手册》。
技术选型
技术选型关系到一个项目的成败,如果没有选好,除了可能导致项目延期、后期维护工作量巨大外,还会为项目带来未知风险。
很多同学喜欢用新出技术框架或者新版本,喜欢尝试新事物是好事,但是也带来未知的东西( 比如:API不熟悉导致误用、 不熟悉其缺点以及副作用、存在一些未知Bug、与现有技术兼容性)。就好比你正学着《华山剑法》、《独孤九剑》,突然新发现有一本《葵花宝典》,怎么看怎么腻害,然后就开始学,学着学着就翻到一页上写着 “欲练此功,必先自宫” ,是不是就傻眼了。
所以在满足当前需求并预留扩展空间的情况下,尽量选择成熟稳定、文档资料齐全的技术。 否则出了问题,查资料的地方都没有,总不能每次出问题都请团队的大神出马吧( ̄▽ ̄)"~
系统设计
系统设计经常步入的一个误区就是过度设计,为了技术而技术,为了设计而设计。并不考虑这样设计带来的成本和风险。
过度的设计
或者
再比如:
1.项目刚启动的时候设计一个宏伟的蓝图,把完整的功能拆分到N个工程、N个微服务里面,
每次发布新功能需要N个工程提交、编译、发布,当出现性能瓶颈或者Bug时,调试就是
一件既费时又痛苦的事。
2.还有简单一两个类能完成的功能恨不得把23中设计模式全部用进去,既增加自己的工作量
有给后来人增加阅读困难,曾经遇到过一个“大神”写的代码,那个代码层次之深,设计之
复杂以至于他的代码后面一两年没人敢动,需要加功能全部绕开他的代码。
3.像几百万的数据就分表1000多张,给后面做查询、导出和排查问题增加不必要的工作量。
系统都是迭代设计出来的,没有通吃的标准架构,也没法一步到位,我们要因地制宜,层层递进,不断优化,最后才是适合自己的架构。特别对于我们公司,一般来说项目时间都是比较紧张的,先快速迭代满足需求+后期重构,大多情况下也是小于过度设计所消耗开发工作量的。
江湖中这门武功就很过度设计,杀敌一千自损八百。
外部依赖
“高内聚、低耦合” 这句话在学习面向对象的时候是不是耳朵听出茧了( ̄▽ ̄)"~ ,在应用依赖方面这一句也是真理。我们每多接入一个外部依赖,系统就多一份故障的危险。
比如你的系统需要缓存一些数据,而这些数据是临时缓存下不需要分布式存储,那么引入分布式缓存技术就不是一个很内聚的方案。
像杨过和小龙女一起修炼的《玉女心经》,在一起时很厉害,分开就容易被人抓住,所以还是《黯然销魂掌》来的靠谱。而有些服务我们是必须要依赖的,这时候我们要怎么处理呢?其实也很简单,那就是不相信它。
1.在调用它前需要对入参进行严格校验(虽然它自己有校验,但是我们不能相信他,
另外请不要介意校验的性能损耗),以防止服务崩溃或者返回非预期的结果。
2.服务设置超时,如Dubbo超时、HttpClient超时、或者线程执行超时,避免把自己Hang住。
3.对执行结果进行校验,以防止执行结果对后续操作产生不良影响。
外部依赖调用示例:
这里说一个我曾经遇到的坑吧,当时调一个商品查询服务,参数就是一个商品ID,结果由于页面没做判空校验,我服务端也没有做校验,于是空的参数进行查询,而对方也没做校验。
上面是对方服务的SqlMap,结果可想而知,我拖库了( ̄▽ ̄)"~,他们挂了。后来庆幸他们是查询接口,而不是删除接口。如果是删除接口估计我现在已经不在阿里了。
当自己作为被依赖方需要怎么做呢,来,我们回忆一下接口设计的六大原则,有没有想起来第一条是什么?单一职责原则,为什么单一职责放在第一条,因为你永远不知道你的用户在用你的接口做什么。
如:以前我在监控团队设计的报警功能,很多用户故意配置很多报警,
再把报警汇到一起当报表大盘使用,这就增加服务器压力很容易把自己搞挂。
最后自己的服务要设计的足够健壮,考虑各种边界和异常条件。
比如: 你提供了一个肉夹馍的服务,当用户来购买时你除了考虑正常购买的用户还需要考虑
1.购买0.32个肉夹馍
2.购买100000个肉夹馍
3.买一个肉夹馍 加点辣
4.买一个肉夹馍 不要肉 不要夹 不要馍
基础技术
在基础技术方面,除了熟练掌握开发语言的语法外,还需要了解常见的坑,只简单列了10条,更多的大家可以自行搜索。
1.HashMap、HashSet、ArrayList、SimpleDateFormat等线程不安全类,进行并发读写。
2.ArrayList等集合在迭代的过程中移除元素
3.HashMap、HashSet等集合类,添加元素后,修改元素内容影响Hash值,导致equals不相等。
4.用==代替equals
5.float double用于精确计算,int 溢出
6.异常处理不彻底,重复包装异常
7.频繁创建线程而非用线程池。
8.数据流Stream未进行缓存和关闭
9.并发解锁、计数器、流关闭未在finally里面第一行执行,finalize方法滥用。
10.不必要的同步
最后需要补充一点,在绝大多数情况下我们需要的是 易读可维护的代码,而不是性能,避免过早、过度优化。
比如:
1.很多同学纠结字符串拼接用StringBuffer、StringBuilder 哪一个好,
我的建议是直接用+号来拼接带来更好的可读性,这里会有些性能损失,多几个微秒,
但是你的CPU利用率超过5%了么?
2.还有一些遍历集合的方式,会提高那么一点点性能,或者减少遍历对象,但是我们的代码就会比较晦涩了。
3.特别复杂的lambda表达式。
从大多应用目前CPU利用率情况来看,我们优化的重点放在IO、网络、数据库读写上,投入产出比会更可观,如果一味的揪着代码的性能不放,每年为公司节省下来的钱可能还不够发给你发一天的工资呢( ̄▽ ̄)"~
最后我们要稳定成熟的技术、尽可能少的依赖、没有过度的设计和炫酷的秀技、扎实的基础,给人相貌平平的感觉就对了( ̄▽ ̄)"~
系统运维
1.严格遵守 各自团队的运维红线,任何时候都不能心存侥幸,以为自己是猪脚光环加持,殊不知自己是那个活不过三集的小喽啰 (保持敬畏之心!)。
2.团队内相互补位,帮助其他同学CodeReview和进行充分测试
套用魔兽中女巫的一句话:Help Me Help You !,好像暴露年龄了~~~~
3.其他注意事项
1.UI设计上 相反的操作尽可能分开,比如:添加、删除不在同一个页面 或者删除需要进行二次确认。
2.关键数据只能逻辑删除,而非物理删除,并提供恢复机制。
3.线上、线下多窗口操作迷失,每次操作前进行二次确认或者高亮提示。
4.自动化、智能化的坑。在数据准确性、流程确定性、以及反馈机制未建立之前过早的进行自动化、智能化。
5.灰度、灰度、灰度
6.很多同学忽视规章制度,认为就是走走形式,其实他的作用远超你想想。
1.首先掌握常用的问题排查工具,如:jstack、jmap、arthas、jvisualvm、jconsole等
2.配备常用的监控工具 如xxxxAPM(书到用时方恨少,故障来时方狠监控少)
java监控需要有最常用的 JVM、Spring方法调用、URL、SQL、异常等多种监控指标。
另外还需要线程诊断能够统计服务的长尾,线程的分类数量,死锁和繁忙等情况
最后:还记得我们系统需要高内聚吧,所以万一这些监控系统都挂掉怎么办?
这里就要为系统的一些关键指标做自监控,自监控的形式很多可以是API、
也可以是页面、也可以是指定的日志。
故障恢复是最难的,当我们准备充足时它也就是最简单的了。
1.执行任何操作前,都制定相应的回滚方案,有条件的情况下回滚方案也需要进行演练。
2.高容灾设计,能够主备切换。
3.信息同步(通知TL、值班长) > 回滚恢复(如:机房出问题先切流,应用出问题先回滚) > 问题定位
4.系统运维以及设计相关操作都需要满足幂等、无状态、面向错误设计、面向终态设计
本文写的比较仓促,有的没有详细展开,也有遗漏的,后面有想起来再补充