唯's Blog

笔者是一个热爱编程的 Java 程序员。

0%

数据库连接池特性

数据库连接池的模块设计

DriverManager 与 DataSource 的区别
  • DriverManger 使用 getConnection 获取数据库的连接,本质是与数据库建立连接

    • 建立数据库连接操作是很耗费资源的操作,产生较大的系统开销
  • 因此有了连接池的引用,使用池化思想:

    • 提前创建好一些连接,供使用
    • 将创建的资源缓存起来,避免频繁的创建连接,等待下一个使用者使用
  • DataSource 的引入

    • 本质是提供数据库连接的服务类(数据源)
    • 对DriverManger进行封装,建立一个中间层(DateSource),将DriverManger创建的连接存入Pool,外部从Pool获取连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23





应用程序 -> DriverManager



--------------------------------------

--------------------------------------



DriverManager



应用程序 -> DataSource -> Pool

> * 用了这个中间层(DataSource)我们可以提供某些服务(连接池、分布式事务)

  • DataSource的形式是JNDI (Java Naming Directory Interface)
  • 数据源的概念在应用程序与数据库连接之间插入了一个中间层,进而可以实现连接池以及事务管理,并且以JNDI的形式,也能够以非常方便的形式使用。
小结
  • DataSource 是基于应用程序与 DriverManager 抽象出来的中间层,解耦了应用程序与数据库的实际连接,类似代理模式可以添加更多的功能:连接池、分布式事务等。

    连接池与DataSource是两回事,只是目前市面上实现了DataSource都赋予了连接池、连接池管理的功能。

HikariPool

设计模型
核心类 HikariDataSource 继承自 DataSource
核心成员 HikariPool pool(volatile 保证可见)实现连接池功能 继承自 PoolBase
  • 字段:

  • DataSource 用于创建数据库连接(DriverDataSource Driver创建连接)

  • connectionBag 用于缓存数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/**

设计亮点

快,减少并发 无锁并发 CAS

线程回收时,优先放在之前使用线程 ThreadLocal 方便下次使用 时间局部性原理。

ThreadLocal<List<Connection>>

缓存线程使用 copyOnWriterList 写时复制,写与读同时操作时,不阻塞读

更新 PoolEntry 状态 使用 CAS

*/

  • addConnectionExecutor 单线程线程池 用于建立数据库连接 -> 线程池允许核心线程超时 executor.allowCoreThreadTimeOut(true);

  • closeConnectionExecutor 单线程线程池 用于关闭数据库连接 -> 线程池允许核心线程超时 executor.allowCoreThreadTimeOut(true);

  • houseKeepingExecutorService 调度线程池 内部定时器,用于实现连接的超时淘汰、连接池的补充等工作。

  • addConnectionQueueReadOnlyView -> addConnectionExecutor(线程池) 阻塞队列的集合 用来判断高并发时 超过MaxSize

1
2
3
4
5
6
7
8
9

final int maxPoolSize = config.getMaximumPoolSize();

LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);

this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);

this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());

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



* PoolEntryCreator,添加连接的任务,实现创建连接的具体逻辑。



#### Pool entry

POOL_ENTRY 作为一个连接节点

通过提交一个 houseKeepingExecutorService 调度任务,来保证超过最大时间的空闲任务close

#### ConnectionBag

* sharedList CopyOnWriteArrayList(读多写少) 缓存数据库连接(pool entry 保证类(ProxyConnection)

* waiters 待获取连接数量

* threadList 缓存当前线程数据库连接(针对同一 个线程返回获取连接,提升效率ThreadLocal)

* handoffQueue 提升效率,并发频繁的场景,避免遍历 copyOnWirte 拿取缓存数据库连接



##### HikariPool getConnection 返回的是装饰/代理的Connection ,Poolentry中存放的是原生的数据库连接 ,



##### 使用 Javassist (字节码变成) 创建 ProxyFactory 用于创建 HikariProxyConnection (代理模式) 来装饰原生 Connection ,调用 Close 方法时不会真实的关闭close(连接池缓存连接)



###### Javassist 利用 Maven 生命周期 compile 时生成新的class文件



​```XML

<plugin>

<!-- Generate proxies -->

<groupId>org.codehaus.mojo</groupId>

<artifactId>exec-maven-plugin</artifactId>

<version>1.6.0</version>

<executions>

<execution>

<phase>compile</phase>

<!-- phase>generate-test-sources</phase -->

<goals>

<goal>java</goal>

</goals>

</execution>

</executions>

<configuration>

<mainClass>com.zaxxer.hikari.util.JavassistProxyFactory</mainClass>

</configuration>

</plugin>



配置参数
  • maxLifeTime: 15m

    底层执行一个延时任务,延时 maxLifeTime 便执行,给 Connection 打上驱逐标记 poolEntry.markEvicted(),如果当前连接是空闲连接 state; STATE_NOT_IN_USE,则直接真实关闭当前连接。若不是 只有等待连接空闲后,在getConnection 时,判断是否存在evict 标记,进行连接close

  • idleTimeout: 10s

  • minIdle: 1

    底层使用一个定时线程池,执行一个定时任务默认 30ms 执行一次,扫描出空闲连接,并进行清理。(保证剩余的线程)

tip 通过 Semaphore 信号量并发工具 实现线程池的暂停功能
  • 核心思想:将信号量消耗完,获取信号量的最大值,,其余获取消耗量的线程都阻塞
1
2
3
4
5
6
7
8
9

public void suspend()

{

acquisitionSemaphore.acquireUninterruptibly(MAX_PERMITS);

}

tip (PoolEntry 状态字段state)
  • AtomicIntegerFieldUpdater 用于原子性(线程安全)更新类对象中的int字段

JDBCTemplate

1
2
3
4
5

JDBCTemplate (DataSource) -> DataSourceUtil.getConnection(DataSource)

封装事务

tip Collection.unmodifiableCollection()

  • 创建/封装 不可变集合 UnmodifiableCollection ,代理模式,重写修改的方法 抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13

public boolean add(E e) {

throw new UnsupportedOperationException();

}

public boolean remove(Object o) {

throw new UnsupportedOperationException();

}