引言
数据库连接池是应用性能的关键组件。不当的连接池配置会导致连接泄漏、性能瓶颈甚至系统崩溃。本文将深入讲解连接池的工作原理,并提供生产环境的优化方案。
连接池工作原理
为什么需要连接池
无连接池:
每次请求 → 创建TCP连接 → 执行SQL → 关闭连接
↓
高延迟(TCP握手 + 认证)
资源浪费(频繁创建/销毁)
连接数爆炸(高并发时)
有连接池:
应用启动 → 创建N个连接 → 复用连接 → 归还连接
↓
低延迟(复用已建立连接)
资源可控(固定连接数)
高并发支持(连接复用)
连接池生命周期
1. 初始化阶段
- 创建最小连接数(minIdle)
- 验证连接可用性
2. 运行阶段
- 请求获取连接
- 连接不足时扩展(直到maxActive)
- 空闲连接回收(超过minIdle)
3. 健康检查
- 定期验证连接(testWhileIdle)
- 剔除失效连接
- 连接泄漏检测
4. 关闭阶段
- 优雅关闭所有连接
- 等待活跃请求完成
HikariCP配置优化
基础配置
# application.yml
spring:
datasource:
hikari:
# 连接池大小
minimum-idle: 10
maximum-pool-size: 50
# 连接超时
connection-timeout: 30000 # 30秒
idle-timeout: 600000 # 10分钟
max-lifetime: 1800000 # 30分钟
# 连接验证
connection-test-query: SELECT 1
validation-timeout: 5000
# 池名称(便于监控)
pool-name: "OrderServicePool"
# 泄漏检测
leak-detection-threshold: 60000 # 60秒未归还视为泄漏
连接池大小计算
/**
* 连接池大小计算公式(PostgreSQL官方推荐)
*
* connections = ((core_count * 2) + effective_spindle_count)
*
* core_count: CPU核心数
* effective_spindle_count: 磁盘数(SSD视为0,HDD视为磁盘数)
*
* 示例:8核CPU + SSD
* connections = (8 * 2) + 0 = 16
*
* 注意:这是经验值,需要根据实际负载调整
*/
public class ConnectionPoolCalculator {
public static int calculatePoolSize(int cpuCores, boolean isSSD, int concurrentRequests) {
// 基础值:CPU核心数 * 2
int baseSize = cpuCores * 2;
// 如果是HDD,增加磁盘数
if (!isSSD) {
baseSize += 4; // 假设4块HDD
}
// 考虑并发请求
int estimatedSize = Math.max(baseSize, concurrentRequests / 10);
// 限制范围:10-100
return Math.min(Math.max(estimatedSize, 10), 100);
}
public static void main(String[] args) {
// 8核CPU,SSD,预期500并发请求
int poolSize = calculatePoolSize(8, true, 500);
System.out.println("推荐连接池大小: " + poolSize); // 输出: 50
}
}
高级配置
@Configuration
public class HikariConfig {
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource dataSource() {
HikariConfig config = new HikariConfig();
// 数据库连接信息
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("user");
config.setPassword("password");
// 连接池大小
config.setMinimumIdle(10);
config.setMaximumPoolSize(50);
// 超时配置
config.setConnectionTimeout(30000); // 获取连接超时
config.setIdleTimeout(600000); // 空闲连接超时
config.setMaxLifetime(1800000); // 连接最大生命周期
// 性能优化
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
// 连接验证
config.setConnectionTestQuery("SELECT 1");
config.setValidationTimeout(5000);
// 泄漏检测
config.setLeakDetectionThreshold(60000);
// 监控指标
config.setMetricTracker(new HikariCPTaskMetrics());
return new HikariDataSource(config);
}
}
Druid连接池
配置示例
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 基础配置
initial-size: 10
min-idle: 10
max-active: 50
max-wait: 60000
# 连接检测
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 空闲连接回收
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
# SQL监控
filters: stat,wall,slf4j
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin123
allow: 127.0.0.1
# Web监控
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
SQL监控
@Configuration
public class DruidMonitorConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 白名单
bean.addInitParameter("allow", "127.0.0.1,192.168.1.0/24");
// 黑名单
bean.addInitParameter("deny", "192.168.1.100");
// 登录账号密码
bean.addInitParameter("loginUsername", "admin");
bean.addInitParameter("loginPassword", "admin123");
// 禁用重置功能
bean.addInitParameter("resetEnable", "false");
return bean;
}
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
bean.addUrlPatterns("/*");
bean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.css,/druid/*");
return bean;
}
}
pgBouncer(PostgreSQL专用)
配置文件
; pgbouncer.ini
[databases]
mydb = host=localhost port=5432 dbname=mydb
[pgbouncer]
; 监听地址
listen_addr = 0.0.0.0
listen_port = 6432
; 认证
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
; 连接池模式
; session: 会话级(每个客户端会话一个连接)
; transaction: 事务级(每个事务一个连接,推荐)
; statement: 语句级(每条SQL一个连接,不推荐)
pool_mode = transaction
; 连接池大小
max_client_conn = 1000
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
reserve_pool_timeout = 5
; 超时配置
server_idle_timeout = 600
server_lifetime = 3600
server_connect_timeout = 15
client_idle_timeout = 0
query_timeout = 0
client_login_timeout = 60
; 日志
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
stats_period = 60
用户认证文件
; userlist.txt
"postgres" "md5hashedpassword"
"appuser" "md5hashedpassword"
# 生成MD5密码
echo -n "password" | md5sum
# 或
psql -c "SELECT 'md5' || md5('password' || 'username')"
Docker部署
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgrespass
volumes:
- postgres_data:/var/lib/postgresql/data
pgbouncer:
image: edoburu/pgbouncer:latest
environment:
DATABASE_URL: postgres://postgres:postgrespass@postgres:5432/mydb
POOL_MODE: transaction
MAX_CLIENT_CONN: 1000
DEFAULT_POOL_SIZE: 50
MIN_POOL_SIZE: 10
RESERVE_POOL_SIZE: 10
SERVER_IDLE_TIMEOUT: 600
SERVER_LIFETIME: 3600
ports:
- "6432:6432"
depends_on:
- postgres
volumes:
postgres_data:
连接泄漏检测
自定义泄漏检测器
@Component
public class ConnectionLeakDetector {
private final HikariDataSource dataSource;
private final Map<Connection, StackTraceElement[]> borrowedConnections =
new ConcurrentHashMap<>();
public ConnectionLeakDetector(HikariDataSource dataSource) {
this.dataSource = dataSource;
startLeakDetection();
}
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void detectLeaks() {
long threshold = 60000; // 60秒
long now = System.currentTimeMillis();
borrowedConnections.forEach((conn, stackTrace) -> {
// 检查连接是否长时间未归还
if (isConnectionBorrowedTooLong(conn, threshold)) {
log.error("Connection leak detected!",
new ConnectionLeakException(stackTrace));
// 强制关闭泄漏的连接
forceCloseConnection(conn);
}
});
}
public Connection getConnection() throws SQLException {
Connection conn = dataSource.getConnection();
// 记录借用连接的堆栈
borrowedConnections.put(conn, Thread.currentThread().getStackTrace());
// 包装连接,归还时移除记录
return new ConnectionWrapper(conn) {
@Override
public void close() throws SQLException {
borrowedConnections.remove(this);
super.close();
}
};
}
private void forceCloseConnection(Connection conn) {
try {
conn.close();
borrowedConnections.remove(conn);
} catch (SQLException e) {
log.error("Failed to force close connection", e);
}
}
}
public class ConnectionLeakException extends RuntimeException {
public ConnectionLeakException(StackTraceElement[] stackTrace) {
super("Connection was borrowed but never returned");
setStackTrace(stackTrace);
}
}
性能监控
Prometheus指标
@Component
public class ConnectionPoolMetrics {
private final HikariDataSource dataSource;
private final MeterRegistry meterRegistry;
public ConnectionPoolMetrics(HikariDataSource dataSource,
MeterRegistry meterRegistry) {
this.dataSource = dataSource;
this.meterRegistry = meterRegistry;
registerMetrics();
}
private void registerMetrics() {
Gauge.builder("hikari.connections.active",
dataSource, ds -> ds.getHikariPoolMXBean().getActiveConnections())
.description("Active connections")
.register(meterRegistry);
Gauge.builder("hikari.connections.idle",
dataSource, ds -> ds.getHikariPoolMXBean().getIdleConnections())
.description("Idle connections")
.register(meterRegistry);
Gauge.builder("hikari.connections.total",
dataSource, ds -> ds.getHikariPoolMXBean().getTotalConnections())
.description("Total connections")
.register(meterRegistry);
Gauge.builder("hikari.connections.pending",
dataSource, ds -> ds.getHikariPoolMXBean().getThreadsAwaitingConnection())
.description("Threads awaiting connection")
.register(meterRegistry);
}
}
Grafana仪表板查询
# 连接池使用率
hikari_connections_active / hikari_connections_total * 100
# 等待连接的线程数
hikari_connections_pending
# 连接获取延迟
rate(hikari_connections_acquire_seconds_sum[5m])
/ rate(hikari_connections_acquire_seconds_count[5m])
# 连接使用时长
rate(hikari_connections_usage_seconds_sum[5m])
/ rate(hikari_connections_usage_seconds_count[5m])
性能基准测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConnectionPoolBenchmark {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void benchmarkConnectionPool() throws InterruptedException {
int threads = 100;
int requestsPerThread = 100;
ExecutorService executor = Executors.newFixedThreadPool(threads);
CountDownLatch latch = new CountDownLatch(threads * requestsPerThread);
long startTime = System.currentTimeMillis();
for (int i = 0; i < threads * requestsPerThread; i++) {
executor.submit(() -> {
try {
jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM users",
Integer.class
);
} finally {
latch.countDown();
}
});
}
latch.await();
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
double avgLatency = (double) totalTime / (threads * requestsPerThread);
double throughput = (threads * requestsPerThread * 1000.0) / totalTime;
System.out.printf("Total time: %dms%n", totalTime);
System.out.printf("Average latency: %.2fms%n", avgLatency);
System.out.printf("Throughput: %.2f requests/sec%n", throughput);
executor.shutdown();
}
}
常见问题与解决方案
1. 连接泄漏
症状:
- 连接池耗尽
- "Connection is not available"错误
- 应用响应变慢
排查:
1. 启用泄漏检测(leak-detection-threshold)
2. 检查未关闭的连接
3. 使用try-with-resources确保关闭
解决方案:
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
// 处理结果
} // 自动关闭所有资源
2. 连接池过小
症状:
- 大量线程等待连接
- hikari.connections.pending > 0
- 应用吞吐量低
排查:
1. 监控活跃连接数
2. 检查等待线程数
3. 分析并发请求量
解决方案:
- 增加maximum-pool-size
- 优化SQL减少连接占用时间
- 考虑读写分离分担负载
3. 连接超时
症状:
- "Connection timeout"错误
- 数据库响应慢
排查:
1. 检查数据库性能
2. 检查网络延迟
3. 检查慢查询
解决方案:
- 优化慢查询
- 增加connection-timeout
- 检查数据库配置
总结
连接池优化核心要点:
合理配置池大小
- 基于CPU核心数和磁盘类型计算
- 根据实际负载调整
- 避免过大(资源浪费)或过小(性能瓶颈)
超时配置
- connection-timeout: 获取连接超时(30秒)
- idle-timeout: 空闲连接回收(10分钟)
- max-lifetime: 连接最大生命周期(30分钟)
健康检查
- 定期验证连接有效性
- 启用泄漏检测
- 监控连接池指标
性能监控
- 活跃连接数
- 等待线程数
- 连接获取延迟
- 连接使用时长
延伸阅读
- HikariCP GitHub
- About Pool Sizing - HikariCP
- pgBouncer Documentation
- Druid Wiki
- PostgreSQL Connection Pooling
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。