本文转载自微信公众号“小姐姐的味道”,作者姐姐养的狗。转载本文请联系味觉小姐公众号。部门来了个新架构师,BAT出身,家住三环,开宝马上班,有车位。这家伙话不多,但一旦说出口,便有着不可动摇的自信。原因是有上亿的高并发经验,每一秒的请求都是其他跑了一年的公司无法企及的。这就很让人羡慕了,毕竟他靠这个挣的钱比我多。俗话说,要想在公司不出事故,就不要写代码。工作太多容易出事故,谁也顾不得轻松。这就是现实。但有时一切都与结果有关。新的研发负责人不懂技术,但是他懂技术指标,所以统计了大家的git提交次数。如果git的活跃度和A股一样绿,那就算是及格了。想来想去,架构师决定取一个并发最高的需求:统计接口的平均响应时间和自启动以来的请求数。为什么说它的并发度高呢?这是因为它统计了所有的接口,自然大于每个接口的请求量。一包AOP代码,每个接口都要从他那里绕过去。是时候让我们的架构师上场了。代码如图所示。架构师说我的代码不需要注释。所谓注释,就是针对垃圾代码的。我想是的,他显然受到了Netflix的影响。考虑到高并发场景,程序使用了一个线程安全的ConcurrentHashMap,然后每次通过监听key获取对应的数据,然后进行value自增。这么简单的代码不需要加任何注释。作为项目中并发度最高的代码,出于对资深架构师的信任,我们不需要做任何代码审查和测试。大家都很忙,码你,上网走走。我建议你先找代码的问题,如果你找到了问题,你比架构师强;如果你没有找到,并不能证明你比架构师弱,没有什么好难过的。下面插一张图,挡住你的思路。安装B被雷击,在线运行一段时间后内存溢出。大家争论不休。毕竟xjjdog说内存溢出问题的排查周期很长,平均需要40天左右才能解决。大家开始演示的时候,架构师偷偷启动了EclipseMAT。MAT非常适合分析内存问题,但前提是你需要摆弄堆栈。架构师可以用jmap,最重要的是权限大,所以自己做了一份,离线分析。我能理解他的心情。毕竟,定位到自己代码中的问题,并不是什么值得高兴的事情。他发现内存堆里全是MonitorKey和MonitorValue。Monitor$MonitorKey@15aeb7ab我和架构师的关系很好,所以他问我:我们有很多接口吗?我说:不行,别看访问量这么大,这么狗屁的生意能有多少接口?数百人持续了一天。他说:我在一堆里发现了好几千万个……说完他就不说话了,因为他发现很多都是一样的界面。肯定是因为参数的问题,所以他在代码中加入了这个,把?后面的部分截掉了。key=key.split("\\?")[0];结果公布在网上,过一会内存又溢出了。这次终于引起了大牛们的注意。经过大家的分析,发现是代码忘记重写MonitorKey的equals和hashCode方法了。我忍不住脸红了。作为好朋友,我不应该让他出丑。但是我隐隐感到高兴,因为他的工资比我高。所以这是一个大问题。很多同学对HashMap的知识点都答得很流利,甚至把红黑树也特意背了下来。可当他换种方式问出来的时候,却又是一头雾水。其中一个问题是:一个普通的对象可以作为HashMap的key吗?答案显然是可以的,但是需要注意重写hashCode和equals方法。如果忘记重写,很大概率会出现内存泄漏。遗憾的是,现实中遗忘的情况很多。Danielarchitects也将被招募。代码重写hashCode和equals方法后,线上没有内存溢出。等等,还没完。毕竟他是架构师,单凭这样的bug,并不能证明他的水平。架构师写的bug一定非比寻常。这种事情发生的多了,研发领导对技术的权威也不再那么冷淡了。我们决定从并发度最高的代码开始,进行代码审查。不幸的是,架构师的访问代码有问题。虽然问题不是很大,但毕竟是个问题。在统计数据的时候,代码使用了ConcurrentHashMap,但是没有用。在visit方法中,先取出key,然后判断为空,再插入value。这显然不是原子操作。线程1:获取keya的值线程2:获取keya的值线程1:a为null,生成ab线程2:a为null,生成ac线程1:savea=b线程2:savea=c这时,B丢失了。业务可以忍,严谨的技术专家忍不住,提出修改建议。架构师说给visit方法加个synchronized就够了。publicsynchronizedvoidvisit(Stringurl,Stringdesc,longtimeCost)我说不行。有一种更优雅的写法,而且效率更高。即使用putIfAbsent方法,代码改动如下:.totalTime.getAndAdd(时间成本);value.avgTime=value.totalTime.get()/value.count.get();大家对这两种方法争论不休。技术总监托着下巴想了半天,看着满脸通红的同学们,说道:这就是我不信任你们的原因。在线环境应尽可能稳定,变化最小。既然加个synchronized就能轻松解决问题,为什么不直接用呢?以下代码更改太大且有风险。导演这才转头对我说:这个BUG不一般。为了让大家吸取教训,大家对整个事故过程做一个回顾。把排错的过程和经验分享给大家,让大家按照这个简单的结构走下去。在日常工作中,我们也要尽量以结果为导向。不管我们用什么手段,只要我们能把事情做好。这就是这篇文章的由来。虚心受教,同时明白自己的薪水不会涨。如果你给我点赞或者友情,或许可以安慰我。作者简介:品味小姐姐(xjjdog)。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。我的个人微信xjjdog0,欢迎加好友进一步交流。
