回顾一下
大约一周前,更新过一次线上,上了分享相册终端页,第二天新鲜事入口一打开,从早上开始就一直开始挂机器,起初认为是压力问题,后来加机器到了快晚上7点的时候,几乎大面积的挂掉,没办法,只能回滚。通过分析,发现是压力测试不到位,因为压力测试不够真实,不够真实是因为数据不够随机,导致缓存为服务减少了压力。
第二天亮哥写了一个足够随机的取数据算法,把算法移植到压测代码上后,进行压测,大概也是夜里7点左右进行,第一次压测不到5分钟就挂了,从现象上看是内存耗尽,FGC频繁。第二次压测大概进行了20分钟左右,又挂了。挂了代表压测足够真实。通过监控工具jconsole发现old区域的曲线一直在往上涨,涨到一定的时候,就挂了。第三次压测大概也进行了20分钟,也挂了,挂了的原因是亮哥无意间用Jstack抓了一个栈,曲线和第二次相似,自己百思不得其解。因为有GC,理论上不会让它一直只涨不跌的。初步认为是什么地方内存发生了泄漏。
第三天就在我还在纠结于内存泄漏这个问题的时候,亮哥从昨晚的堆栈log里找到了答案,堆栈里充满了大量的ReentrantLock,这东西看似不起眼的,有Lock但是栈里却没有Blocked,都被人忽略了。但是跟进代码里一看这把lock锁住了四部对DB的操作,3次查询一次插入。亮哥对我说先把lock干掉,这把锁有问题,代码不能这样写。
@Override
public void save(ShareMap shareMap, int userId) {
lock.lock();
if (findMapId(shareMap.getShareId(), shareMap.getReferId(), userId) == 0){
shareMap.setMapId(idSequenceDAO.nextId(IdSequenceDAO.SHARE_ID));
shareMap.setId(idSequenceDAO.nextId(IdSequenceDAO.SHARE_MAP_ID));
shareMapDAO.insert(shareMap, userId);
}
lock.unlock();
}
因为这把锁在并发量大的时候,完全可以造成无数的线程在这里造成一个临时性的Blocked。为了证明这个问题,我们进行了再一次的压测。这次我们压测从周五下午开始,刚开始一台机器,从监控曲线上看,old区域的内存一直在增加,直到它达到自己的最大内存时,突然一根直线下来降到最大内存的百分之30左右,看到这根曲线我就知道服务正常了。为了达到前一天夜里时候的压测压力,又加了一台机器的流量进行。这次压测我们的代码坚持到了周一我手动把压测服务给杀掉了。通过这次压测,几乎可以确认就是这把lock导致的线上服务大规模的瘫痪。
第一次压测学到的东西:
1、Lock的范围,粒度,以及该不该使用Lock。虽然说这把Lock加上是为了保证下面insert里的某些东西的唯一性,但是为一个概率很小的可能性事件消耗了线上服务的质量,真不值得。
2、服务器挂掉的原因,服务堵了,服务虽然没有Block,但是大大降低了并发效率,导致服务消耗了大量的内存在等待线程排队,使old区域的内存只涨不跌,直到挂掉。
3、知道JVM里面内存分布,内存的分代,知道了什么事FGC,什么是YGC。FGC对服务的影响。
压测成功了,我兴致满满的提测了,测试通过后,就急于更新线上的服务,在我觉得更完了,没事的时候,服务又挂了,这次几乎是一台接一台的挂掉,这次我傻了,心想优化了代码死的还更惨,一头雾水的我真不知掉咋办。想想压测白做了?我承认我当时很低落。低落的不敢看一眼流量图,这让我产生了一种挫败感。
那天夜里我在临近一台机器快要挂掉的时候,用Jstack抓了一个线程堆栈的log,接着回滚线上,然后在抓了一个老版本的jstack log 。
第二天早上一来,我试着对比了两个log。发现新代码的log里充满着大量的findShareMap方法,在往上看就是
com.mysql.jdbc.util.ReadAheadInputStream。我统计了一下com.mysql.jdbc.util.ReadAheadInputStream的行数发现岂有400多行,再对比了一下老代码中的com.mysql.jdbc.util.ReadAheadInputStream,发现才25,我觉得这是一个线索,顺着这条线索我统计了一下findShareMap的方法,发现岂有惊人的1100多行。给我第一感觉就是在等待查表,我看了看代码,发现那条语句就是直接调用DAO的,没有任何其他逻辑,于是我查了查表中的索引,发现这条SQL的查询语句条件居然不是一个索引。我想初步的原因可能已经找到,但是我觉得这仍然不能解释为什么新鲜事路口不开放挂的比上次还快,还恐怖。带着这个疑问,先让DB给我把索引加上了之后,我又进行了一次压测,这次测得不是翻页,而是分享,我用了线上一台机器上的流量直接切到测试机。然后我也没监控了,直接查看log中的findShareMap,这时几乎都在10以下。我觉得问题应该解决了。应该就是少了一个索引导致的线上挂掉。但是这次我仍然不是非常肯定,最后更新一台一台的上去。更一台观察一台,直到所有的都更完。夜里观察了一下高峰的压力。很明显进流量大了很多。由此就解决了我心中的疑问,新鲜事虽然没放开路口,但是仍然有人从列表入口进入查看分享相册。随着分享传播越来越多。量也越来越大。从而不开新鲜事口也可以让线上服务挂掉。
com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:188)
- locked <0x00002aab08030860> (a com.mysql.jdbc.util.ReadAheadInputStream)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2452)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2906)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2895)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3438)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1951)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2101)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2554)
- locked <0x00002aab08030a40> (a java.lang.Object)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1761)
at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1912)
- locked <0x00002aab08030a40> (a java.lang.Object)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:93)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:648)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:591)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:641)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:670)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:678)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:710)
at net.paoding.rose.jade.provider.jdbc.JdbcImpl.query(JdbcImpl.java:63)
at net.paoding.rose.jade.provider.jdbc.JdbcDataAccess.select(JdbcDataAccess.java:79)
at net.paoding.rose.jade.core.SQLThreadLocalWrapper.select(SQLThreadLocalWrapper.java:48)
at net.paoding.rose.jade.core.SelectOperation.execute(SelectOperation.java:66)
at net.paoding.rose.jade.core.JadeDaoInvocationHandler.invoke(JadeDaoInvocationHandler.java:128)
at $Proxy45.findShareMap(Unknown Source)
at com.renren.app.share.biz.impl.ShareMapBizImpl.findShareMap(ShareMapBizImpl.java:75)
第二次压测学到的东西:
1、心态,遇到问题一定要冷静,淡定。这样才能分析原因。
2、Jstack的使用,分析。
3、线上挂掉的原因总结,查问题的思路
4、更新前先做好策略,应对突发情况。
分享到:
相关推荐
百度近期算法更新总结:收录和反链暴涨.pdf
SY4109-2013更新总结[参照].pdf
他汀预防缺血性卒中-TIA中国专家共识更新总结.ppt
主要介绍了详解Android app自动更新总结(已适配9.0),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
INS更新算法总结pdf+xmind
关于定期安全性更新报告总结归纳实习调研报告总结归纳工作总结报告总结归纳.pdf
教师更新观念学习总结工作总结.docx
Unity项目热更新方案性能分析总结.Unity项目热更新方案性能分析总结.对于项目要热更新来说很有用,不错的文档。
2019更新工作总结打包4篇打包.zip
总结 部署 Windows 10 移动企业版和 Windows 10 IoT Mobile 的更新 使用适用于企业的 Windows 更新部署更新 配置适用于企业的 Windows 更新 将适用于企业的 Windows 更新与管理解决方案集成 演练:使用组策略...
(1) 掌握SQL数据更新语句的基本使用方法,如UPDATE、DELETE、INSERT。 (2) 掌握更新语句条件中的嵌套查询使用方法。 (3) 掌握SQL视图语句的基本使用方法,如CREATE VIEW、DROP VIEW。 (4) 掌握视图更新、...
Unity项目热更新方案性能分析总结.Unity项目热更新方案性能分析总结.Unity项目热更新方案性能分析总结.
推荐系统的总结,后面会持续更新。 推荐概念 信息过滤系统解决信息过载用户需求不明确的问题 * 利用一定的规则将物品排序展示给需求不明确的用户 推荐搜索区别 * 推荐个性化较强,用户被动的接受,希望能够提供持续...
更新-小学二年级体育教学工作总结-表格-(含教学总结、德育总结、业务学习总结、安全总结、综合实践).pdf
指针数组与数组指针总结更新
企业-华峰超纤-2020年年终总结(更新后).rar
主要介绍了C#子线程更新UI控件的方法,在桌面应用程序中控制UI界面有着不错的实用价值,需要的朋友可以参考下
oracle使用 merge 更新或插入数据(总结)
子线程中更新UI的三种方法和获取message的两种方法总结