三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

SpringBoot整合Druid:从连接池配置到监控调优实战

SpringBoot整合Druid:从连接池配置到监控调优实战

1. 项目概述:为什么SpringBoot项目需要Druid?

如果你正在用SpringBoot开发一个需要连接数据库的应用,比如一个用户管理系统或者电商后台,那么你大概率已经用上了Spring Boot Starter Data JPA或者MyBatis-Plus。Spring Boot的自动配置确实省心,它默认会使用HikariCP作为数据源连接池。HikariCP很快,号称“光速”,但在生产环境中,光有速度可能还不够。你可能会遇到一些更实际的问题:某个SQL突然执行得很慢,拖垮了整个应用,但你不知道是哪条语句;数据库连接莫名其妙被占满,应用开始报错,你却无从排查;你想知道在业务高峰期,数据库连接的使用情况到底怎么样。

这时候,Druid的价值就凸显出来了。它不仅仅是一个高性能的数据库连接池,更是一个强大的监控和诊断工具。在国内的Java开发圈,尤其是阿里巴巴的技术生态里,Druid几乎是标配。我经历过不止一次线上事故,最后都是靠Druid的监控页面快速定位到慢SQL或者泄露的连接,从而解决问题。所以,把SpringBoot默认的HikariCP换成Druid,对于追求稳定性和可观测性的项目来说,是一个很自然的选择。这个过程不复杂,但里面的细节和配置项,如果理解不透彻,可能会埋下隐患。接下来,我就结合自己踩过的坑,带你从零开始,在SpringBoot项目中完整地配置并用好Druid。

2. 核心依赖引入与基础配置

2.1 依赖选择与引入

首先,我们需要在项目的pom.xml文件中引入Druid的Spring Boot Starter。这里有个关键点:不要只引入druid的普通依赖,要引入druid-spring-boot-starter。这个Starter封装了与SpringBoot的自动配置集成,会省去你大量手动配置@Bean的麻烦。

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> <!-- 请使用最新稳定版本 --> </dependency>

同时,确保你已经有了数据库驱动和Spring Boot的JDBC或数据访问层依赖,比如MySQL和spring-boot-starter-data-jpamybatis-spring-boot-starter

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <!-- 或用 mybatis-spring-boot-starter --> </dependency>

注意:版本号务必去Maven中央仓库核对最新稳定版。我曾因为用了过旧的版本,导致一些新的监控功能无法使用,还遇到了兼容性问题。

2.2 基础连接池参数配置

引入依赖后,下一步就是在application.yml(或application.properties)中配置数据源。SpringBoot会自动识别spring.datasource.druid前缀下的配置。以下是一份兼顾性能和基础监控的配置示例:

spring: datasource: # 基本连接信息 url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver # 指定使用Druid数据源,这是关键一步 type: com.alibaba.druid.pool.DruidDataSource # Druid专属配置 druid: # 连接池大小配置(核心参数,根据业务调整) initial-size: 5 min-idle: 5 max-active: 20 # 获取连接等待超时时间(毫秒) max-wait: 60000 # 连接有效性检查 validation-query: SELECT 1 test-on-borrow: false # 借出时检查,影响性能,不建议开启 test-on-return: false # 归还时检查,影响性能,不建议开启 test-while-idle: true # 空闲时检查,推荐开启 time-between-eviction-runs-millis: 60000 # 空闲连接检查间隔(ms) min-evictable-idle-time-millis: 300000 # 连接最小空闲时间(ms)

参数解读与踩坑经验

  • initial-size, min-idle, max-active:这是连接池的“水位线”。initial-size是启动时就建立的连接数,避免第一次请求的延迟。min-idle是池中始终保持的最小空闲连接数。max-active是最大活跃连接数,相当于池子的容量上限。设置太小,高并发时请求会排队等待;设置太大,会耗尽数据库资源。一般建议max-active是数据库max_connections的70%-80%。我通常从20开始,根据监控数据调整。
  • test-while-idle:这是最重要的健康检查设置。设置为true后,Druid会定期用validation-query检查空闲连接是否有效,自动销毁失效连接并补充新连接。务必开启,这是避免应用使用已断开的数据库连接(比如数据库重启后)导致报错的关键。
  • max-wait:当连接池耗尽,新的请求获取连接的最大等待时间。超时则抛异常。生产环境可以设长一点(如10秒),但也要在代码中做好超时处理。

3. 监控功能配置与安全加固

Druid最强大的功能在于其内置的监控统计。不开启监控,Druid就失去了大半价值。

3.1 启用Web监控统计Servlet

Druid提供了一个类似/druid/index.html的监控页面。我们需要在配置中启用它,并设置访问账号密码,这是安全底线,绝不能裸奔开放

spring: datasource: druid: # 监控统计相关配置 web-stat-filter: enabled: true # 启用Web关联监控 url-pattern: /* # 过滤所有URL exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除静态资源和监控本身 session-stat-enable: true # 启用Session统计(谨慎开启,涉及隐私) principal-session-name: session_user # Session中用户信息的字段名 principal-cookie-name: cookie_user # Cookie中用户信息的字段名 stat-view-servlet: enabled: true # 启用StatViewServlet,提供监控页面 url-pattern: /druid/* # 监控页面的访问路径 login-username: admin # 监控页面登录用户名(必须修改!) login-password: druid123 # 监控页面登录密码(必须修改!) reset-enable: false # 禁用HTML页面上的“重置所有数据”按钮,生产环境务必关闭! allow: 127.0.0.1 # 白名单,多个用逗号分隔。生产环境建议配置内网IP或空(允许所有,但必须配密码) deny: 192.168.1.100 # 黑名单,优先级高于allow

安全配置要点

  1. login-usernamelogin-password:这是第一道防线。绝对不要使用默认值或弱密码。我见过有团队直接部署,监控页面被爬虫扫到,导致数据库信息泄露。
  2. reset-enable: false:这个按钮一旦被误点,所有监控数据清零,对于排查历史问题会是灾难。生产环境必须关掉。
  3. allowdeny:通过IP进行访问控制是最佳实践。在开发环境可以设为127.0.0.1本地访问。生产环境可以设置为运维网段IP,或者结合公司网关做二次认证。如果暂时无法确定IP,至少保证强密码。
  4. url-pattern:默认/druid/*挺好,不建议修改。如果你修改了,记得上面的web-stat-filter.exclusions也要同步更新。

3.2 配置SQL监控与防火墙

除了资源监控,我们更关心SQL执行情况。Druid可以记录所有SQL的执行时间、返回行数等。

spring: datasource: druid: # SQL监控与防火墙 filter: stat: enabled: true # 开启SQL监控 log-slow-sql: true # 记录慢SQL slow-sql-millis: 2000 # 慢SQL阈值,单位毫秒。建议根据业务调整,初期可设为1秒。 merge-sql: true # 合并相似的SQL,便于统计 wall: enabled: true # 启用SQL防火墙,防御SQL注入 config: drop-table-allow: false # 禁止删除表 truncate-allow: false # 禁止清空表 # 合并 filters 的配置(新版本推荐方式) filters: stat,wall,slf4j # 启用统计、防火墙、日志过滤器

监控数据分析经验

  • 慢SQL日志slow-sql-millis设置后,执行时间超过该值的SQL会被记录到日志(如果配置了slf4j过滤器)并在监控页面“慢SQL”栏展示。这是性能调优的黄金入口。定期查看慢SQL,针对性加索引或优化业务逻辑。
  • SQL防火墙:WallFilter能有效拦截一些明显的SQL注入攻击,比如1=1union select等。对于内网管理后台等系统,可以适当放宽限制;对于直接面向公网的接口,建议严格开启。
  • 合并SQL(merge-sql):对于使用PreparedStatement的SQL,select * from user where id = ?,无论参数是1还是2,在统计时会被合并为一条,这样能更准确地反映SQL模板的执行性能,而不是被海量参数值淹没。

4. 高级特性与生产环境调优

基础配置能让Druid跑起来,但要发挥其最大效能,适应生产环境的高并发、高可用场景,还需要进行一些深度调优。

4.1 连接泄漏检测与回收

这是Druid解决线上顽疾的利器。应用代码如果没有正确关闭ConnectionStatementResultSet,就会导致连接泄漏,最终连接池被占满。

spring: datasource: druid: # 连接泄漏检测 remove-abandoned: true # 是否移除泄露的连接 remove-abandoned-timeout: 300 # 连接被占用的超时时间(秒),超过此时间视为泄露 log-abandoned: true # 输出泄露连接的堆栈信息到日志

工作原理与注意事项: Druid会跟踪每一个从连接池借出的连接。如果一个连接被借用时间超过了remove-abandoned-timeout(例如300秒),并且没有被归还,Druid就会认为它泄露了。此时,如果remove-abandonedtrue,Druid会强制回收这个连接;如果log-abandonedtrue,会打印出借用此连接的线程的堆栈信息,帮助你定位是哪段代码没有关闭连接。

警告remove-abandoned是一个“事后补救”机制,它会强制关闭连接,可能导致正在进行的事务被中断。它不能替代良好的编程习惯。正确的做法是在代码中使用try-with-resources(Java 7+)或finally块确保资源关闭。开启此功能主要用于在过渡期或排查历史遗留问题时,发现那些隐藏的泄露点。生产环境长期开启时,超时时间可以设得长一些(如10分钟),避免误杀长时间运行的批处理任务。

4.2 异步初始化与性能优化

对于大型应用,启动时初始化所有连接(initial-size)可能会拖慢启动速度。Druid支持异步初始化。

spring: datasource: druid: # 异步初始化,加快应用启动速度 async-init: true # 连接池的公平锁模式,在高并发下更稳定,但略有性能损耗 use-unfair-lock: false # 默认为true(非公平锁),高并发竞争激烈时可设为false尝试公平锁 # 连接持有时间监控,用于分析连接使用是否合理 time-between-log-stats-millis: 300000 # 每5分钟输出一次统计日志到控制台

调优建议

  • async-init: true:对于initial-size设置较大的场景(如50+),开启此选项可以让你应用秒启,连接在后台线程中慢慢建立。非常实用的一个特性。
  • 锁模式选择:Druid内部使用重入锁管理连接池。use-unfair-lock: true(默认)性能更好,但在极端高并发下,可能造成线程饥饿。如果你监控到获取连接的等待时间异常波动,可以尝试切换到公平锁(false)看是否更平滑。不过99%的场景默认值就够了。
  • 定期日志time-between-log-stats-millis会让Druid定期将关键统计信息(活跃数、等待数等)打印到日志,便于你通过ELK等日志系统进行长期趋势分析,而不仅仅依赖监控页面。

4.3 集成Spring监控与多数据源配置

集成Spring监控:如果你想在Druid监控页面上看到Spring相关的监控信息(如Controller方法调用次数、耗时),需要额外引入druid-spring-boot-starter的依赖(我们已经引入了),并且确保spring-boot-starter-aop在类路径下。Druid会自动通过AOP切面进行统计。

多数据源配置:当你的项目需要连接多个数据库时,Spring Boot的自动配置就不够用了。你需要手动定义多个DruidDataSource的Bean。

@Configuration public class DruidConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.druid.slave") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Primary // 指定主数据源 @Bean public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource master, @Qualifier("slaveDataSource") DataSource slave) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", master); targetDataSources.put("slave", slave); // 使用AbstractRoutingDataSource实现动态数据源路由 AbstractRoutingDataSource dynamicDataSource = new AbstractRoutingDataSource() { @Override protected Object determineCurrentLookupKey() { // 可以从ThreadLocal中获取当前要使用的数据源key,例如 "master" 或 "slave" return DynamicDataSourceContextHolder.getDataSourceKey(); } }; dynamicDataSource.setDefaultTargetDataSource(master); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } // 配置Druid监控Servlet和Filter,注意这里要注入的是最终的dynamicDataSource @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet() { ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); initParams.put("loginPassword", "druid123"); initParams.put("allow", "127.0.0.1"); bean.setInitParameters(initParams); return bean; } @Bean public FilterRegistrationBean<WebStatFilter> webStatFilter() { FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>(new WebStatFilter()); bean.setUrlPatterns(Arrays.asList("/*")); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); bean.setInitParameters(initParams); return bean; } }

然后在application.yml中分别配置两个数据源:

spring: datasource: druid: master: url: jdbc:mysql://master-host:3306/db username: ... password: ... initial-size: 5 # ... 其他master专属配置 slave: url: jdbc:mysql://slave-host:3306/db username: ... password: ... initial-size: 3 # ... 其他slave专属配置

多数据源配置核心点

  1. @Primary注解:必须指定一个默认数据源,否则Spring不知道注入哪个。
  2. 动态数据源路由:上面的例子使用了AbstractRoutingDataSource,你需要自己实现DynamicDataSourceContextHolder(一个基于ThreadLocal的工具类),在Service层或AOP切面中,根据业务逻辑(如读操作用slave,写操作用master)设置当前线程要使用的数据源Key。
  3. 监控配置:在多数据源下,自动配置的监控Servlet可能不工作。你需要像上面代码一样,手动注册StatViewServletWebStatFilter,并且确保Filter和Servlet能正确关联到你的数据源。更复杂的做法是为每个DruidDataSource单独配置一个StatFilter并绑定。

5. 监控页面解读与常见问题排查

配置完成后,启动应用,访问http://你的应用地址/druid/index.html,输入配置的用户名密码,就能看到功能强大的监控后台了。

5.1 核心监控面板解读

  1. 数据源:这里显示连接池的基本状态。重点关注
    • 活跃连接数 (Active):当前正在被使用的连接数。如果长期接近max-active,说明连接池大小可能不足。
    • 等待线程数 (WaitThreadCount):获取连接时发生等待的线程数。如果长期大于0,说明连接不够用,请求在排队。
    • 逻辑连接打开次数 (ConnectCount)关闭次数(CloseCount):理论上这两个数应该接近。如果ConnectCount远大于CloseCount,可能存在连接泄漏。
  2. SQL监控:这里记录了所有执行过的SQL。重点关注
    • 执行时间:找出最耗时的SQL(慢SQL)。
    • 执行次数:找出执行最频繁的SQL,考虑是否引入缓存。
    • 读取行数 (ResultSet)更新行数 (Update):分析SQL效率。
    • 可以点击“SQL”列进行聚合分析,查看同一模板SQL在不同参数下的执行情况。
  3. SQL防火墙:展示被拦截的SQL攻击尝试。如果这里频繁出现记录,说明你的应用可能正在被扫描或攻击。
  4. Web应用:展示URL请求的监控数据,类似于简易版的APM。可以查看每个URI的请求次数、耗时、Jdbc执行次数等,帮助定位接口性能瓶颈。

5.2 常见问题与排查实录

问题一:监控页面无法访问,报404或空白页。

  • 检查1:确认stat-view-servlet.enabled是否为true
  • 检查2:确认访问路径是否正确(默认是/druid/*)。
  • 检查3:检查是否有其他过滤器或安全框架(如Spring Security)拦截了/druid/**路径。需要在安全配置中放行。
  • 检查4:如果是多数据源手动配置,确认是否手动注册了StatViewServlet

问题二:应用运行一段时间后,出现“获取连接超时”异常。

  • 排查步骤
    1. 立刻打开Druid监控面板,查看“数据源”页。
    2. 如果“活跃连接数”等于max-active,且“等待线程数”很多,说明连接池已满。
    3. 点击“连接堆栈”查看当前所有活跃连接正在执行的SQL。很可能是有慢SQL或事务未提交,长时间占用连接。
    4. 同时,检查“SQL监控”页,按“执行时间”倒序排列,找出可疑的慢SQL。
    5. 如果“活跃连接数”不高,但“逻辑连接打开次数”异常高,且“物理连接打开次数”也很高,可能是连接泄漏。开启remove-abandonedlog-abandoned功能,观察日志输出,定位未关闭连接的代码位置。

问题三:监控页面显示的数据(如SQL执行次数)不准确或为零。

  • 检查1:确认filters: stat已经配置。没有这个过滤器,SQL监控功能不会生效。
  • 检查2:检查是否使用了其他数据源代理(如P6Spy),可能会干扰Druid的统计。
  • 检查3:某些特定类型的SQL(如存储过程调用)或通过某些特定框架(早期版本的JPA Native Query)执行的SQL,Druid可能无法统计到。

问题四:应用启动特别慢。

  • 排查:检查initial-size是否设置过大。如果数据库在远端,建立多个连接本身就很耗时。可以尝试设置async-init: true,让连接池在后台异步初始化。

一个真实的踩坑案例:我们有一个定时任务,会在凌晨批量处理数据。最初没有配置test-while-idle,某天数据库半夜例行维护重启后,连接池里持有的连接全部失效。早上定时任务启动,从池中拿到这些失效连接去执行SQL,全部报“Communications link failure”错误,导致任务失败。后来开启了test-while-idle并设置了合理的time-between-eviction-runs-millis,Druid会定期检查并刷新连接,这个问题再没出现过。

配置Druid不是一劳永逸的事情。它更像是一个给你装上了仪表盘的汽车。你需要定期查看这些“仪表盘”(监控页面),了解你应用数据库连接的健康状况(连接数压力)、发动机效率(SQL性能)、以及是否有异常报警(慢SQL、泄露连接)。结合监控数据,反复调整连接池参数、优化SQL语句,才能真正让Druid成为你系统稳定运行的守护者,而不是一个简单的连接池组件。

← 返回列表