数据库连接池配置(案例及排查指南)

2020 年 3 月 18 日

数据库连接池配置(案例及排查指南)

引言


想必本文的读者对数据库都不会陌生,由于数据库良好的特性和服务的稳定性,使得我们的工作几乎离不开,而数据库连接池因为连接复用的优势也被广泛的使用,但凡事不可能只有好处而没有代价,使用连接池一个最直接的代价就是需要配置一堆的参数。其实很多时候这个复杂度也不存在,只要找个工程把配置拷贝一份,改一下用户名密码也就能工作了,因为之前的配置都正常工作了一段时间基本也没问题了,这个逻辑本身没毛病,但有个前提至少知道配了什么,不然问题来了都不知道如何应对。本文以 druid 1.1.5 (https://github.com/alibaba/druid) 连接池为例来阐述几个参数的重要性及如何避免踩坑,虽然下面提到的都是 druid 的配置项,但多数连接池(不限于数据库)其实也都有类似的配置,基本用法和场景均可借鉴。


一、连接池配置


1.1 maxWait


参数表示从连接池获取连接的超时等待时间,单位毫秒,需要注意这个参数只管理获取连接的超时。获取连接等待的直接原因是池子里没有可用连接,具体包括:连接池未初始化,连接长久未使用已被释放,连接使用中需要新建连接,或连接池已耗尽需等待连接用完后归还。这里有一个很关键的点是 maxWait 未配置或者配置为 0 时,表示不设等待超时时间(可能与一些人认为 -1 表示无限等待的预期不符合,虽然在 druid 中 maxWait 配置成 -1 的含义也相同)。对实现细节感兴趣的读者可以从 com.alibaba.druid.pool.DruidDataSource#getConnection 方法入手,查看参数的使用逻辑。


推荐配置:内网(网络状况好) 800;网络状况不是特别好的情况下推荐大于等于 1200,因为 tcp 建连重试一般是 1 秒。


如果不配置 maxWait,后果会怎么样呢?可能有些应用就这么干的,而且也没发生过异常,不过最终墨菲定律还是会显灵的,下面来看几个真实的案例:


案例一


// 参数配置maxWait=0,maxActive=5,
复制代码


正常流量下业务没有发现任何问题,但突发大流量涌入时,造成连接池耗尽,所有新增的 DB 请求处于等待获取连接的状态中。由于 maxWait=0 表示无限等待,在请求速度大于处理速度的情况下等待队列会越排越长,最终业务上的表现就是业务接口大量超时,流量越大造成实际吞吐量反而越低。


案例二


maxWait=0,removeAbandoned=true,removeAbandonedTimeout=180,
复制代码


现象:业务代码正常运行了很长时间没有出现过消息积压情况,在一次全链路压测后产生大量的压测数据,造成了 MQ 消息的堆积。即使重启服务,也只能保持几十秒的正常运行,随后又进入消费停滞的状态。使用 jstack 发现是卡在获取数据库连接中,再过 3 分钟左右后出现错误:abandon connection, owner thread: xxx 。最后业务通过配置 maxWait=3000(3 秒超时),业务 MQ 消息正常消费。


原因分析:业务依赖两个数据源,这里表示为 datasource1 与 datasource2,其中在部分代码段中同时开启了两个库的事务。如图 1 所示,线程 1 获取了 datasource1 中的最后一个连接 connection[n],等待获取 datasource2 的连接,此时线程 2 也正在执行类似的操作,造成了死锁等待。大家对这种互锁一定很熟悉,只是这次是发生在 DB 上。为什么一段时间后程序报 abandon connection 的错误,这是因为配置了 {removeAbandoned:true, removeAbandonedTimeout:180} 这两个参数,这个配置的含义是如果一个连接持有 180 秒还没有归还,就被认为是异常连接(对于 OLTP 的业务查询通常都是毫秒级的),需要关闭掉这条连接。之所以正常情况下没有发生问题是因为连接池水位比较低,资源充足没有造成相互等待的情况。



图 1. 双 DB 连接池死锁问题


1.2 connectionProperties


参数是以键值对表示的字符串,其中可以配置 connectTimeout 和 socketTimeout,它们的单位都是毫秒,这两个参数在应对网络异常方面非常重要。connectTimeout 配置建立 TCP 连接的超时时间,socketTimeout 配置发送请求后等待响应的超时时间。这两个参数也可以通过在 jdbc url 中添加 connectTimeout=xxx&socketTimeout=xxx 的方式配置,试过在 connectinoProperties 中和 jdbc url 两个地方都配置,发现优先使用 connectionProperties 中的配置。如果不设置这两项超时时间,服务会有非常高的风险。现实案例是在网络异常后发现应用无法连接到 DB,但是重启后却能正常的访问 DB。因为在网络异常下 socket 没有办法检测到网络错误,这时连接其实已经变为“死连接”,如果没有设置 socket 网络超时,连接就会一直等待 DB 返回结果,造成新的请求都无法获取到连接。


推荐配置


socketTimeout=3000;connectTimeout=1200


1.3 keepAlive


参数表示是否对空闲连接保活,布尔类型。可能不少人认为 druid 连接池默认会维持 DB 连接的心跳,对池子中的连接进行保活,特别配置了 minIdle 这个参数后觉得,有了 minIdle 最少应该会保持这么多空闲连接。其实,keepAlive 这个参数是在 druid 1.0.28 后新增的,并且默认值是 false,即不进行连接保活。


那么需要保活连接,是不是将 keepAlive 配置成 true 就完事了呢?虽然 true 的确是开启了保活机制,但是应该保活多少个,心跳检查的规则是什么,这些都需要正确配置,否则还是可能事与愿违。这里需要了解几个相关的参数:minIdle 最小连接池数量,连接保活的数量,空闲连接超时踢除过程会保留的连接数(前提是当前连接数大于等于 minIdle),其实 keepAlive 也仅维护已存在的连接,而不会去新建连接,即使连接数小于 minIdle;minEvictableIdleTimeMillis 单位毫秒,连接保持空闲而不被驱逐的最小时间,保活心跳只对存活时间超过这个值的连接进行;maxEvictableIdleTimeMillis 单位毫秒,连接保持空闲的最长时间,如果连接执行过任何操作后计时器就会被重置(包括心跳保活动作);timeBetweenEvictionRunsMillis 单位毫秒,Destroy 线程检测连接的间隔时间,会在检测过程中触发心跳。保活检查的详细流程可参见源码 com.alibaba.druid.pool.DruidDataSource.DestroyTask,其中心跳检查会根据配置使用 ping 或 validationQuery 配置的检查语句。


推荐配置:如果网络状况不佳,程序启动慢或者经常出现突发流量,则推荐配置为 true;


案例一


keepAlive=true,minIdle=5,timeBetweenEvictionRunsMillis=10000,minEvictableIdleTimeMillis=100000,maxEvictableIdleTimeMillis=100000,
复制代码


请问上述配置连接能保活成功吗?不能,由于 minEvictableIdleTimeMillis == maxEvictableIdleTimeMillis,所以连接在开始检测时就会被断定超过 maxEvictableIdleTimeMillis 需要丢弃。


案例二


keepAlive=true,minIdle=5,timeBetweenEvictionRunsMillis=10000,minEvictableIdleTimeMillis=95000,maxEvictableIdleTimeMillis=100000,
复制代码


请问上述配置连接能保活成功吗?具有随机性,由于 maxEvictableIdleTimeMillis - minEvictableIdleTimeMillis < timeBetweenEvictionRunsMillis,所以有可能在这个窗口期并没有执行 Destroy 线程检测任务,无法保证心跳一定会被执行。


1.4 maxActive


最大连接池数量,允许的最大同时使用中的连接数。这里特地唠叨一下,配置 maxActive 千万不要好大喜多,虽然配置大了看起来业务流量飙升后还能处理更多的请求,但切换到 DB 视角会发现其实连接数的增多在很多场景下反而会减低吞吐量,一个非常典型的例子就秒杀,在更新热点数据时 DB 需要加锁操作,这个时候再让更多的连接操作 DB 就有点像假日往高速上涌入的车辆,只会给 DB 添堵。


推荐配置:20,多数场景下 20 已完全够用,当然这个参数跟使用场景相关性很大,一般配置成正常连接数的 3~5 倍。


二、DB“慢查”排查记


上面讲了一些配置的坑,那么是否中规中矩的按照推荐配置就万事大吉了呢,现实中的世界往往没这么简单的事,下面分享一个“慢查”排查的一个案例,了解一下 DB 慢查的排查思路。


有应用反馈发现大量 DB 慢查,并且日志上还记录了详细的执行时间和 SQL 语句。接到问题后我们第一时间排查 DB 发现并没有异常,也没有慢查记录,并且日志中的大部分 SQL 都能匹配索引,测试执行都在毫秒级。于是开始排查网络是否正常,有没丢包、重传等现象,查询监控数据发现也很正常,然后进行抓包分析发现实际请求处理的速度非常正常,至此可以排除 DB 问题。


于是再深入分析,查询 DB 其实可分为两个阶段:1. 获取连接阶段;2. 执行查询阶段;绝大部分情况下获取连接代价非常小,直接就能从连接池获取到,即使需要新建连接代价往往也不大,所以使用时非常容易忽略获取连接这个阶段。什么情况下获取连接会出问题呢?一种情况是建立连接慢,一种是连接池已经耗尽,再对照上面的案例进行排查,依次排除了这两种情况。至此问题还是一筹莫展,还好高手在场,想到用 strace 跟踪 SQL 请求前后干了什么,最后发现记录慢查日志开始和结束之间有写日志操作,这里的写日志是同步的并且在特定情况下正好触发了另一个问题导致写日志非常慢,并且这个日志操作是封装在底层的,连业务开发都不清楚有这么个操作。至此真相水落石出,最终修复了写日志慢的问题后就不再出现类似的“慢查”了。


三、结语


有时一个“慢查”问题有时候可能并非像结果展示的那样确切,看似最可能出在 DB 上的问题,却是另外几个风马牛不相及的原因凑到一起造成的,所以有时还得留个心眼,全局地看问题,看似无路可走时去追查抽象的背后实现。经验加演练能有效地预防故障,限于篇幅本文只挑选几个最容易引发问题及容易误解的参数做一些经验性地介绍,上述很多案例都可以使用 iptables, tc 等工具来模拟断网和丢包来复现,希望有赞的经验能帮助到读者避免一些常见问题。


2020 年 3 月 18 日 19:53285

评论

发布
暂无评论
发现更多内容

动态规划算法重点在于找上一个的公式,Google Code Review,John 易筋 ARTS 打卡 Week 06

John(易筋)

ARTS 打卡计划

奈学:数据湖和数据仓库的区别有哪些?

古月木易

数据仓库 数据湖

一文搞懂 Redis高性能之IO多路复用

flyer0126

redis io 多路复用 高性能

谈谈架构和微服务<一>

Gabriel

架构 微服务 微服务架构 领域驱动设计 软件设计

大型互联网架构与集群技术

xzm

ARTS 打卡 Week 05

teoking

轻松上手promise原理(2):then的简单实现

前端小帅

奈学:数据湖和数据仓库的区别有哪些?

奈学教育

数据仓库 数据湖

架构师训练营第 4 周作业

在野

极客大学架构师训练营

典型的大型互联网应用系统

Z冰红茶

【week04】作业

chengjing

互联网架构作业

qihuajun

如何成为一名合格的 C/C++ 开发者?

张小方

c++ Linux 编程语言 架构设计 后端开发

嗨,兄弟,别担心,这年头谁还没有一点焦虑!

攀岩飞鱼

管理 程序员人生 成长 个人感想 程序员素养

大型互联网应用系统使用技术方案和手段

MySQL 实战 45 讲笔记(2)-查询优化

王传义

MySQL

奈学:数据湖有哪些缺点?

奈学教育

数据湖

架构演化

满山李子

架构师训练营第 4 周——学习总结

在野

极客大学架构师训练营

如何进行高效学习

淡蓝色

深度思考 方法论 感悟 随笔杂谈

读闲书自由和财务自由

池建强

读书 财务自由

架构师训练营第四周课后作业

竹森先生

极客时间 极客大学架构师训练营

第四章总结

游戏夜读 | 游戏关卡设计师

game1night

极客大学架构师训练营第四周学习总结

竹森先生

极客大学 极客大学架构师训练营

SpringBatch系列之Remote-Chunking

稻草鸟人

大数据 Spring Boot SpringBatch 批量任务

我写了一本操作系统词典送给你

cxuan

操作系统 计算机

深入理解Kubernetes的Service:回归本源的场景需求

韩超

Kubernetes 微服务 服务

奈学:数据湖有哪些缺点?

古月木易

数据湖

互联网架构学习总结

qihuajun

消息队列(一)为什么要使用消息队列?

奈何花开

Java MQ 消息队列

数据库连接池配置(案例及排查指南)-InfoQ