1.故障简述
8月30日 上午9:30 发现某核心服务开始告警,主动重启,影响线上成交和查看订单。持续影响3分钟
2.故障引入与处理的整个过程
时间 | 故障处理行动 |
2023-08-30 09:29:57 | 收到监控告警某核心 服务自动完成重启 |
2023-08-30 09:30:33 | 服务全部启动完成 |
2023-08-30 09:51:00 | 排查原因——通过日志看到有数据库关闭 |
2023-08-30 10:03:00 | 看到慢sql统计里有昨日上线的sql |
2023-08-30 10:20:00 | 优化sql提交代码打包上线 |
2023-08-30 10:44:41 | 上线完成,观察日志已经没发现新产生对应的慢sql |
3.故障原因分析
3.1 故障触发
故障根因:由于慢sql增多导致数据库连接池打满,所有dubbo服务涉及数据库操作的都需要等待获取链接,从而把dubbo线程池打满,
服务的健康检查发现dubbo服务不可用,所以发生了重启
3.2 故障排查过程
1.首先发现服务频繁重启,排查原因是因为健康检查没有过,健康接口出错日志是 dubbo线程池满了,目前最大是200
2.同时发现这期间有超过200个慢sql,猜测慢sql阻塞住了请求
3.这期间迅速修复慢sql
对大表进行查询,mysql优化器选择了带函数的检索字段,导致索引失效
4.同时排查慢sql阻塞其余请求的原因,连上服务器,发现服务状态都健康,内存CPU等都很稳定
5.jstack查看线程状态,发现所有容器工作线程都是wait状态,如下:
"XNIO-1 task-5" #178 prio=5 os_prio=0 tid=0x000000002a03e000 nid=0x350c waiting on condition [0x000000004013a000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c4f36dd0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at com.alibaba.druid.pool.DruidDataSource.takeLast(DruidDataSource.java:2029)
at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1557)
at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1337)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1317)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1307)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:109)
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:158)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:116)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:79)
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:82)
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:68)
我们的数据库连接池是DruidDataSource,配置基本都是默认值,现在最大8个连接,深挖代码发现:
就算设置了超时等待时间,只能影响等待线程,不能中断被hold住的查询,如果要中断正在执行的查询,可以通过设置这两个参数:
那么防止慢sql拖垮整个服务的解决方式也很明了:
1.设置maxWait
2.设置removeAbandoned为true,设置removeAbandonedTimeout(应大于业务运行最长时间)
3.设置合理的最大连接数,默认为8,需根据业务自行调整
4.结论
1.上线之前反复确认sql是否正确命中索引,防止慢sql拖垮服务
2.检查和优化数据库配置:执行超时时间,获取链接超时时间等
5.推荐的配置
初始化大小,最大,最小连接等条件根据实际需要进行配置,以下配置相关参数皆为举例
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="10" />
<property name="minIdle" value="6" />
<property name="maxActive" value="50" />
<!-- 配置从连接池获取连接等待超时的时间 -->
<property name="maxWait" value="10000" />
<!-- 配置间隔多久启动一次DestroyThread,对连接池内的连接才进行一次检测,单位是毫秒。
检测时:1.如果连接空闲并且超过minIdle以外的连接,如果空闲时间超过minEvictableIdleTimeMillis设置的值则直接物理关闭。2.在minIdle以内的不处理。
-->
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<!-- 配置一个连接在池中最大空闲时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<!-- 设置从连接池获取连接时是否检查连接有效性,true时,每次都检查;false时,不检查 -->
<property name="testOnBorrow" value="false" />
<!-- 设置往连接池归还连接时是否检查连接有效性,true时,每次都检查;false时,不检查 -->
<property name="testOnReturn" value="false" />
<!-- 设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查 -->
<property name="testWhileIdle" value="true" />
<!-- 检验连接是否有效的查询语句。如果数据库Driver支持ping()方法,则优先使用ping()方法进行检查,否则使用validationQuery查询进行检查。(Oracle jdbc Driver目前不支持ping方法) -->
<property name="validationQuery" value="select 1 from dual" />
<!-- 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 -->
<!-- <property name="validationQueryTimeout" value="1" /> -->
<!-- 打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn -->
<property name="keepAlive" value="true" />
<!-- 连接泄露检查,打开removeAbandoned功能 , 连接从连接池借出后,长时间不归还,将触发强制回连接。回收周期随timeBetweenEvictionRunsMillis进行,如果连接为从连接池借出状态,并且未执行任何sql,并且从借出时间起已超过removeAbandonedTimeout时间,则强制归还连接到连接池中。 -->
<property name="removeAbandoned" value="true" />
<!-- 超时时间,秒 -->
<property name="removeAbandonedTimeout" value="80"/>
<!-- 关闭abanded连接时输出错误日志,这样出现连接泄露时可以通过错误日志定位忘记关闭连接的位置 -->
<property name="logAbandoned" value="true" />
<!-- 根据自身业务及事务大小来设置 -->
<property name="connectionProperties"
value="oracle.net.CONNECT_TIMEOUT=2000;oracle.jdbc.ReadTimeout=10000"></property>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小,Oracle等支持游标的数据库,打开此开关,会以数量级提升性能,具体查阅PSCache相关资料 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20" />
<!-- 配置监控统计拦截的filters -->
<!-- <property name="filters" value="stat,slf4j" /> -->
<property name="proxyFilters">
<list>
<ref bean="log-filter" />
<ref bean="stat-filter" />
</list>
</property>
<!-- 配置监控统计日志的输出间隔,单位毫秒,每次输出所有统计数据会重置,酌情开启 -->
<property name="timeBetweenLogStatsMillis" value="120000" />
</bean>
标签:java,08,重启,30,频繁,sql,DruidDataSource,DataSourceUtils
From: https://blog.51cto.com/u_13222507/7436888