解Bug之路-记一次中间件导致的慢SQL排查过程

发布者:上海IT外包来源:http://www.lanmon.net点击数:1037

我们的分库分表中心件在经由一年的沉淀之后,已经到了斗劲不变的阶段。并且经由线上压测的磨练,单台每秒可以实行1.7W条sql。但线上情形仍是有出乎我们料想的情形。有一个业务线反响,天天有几条sql有长达十几秒的超时。并且sql是主键更新或主键查询,更奇异的是出现超时的是不合的sql,似乎毫无纪律可寻,如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

一个值得注意的点,就是此业务只需一局部流量走我们的中心件,另一局部仍是直接走数据库的,而超时的sql只会在连中心件的时辰出现,如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

很较着,是引入了中心件之后导致的问题。

排查是否sql确实慢

由于数据库中心件只关怀sql,并没有记实对应应用的traceId,所以很难将对应的请乞降sql对应起来。在这里,我们先大抵的统计了在应用端超时的sql的类型是否会有超时的情形。 分析了日志,创造那段时辰所有的sql在往后端数据实行的时辰都只需0.5ms,很是的快。如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

看来是中心件和数据库之间的交互是正常的,那么继续排查线索。

探求超时纪律

由于斗劲难绑定对应请乞降中心件实行sql之间的关系,于是笔者就想着列出所有的非常情形,看看那时辰点是否有纪律,以排查一些批措置导致中心件机能降落的征象。下面是某几条超时sql业务方给出的信息:

业务起头时辰实行sql的应用ip业务实行耗时(s)

2018-12-24 09:45:24xx.xx.xx.24711.75  2018-12-24 12:06:10xx.xx.xx.24010.77  2018-12-24 12:07:19xx.xx.xx.13813.71  2018-12-24 22:43:07xx.xx.xx.24710.77  2018-12-24 22:43:04xx.xx.xx.24513.71

看上去貌似没什么纪律,慢sql存在于不合的应用ip之上,解除某台应用出问题的可能。 超不时辰从早上9点到晚上22点都有创造超时,解除了某个点集中机能降落的可能。

注意到一个微小的纪律

笔者不雅观不雅观察了一堆数据一段时辰,终于创造了一点小纪律,如下面两条所示:

业务起头时辰实行sql的应用ip业务实行耗时(s)

2018-12-24 22:43:07xx.xx.xx.24710.77  2018-12-24 22:43:04xx.xx.xx.24513.71

这两笔sql超时对应的时辰点挺接近的,一个是22:43:07,一个是22:43:04,中心只差了3s,然后与后面的业务实行耗时相加,创造更接近了,让我们重新清算下:

业务起头时辰实行sql的应用ip业务实行耗时(s)业务完成时辰(s)

2018-12-24 22:43:07xx.xx.xx.24710.7722:43:17.77  2018-12-24 22:43:04xx.xx.xx.24513.7122.43:17.71

创造这两笔业务虽然起头时辰不合,但确是同时完成的,这可能是个巧合,也可能是bug出现导致的成效。于是继续看下是否有这些纪律的慢sql,让业务又供给了比来的慢sql,创造这种征象虽然少,可是确实产生了不止一次。笔者俄然感受到,这绝对不是巧合。

由上述纪律导致的思虑

笔者联想到我们中心件有良多若干好多台,假设是中心件何处卡住的话,若是在那一霎时,有两台sql同时落到统一台的话,中心件先卡住,然后在中心件恢复的那一霎时,以0.5ms的速度实行完再前往就会导致这种征象。如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

固然了还有另一种可能,就是sql先以0.5ms的速度实行完,然后中心件何处卡住了,和上面的区别只是中心件卡的位置不合罢了,另一种可能如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

是否落到统一台中心件

线上一共4台中心件,在履历了一堆复杂线上日志捞取分析相对应之后,创造那两条sql确实落在了统一台中心件上。为了保证料想无误,又找了两条适宜此纪律的sql,同样的也落在统一台中心件上面,并且这两台中心件并不是统一台,解除某台机械有问题。如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

业务日志和中心件日志比拟力

在上述创造的根本上,又履历了各类日志分析对应之后,终于找到了耗时sql日志和业务日志对应的联络关系。然后创造一个关头信息。中心件在领受到sql时辰会打印一条日志,创造在应用发出sql到领受到sql还没来得及做后面的路由逻辑之前就差了10s摆布,然后sql实行到前往确是非常快速的,如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

检察对应中心件阿谁时辰点其它sql有无非常

笔者捞取了阿谁时辰点中心件的日志,创造除了这两条sql之外,其它sql都很正常,团体耗时都在1ms摆布,这又让笔者陷入了思虑之中。

再从日志中找信息

在对当前中心件的日志做了各类思虑各类分析之后,又创造一个诡异的点,创造在1s之内,措置慢sql对应的NIO线程的措置sql数目远远小于其它NIO线程。更进一步,创造在这1s的某个时辰点之前,慢sql地点的NIO线程完全不打印任何日志,如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

同时也创造两条sql都落在对应的Reactor-Thread-2的线程里面,再往前回溯,创造近10s内的线程都没有打印任何信息,仿佛什么都没措置。如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

感受离秘闻越来越近了。这边就很较着了,reactor线程被卡住了!

探求reactor线程为何被卡住

笔者继续顺藤摸瓜,斗劲了一下几个卡住的reactor线程末了打印的日志,创造这几条日志对应的sql都很快前往了,没什么非常。然后又斗劲了一下几个卡住的reactor线程恢复后打印出来的第一条sql,创造貌似它们经由过程路由解析起来都很慢,到达了1ms(正常是0.01ms),然后找出了其对应的sql,创造这几条sql都是150K摆布的巨细,按正常思绪,这消失的10s应该就是措置这150K的sql了,如下图所示:

解Bug之路-记一次中心件导致的慢SQL排查过程

为何措置150K的sql会耗时10s

排查是否是搜集问题

首先,这条sql在接入中心件之前就有,也就耗时0.5ms摆布。并且中心件在往数据库发送sql的过程中也是差不多的时辰。若是说搜集有问题的话,那么这段时辰应该会变长,此种情形暂不考虑。

排查是否是nio搜集措置代码的问题

笔者鉴于可能是中心件nio措置代码的问题,机关了同样的sql在线下停止复现,创造实行很快毫无压力。笔者一度思疑是线上情形的问题,traceroute了一下创造搜集根基和线下搭建的情形一样,都是APP机械直连中心件机械,MTU都是1500,中心也没任何路由。思绪一下又陷入了停滞。

柳暗花明

思虑好久无果之后。笔者感受排查一下是否是机关的场景有问题,俄然创造,线上是用的prepareStatement,而笔者在呼吁行里面用的是statement,两者是有区别的,prepare是按照select ?,?,?带参数的情势而statement直接是select 1,2,3如许的情势。

而在我们的中心件中,由于后端的数据库对使用prepareStatement的sql具有较大的机能晋升,我们也支撑了prepareStatement。并且为了可以复用本来的sql解析代码,我们会在领受到对应的sql和参数之后将其复原成不带?的sql算出路由到的数据库节点后,再将原始的带?的sql和参数prepare到对应的数据库,如下图所示:

重新机关prepareStatement场景

笔者重新机关了prepareStatement场景之后,创造在150K的sql下,确实耗时到达了10s,感受终于见到曙光了。

终极缘故缘由字符串拼接

由于是线下,在各类地方打日志之后,终于创造耗时就是在这个将带?的sql衬着为不带问号的sql上面。下面是代码示意:

String sql="select ?,?,?,?,?,?...?,?,?..."; for(int i=0 ; i < paramCount;i++){ sql = sql.replaceFirst("\\?",param[i]); } return sql;

这个replaceFirst在字符串特别大,必要替代的字符频率出现的特别多的时辰方面有庞大的机能耗损。之前就创造replaceFirst这个把持里面有正则的把持导致不凡符号不能精确衬着sql(别的参数里面带?也不能精确衬着),于是其改成了用split ?的编制停止sql的衬着。可是这个版本并没有在此应用对应的集群上使用。可能也恰是这些分外的正则把持导致了这个replaceFirst机能在这种情形下特别差。

对应优化

将其改成新版本后,新代码如下所示:

String splits[] = sql.split("\\?"); String result=""; for(int i=0;i

这个解析时辰从10s降落至2s,但感受仍是不够对劲。

经同事提示,试下StringBuilder。由于此应用使用的是jdk1.8,笔者不息感受jdk1.8已经可以直接用原生的字符串拼接不必要用StringBuilder了。但仍是试了一试,创造从2s降落至8ms!

改成StringBuilder的代码后如下所示:

String splits[] = sql.split("\\?"); StringBuilder builder = new StringBuilder(); for(int i=0;i

笔者查了下质料,创造jdk 1.8虽然做了优化,可是每做一次拼接仍是新建了一个StringBuilder,所以在大字符串频仍拼接的场景仍是必要用一个StringBuilder,以按捺分外的机能损耗。

总结

IO线程不能做任何耗时的把持,如许会导致整个吞吐量急剧降落,对应分库分表这种根本组件在编写代码的时辰必需要细致评估,连java原生的replaceFirst也会在特定情形下出现庞大的机能问题,不能漏掉任何一个点,不然就是下一个坑。

每一次复杂Bug的分析过程都是一次挑战,处理问题最重要也是最坚苦的是定位问题。而定位问题必要的是在看到征象时辰可以浮现出的各类思绪,然后经由过程日志等信息去一条条否决本身的思绪,直至到达独一的阿谁问题点。

IT外包
>
400-635-8089
立即
咨询
电话咨询
服务热线
400-635-8089
微信咨询
微信咨询
微信咨询
公众号
公众号
公众号
返回顶部