共计 9098 个字符,预计需要花费 23 分钟才能阅读完成。
一、数据库连接池的原理
基本原理
对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。
连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
对于共享资源,有一个很著名的设计模式:资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。
数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。如:
外部使用者可通过 getConnection 方法获取连接,使用完毕后再通过 releaseConnection 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
数据库连接池技术带来的优势:
1. 资源重用由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程 / 线程的数量)。
2. 更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么……. 快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
4. 统一的连接管理,避免数据库连接泄漏
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。
二、一个最小化的数据库连接池实现
1. 前言
数据库应用,在许多软件系统中经常用到,是开发中大型系统不可缺少的辅助。但如果对数据库资源没有很好地管理 (如:没有及时回收数据库的游标(ResultSet)、Statement、连接 (Connection) 等资源),往往会直接导致系统的稳定。这类不稳定因素,不单单由数据库或者系统本身一方引起,只有系统正式使用后,随着流量、用户的增加,才会逐步显露。在基于 Java 开发的系统中,JDBC 是程序员和数据库打交道的主要途径,提供了完备的数据库操作方法接口。但考虑到规范的适用性,JDBC 只提供了最直接的数据库操作规范,对数据库资源管理,如:对物理连接的管理及缓冲,期望第三方应用服务器 (Application Server) 的提供。本文,以 JDBC 规范为基础,介绍相关的数据库连接池机制,并就如果以简单的方式,实现有效地管理数据库资源介绍相关实现技术。
2. 连接池技术背景
2.1 JDBC
JDBC 是一个规范,遵循 JDBC 接口规范,各个数据库厂家各自实现自己的驱动程序(Driver),如下图所示:
数据库连接池的实现及原理应用在获取数据库连接时,需要以 URL 的方式指定是那种类型的 Driver,在获得特定的连接后,可按照固定的接口操作不同类型的数据库,如: 分别获取 Statement、执行 SQL 获得 ResultSet 等,如下面的例子 :
import java.sql.*;…DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());Connection dbConn = DriverManager.getConnection(“jdbc:oracle:thin:@127.0.0.1:1521:oracle”,”username”,”password”);Statement st = dbConn.createStatement();ResultSet rs = st.executeQuery(“select * from demo_table”);
…some data source operation in here
rs.close();st.close();dbConn.close();
在完成数据操作后,还一定要关闭所有涉及到的数据库资源。这虽然对应用程序的逻辑没有任何影响,但是关键的操作。上面是个简单的例子,如果搀和众多的 if-else、exception,资源的管理也难免百密一疏。如同 C 中的内存泄漏问题,Java 系统也同样会面临崩溃的恶运。所以数据库资源的管理依赖于应用系统本身,是不安全、不稳定的一种隐患。
2.2 JDBC 连接池
在标准 JDBC 对应用的接口中,并没有提供资源的管理方法。所以,缺省的资源管理由应用自己负责。虽然在 JDBC 规范中,多次提及资源的关闭 / 回收及其他的合理运用。但最稳妥的方式,还是为应用提供有效的管理手段。所以,JDBC 为第三方应用服务器(Application Server)提供了一个由数据库厂家实现的管理标准接口:连接缓冲 (connection pooling)。引入了连接池(Connection Pool) 的概念,也就是以缓冲池的机制管理数据库的资源。
JDBC 最常用的资源有三类:— Connection: 数据库连接。— Statement: 会话声明。— ResultSet: 结果集游标。
分别存在以下的关系:
数据库连接池的实现及原理这是一种“爷—父—子”的关系,对 Connection 的管理,就是对数据库资源的管理。举个例子: 如果想确定某个数据库连接 (Connection) 是否超时,则需要确定其(所有的)子 Statement 是否超时,同样,需要确定所有相关的 ResultSet 是否超时;在关闭 Connection 前,需要关闭所有相关的 Statement 和 ResultSet。因此,连接池 (Connection Pool) 所起到的作用,不仅仅简单地管理 Connection,还涉及到 Statement 和 ResultSet。
2.3 连接池 (ConnectionPool) 与资源管理
ConnectionPool 以缓冲池的机制,在一定数量上限范围内,控制管理 Connection,Statement 和 ResultSet。任何数据库的资源是有限的,如果被耗尽,则无法获得更多的数据服务。在大多数情况下,资源的耗尽不是由于应用的正常负载过高,而是程序原因。在实际工作中,数据资源往往是瓶颈资源,不同的应用都会访问同一数据源。其中某个应用耗尽了数据库资源后,意味其他的应用也无法正常运行。因此,ConnectionPool 的第一个任务是限制:每个应用或系统可以拥有的最大资源。也就是确定连接池的大小 (PoolSize)。ConnectionPool 的第二个任务:在连接池的大小(PoolSize) 范围内,最大限度地使用资源,缩短数据库访问的使用周期。许多数据库中,连接(Connection)并不是资源的最小单元,控制 Statement 资源比 Connection 更重要。以 Oracle 为例:每申请一个连接(Connection)会在物理网络(如 TCP/IP 网络)上建立一个用于通讯的连接,在此连接上还可以申请一定数量的 Statement。同一连接可提供的活跃 Statement 数量可以达到几百。在节约网络资源的同时,缩短了每次会话周期(物理连接的建立是个费时的操作)。但在一般的应用中,多数按照 2.1 范例操作,这样有 10 个程序调用,则会产生 10 次物理连接,每个 Statement 单独占用一个物理连接,这是极大的资源浪费。ConnectionPool 可以解决这个问题,让几十、几百个 Statement 只占用同一个物理连接,发挥数据库原有的优点。通过 ConnectionPool 对资源的有效管理,应用可以获得的 Statement 总数到达:
(并发物理连接数)×(每个连接可提供的 Statement 数量)
例如某种数据库可同时建立的物理连接数为 200 个,每个连接可同时提供 250 个 Statement,那么 ConnectionPool 最终为应用提供的并发 Statement 总数为: 200 × 250 = 50,000 个。这是个并发数字,很少有系统会突破这个量级。所以在本节的开始,指出资源的耗尽与应用程序直接管理有关。对资源的优化管理,很大程度上依靠数据库自身的 JDBC Driver 是否具备。有些数据库的 JDBC Driver 并不支持 Connection 与 Statement 之间的逻辑连接功能,如 SQLServer,我们只能等待她自身的更新版本了。对资源的申请、释放、回收、共享和同步,这些管理是复杂精密的。所以,ConnectionPool 另一个功能就是,封装这些操作,为应用提供简单的,甚至是不改变应用风格的调用接口。
3. 简单 JDBC 连接池的实现
根据第二章中原理机制,Snap-ConnectionPool(一种简单快速的连接池工具,可在 www.snapbug.net 下载)按照部分的 JDBC 规范,实现了连接池所具备的对数据库资源有效管理功能。
3.1 体系描述
在 JDBC 规范中,应用通过驱动接口(Driver Interface)直接方法数据库的资源。为了有效、合理地管理资源,在应用与 JDBC Driver 之间,增加了连接池: Snap-ConnectionPool。并且通过面向对象的机制,使连接池的大部分操作是透明的。参见下图,Snap-ConnectionPool 的体系:
数据库连接池的实现及原理图中所示,通过实现 JDBC 的部分资源对象接口(Connection, Statement, ResultSet),在 Snap-ConnectionPool 内部分别产生三种逻辑资源对象: PooledConnection, PooledStatement 和 PooledResultSet。它们也是连接池主要的管理操作对象,并且继承了 JDBC 中相应的从属关系。这样的体系有以下几个特点:
— 透明性。在不改变应用原有的使用 JDBC 驱动接口的前提下,提供资源管理的服务。应用系统,如同原有的 JDBC,使用连接池提供的逻辑对象资源。简化了应用程序的连接池改造。— 资源封装。复杂的资源管理被封装在 Snap-ConnectionPool 内部,不需要应用系统过多的干涉。管理操作的可靠性、安全性由连接池保证。应用的干涉(如:主动关闭资源),只起到优化系统性能的作用,遗漏操作不会带来负面影响。— 资源合理应用。按照 JDBC 中资源的从属关系,Snap-ConnectionPool 不仅对 Connection 进行缓冲处理,对 Statement 也有相应的机制处理。在 2.3 已描述,合理运用 Connection 和 Statement 之间的关系,可以更大限度地使用资源。所以,Snap- ConnectionPool 封装了 Connection 资源,通过内部管理 PooledConnection,为应用系统提供更多的 Statement 资源。— 资源连锁管理。Snap-ConnectionPool 包含的三种逻辑对象,继承了 JDBC 中相应对象之间的从属关系。在内部管理中,也依照从属关系进行连锁管理。例如:判断一个 Connection 是否超时,需要根据所包含的 Statement 是否活跃;判断 Statement 也要根据 ResultSet 的活跃程度。
3.2 连接池集中管理 ConnectionManager
ConnectionPool 是 Snap-ConnectionPool 的连接池对象。在 Snap-ConnectionPool 内部,可以指定多个不同的连接池 (ConnectionPool) 为应用服务。ConnectionManager 管理所有的连接池,每个连接池以不同的名称区别。通过配置文件适应不同的数据库种类。如下图所示:
数据库连接池的实现及原理 通过 ConnectionManager,可以同时管理多个不同的连接池,提供通一的管理界面。在应用系统中通过 ConnectionManager 和相关的配置文件,可以将凌乱散落在各自应用程序中的数据库配置信息(包括:数据库名、用户、密码等信息),集中在一个文件中。便于系统的维护工作。
3.3 连接池使用范例
对 2.1 的标准 JDBC 的使用范例,改为使用连接池,结果如下:
import java.sql.*;
import net.snapbug.util.dbtool.*;
//…
ConnectionPool dbConn = ConnectionManager
.getConnectionPool(“testOracle”);
Statement st = dbConn.createStatement();
ResultSet rs = st.executeQuery(
“select * from demo_table” );
//…
some data source operation
in herers.close();st.close();
在例子中,Snap-ConnectionPool 封装了应用对 Connection 的管理。只要改变 JDBC 获取 Connection 的方法,为获取连接池 (ConnectionPool)(粗体部分),其他的数据操作都可以不做修改。按照这样的方式,Snap- ConnectionPool 可帮助应用有效地管理数据库资源。如果应用忽视了最后资源的释放: rs.close() 和 st.close(),连接池会通过超时(time-out) 机制,自动回收。
4. 小结
无论是 Snap-ConnectionPool 还是其他的数据库连接池,都应当具备一下基本功能:
- 对源数据库资源的保护
- 充分利用发挥数据库的有效资源
- 简化应用的数据库接口,封闭资源管理。
- 对应用遗留资源的自动回收和整理,提高资源的再次利用率。
在这个前提下,应用程序才能投入更多的精力于各自的业务逻辑中。数据库资源也不再成为系统的瓶颈。
三、Druid 连接池(新版 starter)在 SpringBoot 下的使用
1. 更新 pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
2. 编写 application.yml,部分说明写在注释了:
spring:
application:
name: springboot-test-exam1
datasource:
# 使用阿里的 Druid 连接池
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
# 填写你数据库的 url、登录名、密码和数据库名
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&characterEncoding=utf8
username: root
password: root
druid:
# 连接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开 PSCache,并且指定每个连接上 PSCache 的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的 filters,去掉后监控界面 sql 无法统计,’wall’ 用于防火墙
filters: stat,wall,log4j
# 通过 connectProperties 属性来打开 mergeSql 功能;慢 SQL 记录
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# 配置 DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: “/*”
exclusions: “*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*”
# 配置 DruidStatViewServlet
stat-view-servlet:
url-pattern: “/druid/*”
# IP 白名单(没有配置或者为空,则允许所有访问)
allow: 127.0.0.1,192.168.163.1
# IP 黑名单 (存在共同时,deny 优先于 allow)
deny: 192.168.1.73
# 禁用 HTML 页面上的“Reset All”功能
reset-enable: false
# 登录名
login-username: admin
# 登录密码
login-password: 123456
为了方便使用 application.properties 的读者,使用下面的配置和上面相同
server.port=8080
spring.application.name=springboot-test-exam1
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/databaseName?useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.filters=stat,wall,log4j
spring.datasource.druid.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1
spring.datasource.druid.stat-view-servlet.deny=192.168.1.73
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=123456
运行结果访问:http://localhost:8080/druid/,登录名:admin,密码 123456
四、参考引用
https://blog.csdn.net/weixin_… https://www.cnblogs.com/wym78…