一、引子
合理配置一个应用的数据库参数,使其运行良好,这很重要。本文以某务中台的生产环境为例,从Apollo上拔下来一套配置,分析是否合理。
二、MybatisPlus配置
由于我们使用Apollo配置参数,所以分两部分:1.个体配置 2.全局配置
2.1 mybatisplus个体配置
mybatis-plus.mapper-locations = classpath*:/mapper/*Mapper.xml mapper文件地址匹配
mybatis-plus.type-aliases-package =xx.po 映射的实体包路径,
mybatis-plus.tenant-config.ignoretable = table1,table2
mybatis-plus.auth-config = []
mybatis-plus.global-config.sql-parser-cache = true 缓存sql解析
2.2 mybatis-plus全局配置
mybatis-plus.mapper-locations = classpath:/mapper/*Mapper.xml mapper文件地址匹配
mybatis-plus.configuration.map-underscore-to-camel-case = true 下划线转驼峰
mybatis-plus.global-config.logic-delete-value = true 逻辑已删除值
mybatis-plus.global-config.logic-not-delete-value = false 逻辑未删除值
mybatis-plus.max-query-records-size = 10000
三、Datasource配置
3.1 dataSource个体配置
=========数据库配置=========
spring.datasource.connectionProperties = druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 通过connectProperties属性来打开mergeSql功能;慢SQL记录5秒
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource 使用德鲁伊连接池
spring.datasource.driver-class-name = org.postgresql.Driver 驱动类名
spring.datasource.url = xx 数据库连接url
spring.datasource.username = ${dbUserName} 用户名
spring.datasource.password = ${dbPassword} 密码
spring.datasource.minIdle = 5 最小空闲连接数 5
spring.datasource.initialSize = 5 初始连接数 5
spring.datasource.maxActive = 100 最大连接数
spring.datasource.maxWait = 60000 获取连接等待超时的时间 60s=1分钟
spring.datasource.filters = stat,wall 监控统计拦截,用于监控界面sql统计
spring.datasource.poolPreparedStatements = false 是否启用缓存PreparedStatements
spring.datasource.maxPoolPreparedStatementPerConnectionSize = 20 指定每个连接上preStatement缓存数---》未生效!!!
=========健康检查=========
spring.datasource.validationQuery = SELECT 1 连接池的健康检查SQL
spring.datasource.testOnBorrow = false 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.testOnReturn = false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.testWhileIdle = true 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
#每timeBetweenEvictionRunsMillis毫秒检查一次连接池中空闲的连接,把空闲时间超过minEvictableIdleTimeMillis毫秒的连接断开,直到连接池中的连接数到minIdle为止
spring.datasource.minEvictableIdleTimeMillis = 300000 最小可驱逐空闲时间,连接保持空闲而不被驱逐的最长时间,单位是毫秒 300s=5分钟
spring.datasource.timeBetweenEvictionRunsMillis = 60000 间隔多久才进行一次驱逐检测,单位是毫秒 60s=1分钟
=========连接超时=========
# 关闭abanded连接时输出错误日志,预生产/生产不建议开启,对性能影响
spring.datasource.logAbandoned = false
# 是否清除已经超过“removeAbandonedTimout”设置的无效连接。
spring.datasource.removeAbandoned = true
# 连接超过指定时间未关闭,就会被强行回收 180s=3分钟
spring.datasource.removeAbandonedTimeoutMillis = 180000
四、源码剖析
看完配置,大家心里还是懵逼对吧,参数如何生效,druid到底如何运行?
下面,带着问题,深入源码,直接剖析druid如何申请连接、释放连接、连接泄露检查。
4.1.申请连接
最终跟进到DruidDataSource的getConnectionDirect(long maxWaitMillis),获取得到连接后,validationQuery有效性检查,源码如下:
1.testOnBorrow =true,先直接校验,执行validationQuery,失败就关闭连接JdbcUtils.close(realConnection);
2.testWhileIdle=true,如果testOnBorrow =false, 测试空闲的连接,执行validationQuery,失败就关闭连接JdbcUtils.close(realConnection);
3.removeAbandoned=true,如果开启了泄露回收:把连接添加进Map<DruidPooledConnection, Object> activeConnections 。供泄露回收时使用。
分支1和2只会有一个执行。
4.2.释放连接
德鲁伊连接池在获取连接时,会调用一次DruidDataSource的init()。方法中createAndStartDestroyThread()开启了一个销毁线程。
销毁连接的线程包含了run(),如下:
在一个for空条件循环中,根据配置的timeBetweenEvictionRunsMillis连接检测间隔时间,执行一次DestroyTask.run()就休眠一次间隔时间。未设置默认60s。(实际源码中定义了60spublic static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L;,所以用户未设置,默认60s,上图中else分支sleep1秒不会执行到)
追踪DestroyTask.run()如下:
2个步骤:
- shrink()收缩校验
- removeAbandoned()连接泄露移除
shrink()收缩校验
DruidDataSource内部定义了DruidConnectionHolder[] 类型的3个数组:
- 1.connections:可用连接数组。申请连接就从这里数组队尾拿连接。
- 2.evictConnections:待移除连接数组。
- 3.keepAliveConnections:待保活检测数组。
塞进数组
shrink()中计算出需要校验的数量checkCount,执行收缩校验核心逻辑:
- 校验物理连接的超时时间phyTimoutMills:超时放入evictConnections中,等待移除。
- 空余时间大于minEvictableIdleTimeMillis(受保最小空闲时间),并且索引(poolingCount)小于checkCount的连接则放入evictConnections;
- 空余时间大于minEvictableIdleTimeMillis(受保最小空闲时间),并且索引大于checkCount的连接,假若空余时间大于maxEvictableIdleTimeMillis则放入evictConnections,否则放入keepAliveConnections中进行keepAlive检测。
如下图:
数组处理
1.evictConnections:待移除连接数组。使用JdbcUtils.close() 关闭连接。
2.keepAliveConnections:待保活检测数组。根据配置的validationQuery查询SQL执行连接可用性校验。校验通过后再put(holder)塞进connections可用连接数组。
4.3.泄露连接移除
如果开启了removeAbandoned ,执行removeAbandoned()。移除泄露连接逻辑如下:
实际上,就是对可能的连接泄露(打开连接后长时间不关闭)兜底。
1)遍历活跃连接Map<DruidPooledConnection, Object> activeConnections。
2)跳过运行中的连接,running定义:执行SQL前赋值true ,执行完后置false。---》问题1得到答案,不会暴力关闭执行中的连接。
3)如果当前连接已连接时间>=removeAbandonedTimeoutMillis ,直接从activeConnections map 中移除。
这里消耗性能主要两步骤:
- 1.内存中记录+移除泄露连接
- 2.打印相关日志的IO---》logAbandoned=false 可关闭写日志
spring 的druid 连接池一般不会造成泄露。如果出现连接泄露,应该找到问题解决。---》问题2得到答案,目前关闭了写日志,就剩下了第一点“内存占用+过滤的性能”成本,要求不高的场景可以作为兜底方案使用。如果项目已稳定,推荐关闭。
五.分析&总结
本节为我们根据:申请、释放连接相关的参数配置,剖析策略是否合理。
5.1 配置分析
spring.datasource.testOnBorrow = false 申请连接时执行validationQuery检测连接是否有效
spring.datasource.testOnReturn = false 归还连接时执行validationQuery检测连接是否有效
spring.datasource.testWhileIdle = true testOnBorrow=false时才生效,申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.initialSize = 5 初始连接数 5
spring.datasource.maxActive = 100 最大连接数
spring.datasource.minIdle = 5 最小空闲连接数 5
timeBetweenEvictionRunsMillis= 60000 60s=1分钟检测一次
minEvictableIdleTimeMillis=300000 300s=5分钟 最小空闲不移除时间
maxEvictableIdleTimeMillis 未设置最大空闲移除时间,默认DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 60L * 7 = 7小时。
keepAlive: 未设置保活开关,默认false关闭。不执行保活测试策略。
上述配置对应的策略:
1.初始策略
初始5个连接,最多可开启100个连接。
2.申请策略
申请连接的时候检测,如果连接空闲时间大于1分钟(检测间隔时间),执行validationQuery检测连接是否有效。---》这里可确保我们空闲时间超过1分钟的连接,校验后使用。
3.回收策略
每一分钟执行一次检测,策略如下:
1.连接空闲小于5分钟,不移除。
2.连接空闲大于5分钟,保留”minIdle设置的5个idle连接”,可移除(总数-5)个连接。
3.连接空闲大于7小时,可移除“minIdle设置的5个idle连接”。---》因为没有设置maxEvictableIdleTimeMillis ,默认空闲7小时后才会移除。不过一共就5个倒也没什么事。
4.连接空闲5分钟~7小时,由于没开启keepAlive保活开关,无法对“minIdle设置的5个idle连接”保活测试。-->minIdle设置的5个idle连接,这段时间一直不回收,也不做保活测试,连接是否有效无法保证。
5.2总结
1.现有项
removeAbandoned=true 开启连接泄露检测,要求不高的场景可以作为兜底方案使用。如果项目已稳定,推荐关闭。
2.可添加项
phyTimeoutMillis:看需要开启。物理超时时间。不管空闲时间,超时直接移除。---》这个是终极兜底方案,可以确保超时强制移除。
maxEvictableIdleTimeMillis:建议开启,实现精细化控制。
keepAlive: 建议开启。可针对“minIdle设置的空闲连接”,进行保活测试,从而提升连接的质量。
如果你觉得本文对你有点帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!