扬州市网站建设_网站建设公司_外包开发_seo优化
2026/1/16 10:49:40 网站建设 项目流程

前言

刚工作那会儿,遇到过一个诡异的问题:服务刚启动时第一批请求特别慢,好几秒才响应,之后就正常了。

查了半天发现是数据库连接的锅——每次请求都新建连接,TCP握手 + MySQL认证,一套下来几百毫秒。用上连接池后,响应时间从秒级降到毫秒级。

连接池这东西,平时不出问题感觉不到它的存在,一出问题就是大麻烦。这篇文章讲清楚原理和调优,让你以后遇到问题能快速定位。

为什么需要连接池

创建连接的代价

数据库连接不是直接就能用的,要经过:

客户端                    数据库|                        ||------- SYN --------->  |  |<------ SYN-ACK ------  |  TCP三次握手|------- ACK --------->  |  |                        ||---- 认证请求 -------->  ||<--- 认证Challenge ----  |  MySQL认证|---- 认证响应 -------->  ||<--- 认证OK ----------  ||                        |

一次连接建立,最少也要几十毫秒(局域网),跨机房可能几百毫秒。

如果每次查询都新建连接:

// 不用连接池(千万别这样写)
public User getUser(int id) {Connection conn = DriverManager.getConnection(url, user, password);  // 每次新建try {PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");ps.setInt(1, id);ResultSet rs = ps.executeQuery();// ...} finally {conn.close();  // 用完就关}
}

假设100 QPS,每个连接建立耗时50ms,光建连接就得5秒。

连接池的作用

连接池预先创建好一批连接,要用的时候借出去,用完还回来:

+--------------------+
|    连接池          |
|  +----+ +----+     |
|  |conn| |conn| ... |   <- 空闲连接
|  +----+ +----+     |
+--------------------+^      ||      v还回   借出^      ||      v
+--------------------+
|    应用代码        |
+--------------------+

好处:

  1. 避免重复建连接:几十毫秒 → 微秒级
  2. 控制连接数量:防止撑爆数据库
  3. 连接复用:一个连接可以被多个请求复用

连接池核心原理

核心数据结构

一个连接池至少要有这些东西:

class SimpleConnectionPool {// 空闲连接队列private Queue<Connection> idleConnections = new LinkedList<>();// 正在使用的连接private Set<Connection> activeConnections = new HashSet<>();// 最大连接数private int maxPoolSize = 10;// 等待获取连接的线程private Object lock = new Object();
}

获取连接的流程

public Connection getConnection(long timeout) {synchronized (lock) {long deadline = System.currentTimeMillis() + timeout;while (true) {// 1. 有空闲连接,直接返回if (!idleConnections.isEmpty()) {Connection conn = idleConnections.poll();activeConnections.add(conn);return conn;}// 2. 没达到最大数,创建新连接if (activeConnections.size() < maxPoolSize) {Connection conn = createNewConnection();activeConnections.add(conn);return conn;}// 3. 等待其他线程归还long waitTime = deadline - System.currentTimeMillis();if (waitTime <= 0) {throw new SQLException("获取连接超时");}lock.wait(waitTime);}}
}

归还连接

public void returnConnection(Connection conn) {synchronized (lock) {activeConnections.remove(conn);// 检查连接是否还有效if (isValid(conn)) {idleConnections.offer(conn);} else {conn.close();  // 坏了就丢掉}lock.notifyAll();  // 唤醒等待的线程}
}

连接有效性检测

连接可能因为各种原因失效:

  • 数据库重启
  • 网络中断
  • 连接超时被踢

所以借出前要检测:

private boolean isValid(Connection conn) {try {// 方式1:发送轻量查询Statement stmt = conn.createStatement();stmt.execute("SELECT 1");return true;} catch (SQLException e) {return false;}
}

HikariCP:最快的连接池

HikariCP是目前最快的Java连接池,SpringBoot 2.x默认用它。

为什么HikariCP快

  1. 字节码优化:用Javassist动态生成代理类,比反射快
  2. 无锁设计:用CAS代替synchronized,减少线程阻塞
  3. FastList:自定义的List实现,针对连接池场景优化
  4. 精简代码:整个核心代码只有几千行

基本配置

# application.yml
spring:datasource:hikari:# 连接池大小minimum-idle: 5          # 最小空闲连接maximum-pool-size: 20    # 最大连接数# 超时设置connection-timeout: 30000  # 获取连接超时(毫秒)idle-timeout: 600000       # 空闲连接超时(毫秒)max-lifetime: 1800000      # 连接最大存活时间(毫秒)# 连接检测connection-test-query: SELECT 1

连接池大小怎么设

这是最常被问的问题。官方有个公式:

连接数 = (核心数 * 2) + 有效磁盘数

但实际情况要复杂得多,建议从小开始逐步调整:

# 一般Web应用
最大连接数 = CPU核数 * 2 ~ 4# 例如8核服务器
maximum-pool-size: 20
minimum-idle: 5

为什么不是越大越好?

连接数太多:
├── 数据库连接数有限(MySQL默认151)
├── 每个连接都占内存(MySQL每连接约1MB)
├── 更多连接 = 更多上下文切换
└── 锁竞争更激烈

经验法则:宁可排队等连接,不要撑爆数据库

超时参数详解

hikari:# 获取连接最多等30秒connection-timeout: 30000# 空闲连接超过10分钟就关闭# 注意:要小于数据库的wait_timeoutidle-timeout: 600000# 连接最多存活30分钟,然后强制关闭重建# 防止连接时间太长出问题max-lifetime: 1800000

关键点max-lifetime 必须比数据库的 wait_timeout 小几分钟:

-- 查看MySQL的wait_timeout
SHOW VARIABLES LIKE 'wait_timeout';
-- 默认28800秒(8小时)

如果 max-lifetime > wait_timeout,数据库会先把连接断掉,连接池不知道,就会拿到死连接。

监控指标

HikariCP暴露了很多指标,配合Prometheus很好用:

// 开启指标
HikariConfig config = new HikariConfig();
config.setMetricRegistry(new PrometheusMeterRegistry(...));

关键指标:

# 活跃连接数
hikaricp_connections_active# 空闲连接数
hikaricp_connections_idle# 等待获取连接的线程数
hikaricp_connections_pending# 获取连接耗时
hikaricp_connections_acquire_seconds

告警规则

groups:
- name: hikari-alertsrules:- alert: ConnectionPoolExhaustedexpr: hikaricp_connections_pending > 0for: 1mannotations:summary: "连接池耗尽,有线程在等待"- alert: ConnectionAcquireSlowexpr: hikaricp_connections_acquire_seconds_max > 1for: 5mannotations:summary: "获取连接超过1秒"

常见问题排查

问题1:Connection is not available

HikariPool-1 - Connection is not available, request timed out after 30000ms

原因:连接池满了,30秒内没拿到连接。

排查:

-- 看数据库实际连接数
SHOW STATUS LIKE 'Threads_connected';-- 看连接来源
SELECT * FROM information_schema.processlist;

可能原因:

  1. 连接池太小
  2. 有慢查询占着连接不放
  3. 连接泄漏(借出去没还)

问题2:连接泄漏

连接借出去忘了还,池子里的连接越来越少。

HikariCP有泄漏检测:

hikari:leak-detection-threshold: 60000  # 连接借出超过60秒就报警

日志会显示借出连接的堆栈,定位泄漏代码:

ProxyLeakTask - Connection leak detection triggered for conn0at com.example.UserService.getUser(UserService.java:42)at ...

问题3:连接被数据库断开

Communications link failure
The last packet successfully received from the server was xxx milliseconds ago

原因:连接闲置太久,被数据库踢了。

解决:

hikari:# 定期发心跳保活keepalive-time: 30000  # 每30秒发一次心跳# 或者让连接在数据库踢之前主动关闭max-lifetime: 1700000  # 小于wait_timeout

问题4:启动时连接失败

服务启动时数据库还没好,连接失败:

hikari:# 初始化时允许失败initialization-fail-timeout: -1# 慢慢重试connection-timeout: 30000

或者用优雅启动,等数据库好了再接入流量。

多数据源配置

实际项目经常要连多个库:

@Configuration
public class DataSourceConfig {@Bean@Primary@ConfigurationProperties("spring.datasource.primary.hikari")public DataSource primaryDataSource() {return DataSourceBuilder.create().type(HikariDataSource.class).build();}@Bean@ConfigurationProperties("spring.datasource.secondary.hikari")public DataSource secondaryDataSource() {return DataSourceBuilder.create().type(HikariDataSource.class).build();}
}
spring:datasource:primary:hikari:jdbc-url: jdbc:mysql://master:3306/dbmaximum-pool-size: 20secondary:hikari:jdbc-url: jdbc:mysql://slave:3306/dbmaximum-pool-size: 10

读写分离

public class RoutingDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master";}
}

生产经验

经验1:监控先行

上线前先把监控加上。连接池问题往往是间歇性的,没监控数据很难定位。

经验2:压测确定参数

不同业务对连接池需求不同。用压测工具(JMeter、wrk)在真实负载下调整参数。

经验3:分环境配置

# 开发环境
spring:profiles: devdatasource:hikari:maximum-pool-size: 5# 生产环境
spring:profiles: proddatasource:hikari:maximum-pool-size: 30

经验4:多机房注意网络

我们有跨机房数据库访问的场景,连接建立延迟高。这种情况下:

  1. 适当增大 connection-timeout
  2. 用更激进的预热策略
  3. 我们用星空组网把两边网络打通后,延迟稳定很多

总结

连接池的核心就三件事:

功能 配置项 建议值
池大小 maximum-pool-size CPU核数 * 2 ~ 4
超时控制 connection-timeout 30秒
连接存活 max-lifetime < 数据库wait_timeout

记住几个原则:

  1. 连接数不是越多越好,够用就行
  2. 监控比调参重要,先能看到问题
  3. 连接泄漏是大忌,用框架自动管理
  4. 超时要协调,连接池和数据库要配合

连接池配好了是透明的,配不好就是定时炸弹。希望这篇文章能帮你理解原理,遇到问题时知道往哪个方向排查。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询