Home 据库连接池原理与设计思想
Post
Cancel

据库连接池原理与设计思想

一、为什么需要数据库连接池

数据库连接建立成本非常高:

  • 需要 网络三次握手 + 身份认证 + 权限检查 + 内存结构初始化
  • 单次建立连接可能耗时 5ms~100ms
  • 高并发场景若频繁 open/close connection 会导致系统吞吐急剧下降

连接池目标:

  • 预先建立一定数量的连接保留在池中
  • 应用程序需要时直接从池子获取
  • 用完后不是关闭,而是归还池中复用

二、数据库连接池核心设计思想

组件 作用
连接池(Pool) 保存连接对象的容器(可使用 BlockingQueue)
连接对象(Connection Wrapper) 真正 JDBC 连接的代理包装,用于监听 close 方法
连接工厂(ConnectionFactory) 负责新建数据库连接
空闲检测与健康检查 定期检查超时连接、bad 连接,并回收
扩缩容策略 动态扩容到 maxPoolSize,空闲时收缩
等待队列 / 超时机制 若无可用连接,阻塞等待 / 超时失败

伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package pool;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class MyConnectionPool {

    private final String jdbcUrl;
    private final String username;
    private final String password;

    private final int maxPoolSize;
    private final int initPoolSize;
    private final long waitTimeout;

    private final BlockingQueue<Connection> pool;

    public MyConnectionPool(String jdbcUrl, String username, String password,
                            int initPoolSize, int maxPoolSize, long waitTimeout) throws Exception {

        this.jdbcUrl = jdbcUrl;
        this.username = username;
        this.password = password;
        this.initPoolSize = initPoolSize;
        this.maxPoolSize = maxPoolSize;
        this.waitTimeout = waitTimeout;
        this.pool = new ArrayBlockingQueue<>(maxPoolSize);

        init();
    }

    private void init() throws Exception {
        for (int i = 0; i < initPoolSize; i++) {
            pool.offer(createRealConnection());
        }
        System.out.println("Connection pool initialized, size=" + initPoolSize);
    }

    /** 创建真实 JDBC 连接 */
    private Connection createRealConnection() throws Exception {
        return DriverManager.getConnection(jdbcUrl, username, password);
    }

    /** 获取连接(可能需要等待) */
    public Connection getConnection() throws Exception {
        Connection conn = pool.poll();
        if (conn != null) {
            return wrap(conn);
        }

        synchronized (pool) {
            if (pool.size() < maxPoolSize) {
                return wrap(createRealConnection());
            }
        }

        conn = pool.poll(waitTimeout, TimeUnit.MILLISECONDS);
        if (conn == null) {
            throw new RuntimeException("Timeout: failed to get DB connection");
        }

        return wrap(conn);
    }

    /** 包装 connection,拦截 close() 并归还到 pool */
    private Connection wrap(Connection realConnection) {
        return (Connection) Proxy.newProxyInstance(
                realConnection.getClass().getClassLoader(),
                new Class[]{Connection.class},
                (proxy, method, args) -> {
                    if ("close".equals(method.getName())) {
                        pool.offer(realConnection);
                        return null;
                    }
                    return method.invoke(realConnection, args);
                }
        );
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pool.MyConnectionPool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class TestPool {
  public static void main(String[] args) throws Exception {

    MyConnectionPool pool = new MyConnectionPool(
      "jdbc:mysql://localhost:3306/test",
      "root",
      "123456",
      3,
      10,
      2000
    );

    Connection conn = pool.getConnection();

    PreparedStatement stmt = conn.prepareStatement("SELECT 1");
    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
      System.out.println("result = " + rs.getInt(1));
    }

    conn.close(); // 实际不会关闭,而是归还连接
  }
}

三、连接池的高级特性

  • 连接有效性检查
  • 空闲连接回收
  • 连接泄漏检测
  • 动态扩缩容
  • 连接池监控
  • 连接池配置

以下示例空闲连接回收,单独一个线程定时检查空闲连接,并关闭超时或坏的连接

1
2
3
4
5
6
7
8
9
10
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
    for (Connection conn : pool) {
        if (System.currentTimeMillis() - lastUsedTime(conn) > idleTimeout) {
            pool.remove(conn);
            conn.close();
            currentTotal.decrementAndGet();
        }
    }
}, 30, 30, TimeUnit.SECONDS);

weixin.png

公众号名称:怪味Coding
微信扫码关注或搜索公众号名称
This post is licensed under CC BY 4.0 by the author.