在微服务架构中,多数据源访问已经成为标配需求。想象一下这样的场景:你的订单服务需要同时读写MySQL主库、查询MySQL从库、还要定期从ClickHouse拉取分析报表。如果每次操作都手动管理连接,代码很快就会变成意大利面条式的混乱结构。这就是baomidou动态数据源框架的价值所在——它让数据源切换变得像切换电视频道一样简单。
但仅有切换能力还不够,就像赛车需要仪表盘一样,我们需要实时监控连接池状态。Druid就是这个仪表盘,它能提供连接泄漏检测、SQL防火墙、性能监控等高级功能。我去年参与的一个电商项目就曾因为连接泄漏导致数据库连接耗尽,引入Druid后我们通过它的监控界面快速定位到了问题代码。
创建新的Spring Boot项目时,建议使用Spring Initializr生成基础骨架。这里有个容易踩的坑:baomidou的dynamic-datasource-spring-boot-starter已经内置了HikariCP连接池,如果同时引入Druid的starter会导致冲突。正确的做法是排除HikariCP:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
实测发现,版本匹配很关键。有次升级Spring Boot到2.7.x后,由于Druid版本滞后导致监控页面无法打开。建议保持版本对应关系:
在application.yml中,我们需要配置三层结构:动态数据源路由、各数据源连接信息、Druid专属参数。这是我经过多个项目验证的模板配置:
yaml复制spring:
datasource:
dynamic:
primary: master
strict: true
datasource:
master:
url: jdbc:mysql://localhost:3306/main_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
initial-size: 5
max-active: 20
filters: stat,wall
slave1:
url: jdbc:mysql://replica1:3306/read_db
username: reader
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
test-while-idle: true
validation-query: SELECT 1
注意strict参数建议生产环境设为true,这样在切换不存在的DS时会立即报错,避免静默失败。开发阶段可以设为false方便调试。
Druid最强大的功能莫过于它的监控能力。要启用监控面板,需要添加以下配置:
java复制@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("loginUsername", "admin");
reg.addInitParameter("loginPassword", "admin123");
return reg;
}
@Bean
public FilterRegistrationBean<WebStatFilter> filterRegistrationBean() {
FilterRegistrationBean<WebStatFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new WebStatFilter());
reg.addUrlPatterns("/*");
reg.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return reg;
}
}
启动应用后访问/druid路径,你会看到一个功能丰富的监控面板。我特别喜欢它的SQL监控功能,能清晰看到慢SQL排行和执行时间分布。曾经通过这个功能发现某个报表查询没有使用索引,优化后响应时间从3秒降到了200毫秒。
Druid提供了数十个可调参数,但实际项目中只需要关注几个关键指标:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| initialSize | 5-10 | 初始化连接数,根据并发量调整 |
| maxActive | 20-100 | 最大连接数,超过会导致等待 |
| minIdle | 5-10 | 最小空闲连接,避免频繁创建 |
| maxWait | 60000 | 获取连接超时时间(ms) |
| timeBetweenEvictionRunsMillis | 60000 | 检测间隔(ms) |
对于读写分离场景,建议为主库设置更大的maxActive值,因为写操作通常更耗时。从库可以适当减小,比如:
baomidou提供的@DS注解非常灵活,但使用不当会导致意外行为。这是我的经验总结:
java复制@Mapper
@DS("slave1")
public interface ReportMapper {
List<Report> getDailyReport();
}
java复制@Mapper
@DS("master")
public interface OrderMapper {
@DS("slave1")
List<Order> queryOrders(OrderQuery query);
@DS("master")
void createOrder(Order order);
}
对于需要根据运行时条件动态切换的场景,可以使用DynamicDataSourceContextHolder:
java复制public List<Data> fetchData(boolean useBackup) {
try {
if(useBackup) {
DynamicDataSourceContextHolder.push("backup");
}
return dataMapper.selectList();
} finally {
if(useBackup) {
DynamicDataSourceContextHolder.clear();
}
}
}
在灰度发布场景中,我曾用这种方式实现新老数据库的流量切换:当新库出现问题时,自动回切到旧库,保证服务可用性。
跨数据源的事务需要引入JTA解决方案,比如Atomikos。配置示例:
java复制@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JtaTransactionManager jtaTransactionManager() {
return new JtaTransactionManager();
}
但分布式事务性能开销大,实际项目中更推荐使用最终一致性方案,比如:
除了Druid自带的监控,建议将关键指标导出到Prometheus:
yaml复制spring:
datasource:
druid:
stat-view-servlet:
enabled: true
web-stat-filter:
enabled: true
filter:
stat:
merge-sql: true
log-slow-sql: true
slow-sql-millis: 1000
配合Grafana可以打造更强大的监控看板,设置合理的告警阈值,比如:
当发现连接数持续增长不释放时,可以:
我曾遇到一个案例:分页查询没有设置合理的limit,导致内存中加载了百万级数据,连接无法释放。通过Druid的SQL监控快速定位到了问题SQL。
对于响应变慢的问题,Druid的监控面板特别有用:
有个实际案例:某个统计接口突然变慢,通过分析发现是缺少复合索引导致。添加索引后性能提升了20倍。