起因
刚从前几天的修改配置导致服务假死中缓过劲来,我还在享受着周日の甜蜜美梦。突然服务监控传来噩耗,说是我们订单服务宕机了。哦豁,一看时间早上八点零五。不过这些事情我当时并不是知道,因为那天手机关机了(这不是重点),后来在下午分析了一下问题出现的原因。我们又遇到了缓存穿透。这让我想起了Q1那噩梦的一天,还好这次只是单个服务宕机,冷静下来我们开始追溯事情发生的原因。
订单服务只是部署在两台机器(我们的量你懂的。没啥请求),其负责的内容则是查询订单信息和下单等业务,早上八点的宕机运维告诉我们是由于一条慢SQL重复执行多次,导致占满了链接池链接,继而其他的API要执行查询操作时拿不到DB的链接,最终恶性循环,服务的连接数达到了上线,最终处于假死状态,新来的请求不再有返回结果,服务处于不可用状态。(真就得了慢SQL,一个传染两)
而造成这个慢sql堆积的原因则是APP首页的缓存失效了,导致请求查询不到缓存,继而缓存穿透最终直接通过内部请求查询DB。加之SQL本身也存在问题,没有合理的索引和大表之间的Join导致执行时间几乎在分钟级别。
我们只是紧急的上了慢SQL的缓存,加大了缓存失效的时间,做完这一系列操作之后我们意识到不能再让服务裸奔了,得有完善的监控和对错误的良好FeedBack。的确,目前缺少SQL级别的服务监控。
解决方式
Druid监控
想要解决慢SQL得先知道哪些SQL慢,所以第一步得先增加对链接的监控
项目加入Duridweb的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
在DataSource的配置中开启DruidMonitor:
DataSourceFactoryBean factoryBean = new DataSourceFactoryBean();
Properties dsProps = new Properties(){{
setProperty("druidMonitor","true");
}};
factoryBean.setDsProps(dsProps);
这样服务启动之后druid监控也随之启动,这里有更多详细关于数据源的配置就不描述了。访问服务Ip+port/druid/index.html 可以看到有哪些sql慢。但是作为一个提供API服务的应用,我们更在乎uri的响应和uri对应方法执行SQL的快慢,当在一个事务中执行的时候,随着扫描行数的走高,最终形成慢API,而这些接口可能是达到某些条件之后才会扫描那么多行。但是人工查询很吃对接口的了解程度,有些接口业务逻辑复杂,容易出现一些晦涩难懂的桥段,但是我们只是优化SQL速度的,并不关心业务流程(针对查询),所以我们需要一个工具来帮助我们分析和修改。
Arthas
这里使用到一款阿里的Java诊断工具,Github:arthas,而SQL是执行慢,所以我们主要使用 trace 命令来寻找低速的代码段,有关trace的详细介绍:https://alibaba.github.io/arthas/trace.html
在服务器wget一下子,启动已java -jar的方式启动,他会自动扫描在这台服务器上运行的java应用,然后ps -ef 找到自己的java应用的pid 然后选择进入。
然后就可以使用trace命令来查看到底是哪块的代码执行缓慢进而修改程序来优化。
最后
修改慢SQL是一件非常费劲的事情,在修改之前得理解之前的业务逻辑,在参数相同的情况使用不同的查询方式产生相同的结果,或者是合理建立索引(当然缓慢的SQL大部分情况下都是不走索引导致扫描全表而带来的缓慢),亦或者从流程上逐渐缩小查询的集合大小。修改需要时间,健壮的服务离不开合理的、符合设计规范的程序和DB。