SpringBoot + 数据库连接池监控 + 动态扩容:连接不足时自动扩容,避免请求排队

前言

数据库连接池是应用与数据库之间的桥梁,它的健康状况直接影响着整个系统的性能。当连接池中的连接被耗尽时,新的请求只能排队等待,响应时间从毫秒级飙升到秒级甚至超时,这就是 dreaded 的"连接池耗尽"问题。

传统的连接池配置是静态的:在应用启动时设置好最大连接数,之后就一成不变。但在实际生产环境中,流量是动态变化的:

  • 早高峰:用户登录、查看数据,连接需求激增
  • 大促期间:订单量暴增,数据库压力陡增
  • 夜间批处理:定时任务集中执行,连接竞争激烈

本文将介绍一套完整的数据库连接池监控与动态扩容方案,实现:

  • 实时监控:全面掌握连接池运行状态
  • 智能扩容:连接不足时自动增加连接数
  • 缩容回收:空闲时自动释放多余连接
  • 告警通知:异常情况及时通知运维

一、连接池问题分析

1. 连接池耗尽的场景

┌─────────────────────────────────────────────────────────────┐
│                   连接池耗尽场景示意                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  正常情况                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  连接池:10个连接                                     │   │
│  │  活跃连接:3个                                        │   │
│  │  空闲连接:7个                                        │   │
│  │  等待队列:0个请求                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  流量激增                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  连接池:10个连接                                     │   │
│  │  活跃连接:10个(全部占满)                            │   │
│  │  空闲连接:0个                                        │   │
│  │  等待队列:50个请求(排队中)                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  后果:                                                       │
│  - 请求响应时间从 10ms → 5000ms+                            │
│  - 大量请求超时失败                                          │
│  - 用户体验极差,系统几乎不可用                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2. 连接池关键指标

指标说明健康阈值危险阈值
活跃连接数正在使用的连接< 70%> 90%
空闲连接数可用的连接> 20%< 10%
等待队列长度等待连接的请求0> 10
连接等待时间获取连接的平均时间< 100ms> 500ms
连接使用率活跃连接 / 最大连接< 70%> 90%

3. 静态配置的痛点

┌─────────────────────────────────────────────────────────────┐
│                   静态配置 vs 动态配置                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  静态配置(传统方式)                                          │
│  ───────────────────                                          │
│  最大连接数 = 50(固定)                                       │
│                                                             │
│  低峰期:使用 5 个连接    ──► 45 个连接闲置,浪费资源          │
│  高峰期:需要 80 个连接   ──► 30 个请求排队,性能下降          │
│                                                             │
│  动态配置(本文方案)                                          │
│  ───────────────────                                          │
│  初始连接数 = 10                                               │
│  最小连接数 = 5                                                │
│  最大连接数 = 100                                              │
│                                                             │
│  低峰期:自动缩容到 5 个  ──► 节省资源                        │
│  高峰期:自动扩容到 80 个 ──► 满足需求,无排队                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、整体架构设计

1. 系统架构

┌─────────────────────────────────────────────────────────────┐
│                连接池监控与动态扩容系统架构                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   监控采集层                         │   │
│  │  ┌──────────────┐  ┌──────────────┐                │   │
│  │  │ HikariCP     │  │ 连接池指标   │                │   │
│  │  │ MXBean 监控  │  │ 采集器       │                │   │
│  │  └──────┬───────┘  └──────┬───────┘                │   │
│  │         │                 │                        │   │
│  │         └────────┬────────┘                        │   │
│  │                  ▼                                 │   │
│  │         ┌──────────────┐                          │   │
│  │         │ 指标数据流   │                          │   │
│  │         └──────┬───────┘                          │   │
│  └────────────────┼──────────────────────────────────┘   │
│                   │                                        │
│                   ▼                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   分析决策层                         │   │
│  │  ┌──────────────┐  ┌──────────────┐                │   │
│  │  │ 负载分析器   │  │ 扩容决策器   │                │   │
│  │  │ (趋势预测)   │  │ (策略引擎)   │                │   │
│  │  └──────┬───────┘  └──────┬───────┘                │   │
│  │         │                 │                        │   │
│  │         └────────┬────────┘                        │   │
│  │                  ▼                                 │   │
│  │         ┌──────────────┐                          │   │
│  │         │ 扩容/缩容指令│                          │   │
│  │         └──────┬───────┘                          │   │
│  └────────────────┼──────────────────────────────────┘   │
│                   │                                        │
│                   ▼                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   执行控制层                         │   │
│  │  ┌──────────────┐  ┌──────────────┐                │   │
│  │  │ 连接池配置   │  │ 平滑扩缩容   │                │   │
│  │  │ 动态修改器   │  │ 控制器       │                │   │
│  │  └──────────────┘  └──────────────┘                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   告警通知层                         │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐           │   │
│  │  │ 邮件告警 │ │ 钉钉通知 │ │ 日志记录 │           │   │
│  │  └──────────┘ └──────────┘ └──────────┘           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2. 核心组件

组件职责技术选型
连接池监控器实时采集连接池指标HikariCP MXBean
负载分析器分析连接使用趋势滑动窗口算法
扩容决策器根据策略决定扩缩容规则引擎
配置修改器动态修改连接池配置HikariConfig
告警服务异常时发送通知邮件/钉钉/企业微信

3. 扩容策略

┌─────────────────────────────────────────────────────────────┐
│                     扩容决策流程                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 采集指标(每 10 秒)                                      │
│      │                                                      │
│      ▼                                                      │
│  2. 计算连接使用率                                            │
│      使用率 = 活跃连接数 / 最大连接数                           │
│      │                                                      │
│      ▼                                                      │
│  3. 判断是否需要扩容                                           │
│      │                                                      │
│      ├── 使用率 > 80% 且 等待队列 > 0 ──► 需要扩容            │
│      │                                                      │
│      ├── 使用率 < 30% 持续 5 分钟 ──► 需要缩容                │
│      │                                                      │
│      └── 其他情况 ──► 保持现状                                │
│      │                                                      │
│      ▼                                                      │
│  4. 计算目标连接数                                            │
│      目标数 = min(当前最大连接数 + 增量, 绝对上限)               │
│      │                                                      │
│      ▼                                                      │
│  5. 执行扩容/缩容                                             │
│      │                                                      │
│      ▼                                                      │
│  6. 记录日志并通知                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

三、代码实现

1. 项目结构

SpringBoot-ConnectionPool-Demo/
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── example/
│       │           └── connectionpool/
│       │               ├── ConnectionPoolApplication.java
│       │               ├── config/
│       │               │   ├── DataSourceConfig.java
│       │               │   └── DynamicPoolConfig.java
│       │               ├── monitor/
│       │               │   ├── ConnectionPoolMonitor.java
│       │               │   ├── ConnectionPoolMetrics.java
│       │               │   └── PoolStatistics.java
│       │               ├── scaler/
│       │               │   ├── PoolScaler.java
│       │               │   ├── ScaleStrategy.java
│       │               │   └── ScaleDecision.java
│       │               ├── controller/
│       │               │   ├── PoolController.java
│       │               │   └── UserController.java
│       │               ├── service/
│       │               │   ├── UserService.java
│       │               │   └── SimulationService.java
│       │               ├── entity/
│       │               │   └── User.java
│       │               ├── repository/
│       │               │   └── UserRepository.java
│       │               └── notification/
│       │                   └── NotificationService.java
│       └── resources/
│           └── application.yml
├── pom.xml
└── README.md

2. 动态连接池配置

# application.yml
spring:
  application:
    name: connection-pool-demo

  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    driver-class-name: org.h2.Driver
    hikari:
      # 初始配置
      minimum-idle: 5
      maximum-pool-size: 20
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      pool-name: DynamicHikariPool

# 动态连接池配置
dynamic:
  pool:
    enabled: true
    
    # 扩容配置
    scale-up:
      enabled: true
      threshold-percentage: 80          # 使用率超过80%触发扩容
      queue-length-threshold: 5         # 等待队列超过5触发扩容
      increment-size: 10                # 每次增加10个连接
      max-pool-size: 100                # 最大连接数上限
      cooldown-seconds: 60              # 扩容冷却时间(秒)
    
    # 缩容配置
    scale-down:
      enabled: true
      threshold-percentage: 30          # 使用率低于30%触发缩容
      min-pool-size: 5                  # 最小连接数下限
      decrement-size: 5                 # 每次减少5个连接
      idle-duration-minutes: 5          # 持续空闲5分钟才缩容
    
    # 监控配置
    monitor:
      enabled: true
      interval-seconds: 10              # 监控间隔
      metrics-retention-minutes: 60     # 指标保留时间
    
    # 告警配置
    alert:
      enabled: true
      high-usage-threshold: 90          # 使用率超过90%告警
      queue-wait-threshold: 1000        # 等待时间超过1000ms告警
      channels: email,dingtalk

logging:
  level:
    com.example.connectionpool: DEBUG
    com.zaxxer.hikari: DEBUG

3. 连接池监控器

@Component
@Slf4j
public class ConnectionPoolMonitor {

    @Autowired
    private HikariDataSource dataSource;

    @Autowired
    private DynamicPoolConfig config;

    @Autowired
    private PoolScaler poolScaler;

    @Autowired
    private NotificationService notificationService;

    private final Queue<PoolStatistics> metricsHistory = new ConcurrentLinkedQueue<>();

    @Scheduled(fixedRateString = "${dynamic.pool.monitor.interval-seconds:10}000")
    public void monitor() {
        if (!config.isEnabled()) {
            return;
        }

        try {
            HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();
            HikariConfigMXBean configMXBean = dataSource.getHikariConfigMXBean();

            if (poolMXBean == null) {
                return;
            }

            PoolStatistics stats = collectMetrics(poolMXBean, configMXBean);
            metricsHistory.offer(stats);

            // 清理过期数据
            cleanupOldMetrics();

            // 记录日志
            log.debug("连接池状态: 活跃={}, 空闲={}, 等待={}, 总数={}",
                stats.getActiveConnections(),
                stats.getIdleConnections(),
                stats.getPendingThreads(),
                stats.getTotalConnections());

            // 检查是否需要扩缩容
            checkAndScale(stats);

            // 检查告警条件
            checkAlerts(stats);

        } catch (Exception e) {
            log.error("连接池监控失败", e);
        }
    }

    private PoolStatistics collectMetrics(HikariPoolMXBean poolMXBean, 
                                          HikariConfigMXBean configMXBean) {
        return PoolStatistics.builder()
            .timestamp(System.currentTimeMillis())
            .activeConnections(poolMXBean.getActiveConnections())
            .idleConnections(poolMXBean.getIdleConnections())
            .totalConnections(poolMXBean.getTotalConnections())
            .pendingThreads(poolMXBean.getThreadsAwaitingConnection())
            .maxPoolSize(configMXBean.getMaximumPoolSize())
            .minIdleConnections(configMXBean.getMinimumIdle())
            .usagePercentage(calculateUsagePercentage(poolMXBean, configMXBean))
            .build();
    }

    private double calculateUsagePercentage(HikariPoolMXBean poolMXBean, 
                                           HikariConfigMXBean configMXBean) {
        int maxSize = configMXBean.getMaximumPoolSize();
        if (maxSize == 0) {
            return 0;
        }
        return (double) poolMXBean.getActiveConnections() / maxSize * 100;
    }

    private void cleanupOldMetrics() {
        long cutoff = System.currentTimeMillis() - 
            TimeUnit.MINUTES.toMillis(config.getMonitor().getMetricsRetentionMinutes());
        
        while (!metricsHistory.isEmpty() && 
               metricsHistory.peek().getTimestamp() < cutoff) {
            metricsHistory.poll();
        }
    }

    private void checkAndScale(PoolStatistics stats) {
        ScaleDecision decision = poolScaler.evaluate(stats, getRecentMetrics());
        
        if (decision.isShouldScale()) {
            log.info("触发连接池{}: 当前={}, 目标={}, 原因={}",
                decision.getScaleType() == ScaleDecision.ScaleType.UP ? "扩容" : "缩容",
                stats.getMaxPoolSize(),
                decision.getTargetPoolSize(),
                decision.getReason());

            boolean success = poolScaler.executeScale(decision);
            
            if (success) {
                notificationService.notifyScale(decision, stats);
            }
        }
    }

    private void checkAlerts(PoolStatistics stats) {
        // 高使用率告警
        if (stats.getUsagePercentage() > config.getAlert().getHighUsageThreshold()) {
            notificationService.notifyHighUsage(stats);
        }

        // 高等待队列告警
        if (stats.getPendingThreads() > 0) {
            notificationService.notifyQueueWait(stats);
        }
    }

    public List<PoolStatistics> getRecentMetrics() {
        return new ArrayList<>(metricsHistory);
    }

    public PoolStatistics getCurrentMetrics() {
        List<PoolStatistics> metrics = getRecentMetrics();
        return metrics.isEmpty() ? null : metrics.get(metrics.size() - 1);
    }

}

4. 连接池扩缩容器

@Component
@Slf4j
public class PoolScaler {

    @Autowired
    private HikariDataSource dataSource;

    @Autowired
    private DynamicPoolConfig config;

    private final AtomicLong lastScaleTime = new AtomicLong(0);
    private final AtomicInteger consecutiveLowUsage = new AtomicInteger(0);

    public ScaleDecision evaluate(PoolStatistics current, List<PoolStatistics> history) {
        // 检查冷却时间
        long cooldownMillis = TimeUnit.SECONDS.toMillis(
            config.getScaleUp().getCooldownSeconds());
        if (System.currentTimeMillis() - lastScaleTime.get() < cooldownMillis) {
            return ScaleDecision.noScale("冷却期内,跳过评估");
        }

        // 评估扩容
        ScaleDecision scaleUpDecision = evaluateScaleUp(current, history);
        if (scaleUpDecision.isShouldScale()) {
            return scaleUpDecision;
        }

        // 评估缩容
        return evaluateScaleDown(current, history);
    }

    private ScaleDecision evaluateScaleUp(PoolStatistics current, List<PoolStatistics> history) {
        if (!config.getScaleUp().isEnabled()) {
            return ScaleDecision.noScale("扩容已禁用");
        }

        int currentMax = current.getMaxPoolSize();
        int absoluteMax = config.getScaleUp().getMaxPoolSize();

        // 已达到上限
        if (currentMax >= absoluteMax) {
            return ScaleDecision.noScale("已达到最大连接数上限: " + absoluteMax);
        }

        // 使用率超过阈值
        boolean highUsage = current.getUsagePercentage() > 
            config.getScaleUp().getThresholdPercentage();

        // 有等待线程
        boolean hasQueue = current.getPendingThreads() >= 
            config.getScaleUp().getQueueLengthThreshold();

        if (highUsage || hasQueue) {
            int increment = config.getScaleUp().getIncrementSize();
            int targetSize = Math.min(currentMax + increment, absoluteMax);

            String reason = String.format("使用率%.1f%% %s 等待队列%d",
                current.getUsagePercentage(),
                highUsage ? "超过阈值" : "正常",
                current.getPendingThreads());

            return ScaleDecision.scaleUp(targetSize, reason);
        }

        return ScaleDecision.noScale("未达到扩容条件");
    }

    private ScaleDecision evaluateScaleDown(PoolStatistics current, List<PoolStatistics> history) {
        if (!config.getScaleDown().isEnabled()) {
            return ScaleDecision.noScale("缩容已禁用");
        }

        int currentMax = current.getMaxPoolSize();
        int absoluteMin = config.getScaleDown().getMinPoolSize();

        // 已达到下限
        if (currentMax <= absoluteMin) {
            consecutiveLowUsage.set(0);
            return ScaleDecision.noScale("已达到最小连接数下限: " + absoluteMin);
        }

        // 使用率低于阈值
        if (current.getUsagePercentage() < config.getScaleDown().getThresholdPercentage()) {
            int count = consecutiveLowUsage.incrementAndGet();
            int requiredCount = config.getScaleDown().getIdleDurationMinutes() * 6; // 每10秒检查一次

            if (count >= requiredCount) {
                int decrement = config.getScaleDown().getDecrementSize();
                int targetSize = Math.max(currentMax - decrement, absoluteMin);

                consecutiveLowUsage.set(0);
                return ScaleDecision.scaleDown(targetSize, 
                    String.format("使用率持续%.1f%%低于阈值%d%%", 
                        current.getUsagePercentage(),
                        config.getScaleDown().getThresholdPercentage()));
            }
        } else {
            consecutiveLowUsage.set(0);
        }

        return ScaleDecision.noScale("未达到缩容条件");
    }

    public boolean executeScale(ScaleDecision decision) {
        try {
            HikariConfigMXBean configMXBean = dataSource.getHikariConfigMXBean();
            int currentSize = configMXBean.getMaximumPoolSize();
            int targetSize = decision.getTargetPoolSize();

            if (currentSize == targetSize) {
                return false;
            }

            // 修改最大连接数
            configMXBean.setMaximumPoolSize(targetSize);
            
            // 同时调整最小空闲连接
            if (decision.getScaleType() == ScaleDecision.ScaleType.UP) {
                int newMinIdle = Math.min(targetSize, configMXBean.getMinimumIdle() + 5);
                configMXBean.setMinimumIdle(newMinIdle);
            } else {
                int newMinIdle = Math.max(config.getScaleDown().getMinPoolSize(), 
                    configMXBean.getMinimumIdle() - 5);
                configMXBean.setMinimumIdle(newMinIdle);
            }

            lastScaleTime.set(System.currentTimeMillis());
            
            log.info("连接池{}完成: {} -> {}",
                decision.getScaleType() == ScaleDecision.ScaleType.UP ? "扩容" : "缩容",
                currentSize, targetSize);

            return true;

        } catch (Exception e) {
            log.error("执行连接池扩缩容失败", e);
            return false;
        }
    }

}

5. 扩缩容决策类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ScaleDecision {

    private boolean shouldScale;
    private ScaleType scaleType;
    private int targetPoolSize;
    private String reason;

    public enum ScaleType {
        UP, DOWN
    }

    public static ScaleDecision noScale(String reason) {
        return ScaleDecision.builder()
            .shouldScale(false)
            .reason(reason)
            .build();
    }

    public static ScaleDecision scaleUp(int targetSize, String reason) {
        return ScaleDecision.builder()
            .shouldScale(true)
            .scaleType(ScaleType.UP)
            .targetPoolSize(targetSize)
            .reason(reason)
            .build();
    }

    public static ScaleDecision scaleDown(int targetSize, String reason) {
        return ScaleDecision.builder()
            .shouldScale(true)
            .scaleType(ScaleType.DOWN)
            .targetPoolSize(targetSize)
            .reason(reason)
            .build();
    }

}

6. 连接池指标统计

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PoolStatistics {

    private long timestamp;
    private int activeConnections;
    private int idleConnections;
    private int totalConnections;
    private int pendingThreads;
    private int maxPoolSize;
    private int minIdleConnections;
    private double usagePercentage;

    public int getQueueLength() {
        return pendingThreads;
    }

    public boolean isHealthy() {
        return usagePercentage < 80 && pendingThreads == 0;
    }

    public boolean isCritical() {
        return usagePercentage > 90 || pendingThreads > 10;
    }

}

7. 连接池管理接口

@RestController
@RequestMapping("/api/pool")
@Slf4j
public class PoolController {

    @Autowired
    private ConnectionPoolMonitor poolMonitor;

    @Autowired
    private HikariDataSource dataSource;

    @Autowired
    private PoolScaler poolScaler;

    @GetMapping("/status")
    public ApiResponse<Map<String, Object>> getStatus() {
        Map<String, Object> status = new HashMap<>();
        
        PoolStatistics current = poolMonitor.getCurrentMetrics();
        if (current != null) {
            status.put("current", current);
        }

        HikariConfigMXBean configMXBean = dataSource.getHikariConfigMXBean();
        status.put("maxPoolSize", configMXBean.getMaximumPoolSize());
        status.put("minIdle", configMXBean.getMinimumIdle());

        return ApiResponse.success(status);
    }

    @GetMapping("/metrics/history")
    public ApiResponse<List<PoolStatistics>> getMetricsHistory() {
        return ApiResponse.success(poolMonitor.getRecentMetrics());
    }

    @PostMapping("/scale")
    public ApiResponse<String> manualScale(@RequestParam int targetSize) {
        try {
            HikariConfigMXBean configMXBean = dataSource.getHikariConfigMXBean();
            int currentSize = configMXBean.getMaximumPoolSize();
            
            configMXBean.setMaximumPoolSize(targetSize);
            
            return ApiResponse.success(String.format("连接池已调整: %d -> %d", 
                currentSize, targetSize));
        } catch (Exception e) {
            log.error("手动调整连接池失败", e);
            return ApiResponse.error("调整失败: " + e.getMessage());
        }
    }

    @GetMapping("/config")
    public ApiResponse<Map<String, Object>> getConfig() {
        Map<String, Object> config = new HashMap<>();
        
        HikariConfigMXBean configMXBean = dataSource.getHikariConfigMXBean();
        config.put("maximumPoolSize", configMXBean.getMaximumPoolSize());
        config.put("minimumIdle", configMXBean.getMinimumIdle());
        config.put("connectionTimeout", configMXBean.getConnectionTimeout());
        config.put("idleTimeout", configMXBean.getIdleTimeout());
        config.put("maxLifetime", configMXBean.getMaxLifetime());

        return ApiResponse.success(config);
    }

}

8. 通知服务

@Service
@Slf4j
public class NotificationService {

    @Autowired
    private DynamicPoolConfig config;

    public void notifyScale(ScaleDecision decision, PoolStatistics stats) {
        if (!config.getAlert().isEnabled()) {
            return;
        }

        String message = String.format(
            "【连接池%s通知】\n时间: %s\n类型: %s\n目标大小: %d\n当前状态: 活跃=%d, 空闲=%d, 等待=%d\n原因: %s",
            decision.getScaleType() == ScaleDecision.ScaleType.UP ? "扩容" : "缩容",
            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
            decision.getScaleType(),
            decision.getTargetPoolSize(),
            stats.getActiveConnections(),
            stats.getIdleConnections(),
            stats.getPendingThreads(),
            decision.getReason()
        );

        log.info(message);

        // 这里可以集成邮件、钉钉等通知渠道
        sendNotification(message);
    }

    public void notifyHighUsage(PoolStatistics stats) {
        String message = String.format(
            "【连接池高使用率告警】\n使用率: %.1f%%\n活跃连接: %d\n最大连接: %d\n等待队列: %d",
            stats.getUsagePercentage(),
            stats.getActiveConnections(),
            stats.getMaxPoolSize(),
            stats.getPendingThreads()
        );

        log.warn(message);
        sendNotification(message);
    }

    public void notifyQueueWait(PoolStatistics stats) {
        String message = String.format(
            "【连接池等待队列告警】\n等待线程: %d\n活跃连接: %d\n空闲连接: %d",
            stats.getPendingThreads(),
            stats.getActiveConnections(),
            stats.getIdleConnections()
        );

        log.warn(message);
        sendNotification(message);
    }

    private void sendNotification(String message) {
        // 实现邮件、钉钉等通知逻辑
        // TODO: 集成实际的通知渠道
    }

}

9. 数据源配置

@Configuration
@Slf4j
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public HikariDataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setPoolName("DynamicHikariPool");
        
        // 注册 MBean 以便监控
        dataSource.setRegisterMbeans(true);
        
        log.info("初始化 HikariCP 连接池");
        return dataSource;
    }

}

四、高级功能

1. 连接池性能测试

@Service
@Slf4j
public class SimulationService {

    @Autowired
    private UserService userService;

    @Autowired
    private ExecutorService executorService;

    public void simulateHighLoad(int concurrentRequests, int durationSeconds) {
        log.info("开始模拟高并发场景: {} 并发, {} 秒", concurrentRequests, durationSeconds);

        CountDownLatch latch = new CountDownLatch(concurrentRequests);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);

        long endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(durationSeconds);

        for (int i = 0; i < concurrentRequests; i++) {
            executorService.submit(() -> {
                try {
                    while (System.currentTimeMillis() < endTime) {
                        try {
                            userService.getAllUsers();
                            successCount.incrementAndGet();
                        } catch (Exception e) {
                            failCount.incrementAndGet();
                        }
                        
                        // 模拟随机间隔
                        Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100));
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        log.info("模拟完成: 成功={}, 失败={}", successCount.get(), failCount.get());
    }

}

2. 连接池指标导出

@Component
@Slf4j
public class ConnectionPoolMetrics {

    @Autowired
    private HikariDataSource dataSource;

    @Autowired
    private MeterRegistry meterRegistry;

    @PostConstruct
    public void init() {
        // 活跃连接数
        Gauge.builder("hikaricp.connections.active", 
                () -> getPoolMXBean().getActiveConnections())
            .description("活跃连接数")
            .register(meterRegistry);

        // 空闲连接数
        Gauge.builder("hikaricp.connections.idle", 
                () -> getPoolMXBean().getIdleConnections())
            .description("空闲连接数")
            .register(meterRegistry);

        // 等待线程数
        Gauge.builder("hikaricp.connections.pending", 
                () -> getPoolMXBean().getThreadsAwaitingConnection())
            .description("等待连接的线程数")
            .register(meterRegistry);

        // 连接使用率
        Gauge.builder("hikaricp.connections.usage", this::getUsagePercentage)
            .description("连接使用率")
            .register(meterRegistry);

        // 最大连接数
        Gauge.builder("hikaricp.connections.max", 
                () -> getConfigMXBean().getMaximumPoolSize())
            .description("最大连接数")
            .register(meterRegistry);
    }

    private HikariPoolMXBean getPoolMXBean() {
        return dataSource.getHikariPoolMXBean();
    }

    private HikariConfigMXBean getConfigMXBean() {
        return dataSource.getHikariConfigMXBean();
    }

    private double getUsagePercentage() {
        HikariPoolMXBean poolMXBean = getPoolMXBean();
        HikariConfigMXBean configMXBean = getConfigMXBean();
        
        int maxSize = configMXBean.getMaximumPoolSize();
        if (maxSize == 0) {
            return 0;
        }
        return (double) poolMXBean.getActiveConnections() / maxSize * 100;
    }

}

五、最佳实践

1. 连接池参数设置建议

参数开发环境测试环境生产环境说明
minimum-idle51010-20根据业务低峰期连接需求
maximum-pool-size205050-100根据数据库承载能力
connection-timeout300003000010000-30000获取连接等待时间
idle-timeout600000600000300000-600000空闲连接回收时间
max-lifetime180000018000001800000连接最大生命周期

2. 扩容策略建议

# 保守策略(适合稳定业务)
scale-up:
  threshold-percentage: 85
  increment-size: 5
  max-pool-size: 50

# 激进策略(适合大促场景)
scale-up:
  threshold-percentage: 70
  increment-size: 20
  max-pool-size: 200

3. 监控告警阈值

告警类型阈值级别
高使用率> 90%严重
使用率偏高> 80%警告
等待队列> 10严重
扩容频繁5分钟内 > 3次警告

六、常见问题

Q1: 动态修改连接池配置会影响正在执行的请求吗?

A:

  • 增加最大连接数:不会影响现有请求,新请求可以使用增加的连接
  • 减少最大连接数:不会强制关闭活跃连接,但空闲连接会被回收
  • 建议:缩容时逐步减少,避免对业务造成影响

Q2: 连接池扩到很大会有什么问题?

A:

  • 数据库端连接数限制
  • 内存占用增加
  • 上下文切换开销增大
  • 建议:根据数据库实际承载能力设置上限

Q3: 如何确定连接池的合理大小?

A:

  • 公式:连接数 = ((核心数 × 2) + 有效磁盘数)
  • 压测:逐步增加连接数,观察吞吐量变化
  • 监控:根据实际使用率动态调整

更多技术文章,欢迎关注公众号"服务端技术精选",及时获取最新动态。


标题:SpringBoot + 数据库连接池监控 + 动态扩容:连接不足时自动扩容,避免请求排队
作者:jiangyi
地址:http://jiangyi.space/articles/2026/03/29/1774590750902.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消