一、池化思想
池化思想:一般是对对象的池化,核心思想是空间换时间,期望使用预先创建好的对象来减少频繁创建对象的性能开销,同时还可以对对象进行统一管理,减少对象使用成本。
具体体现:字符串常量池、数据库连接池、线程池
好处:会初始预设资源,解决的问题就是抵消每次获取资源的消耗, 避免了重复创建对象的过程,提高程序的效率。为了资源的反复利用。
二、数据库连接池
1、为什么要用数据库连接池?
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
连接池技术尽可能多地重用了消耗内存资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
2、传统连接 与 连接池的区别
拿一个简单的MySQL的 举例, 看从连接的创建到最后的销毁要经过哪些过程
- TCP建立连接的三次握手
- MySQL认证的认证校验
- 真正的SQL执行
- MySQL的关闭
- TCP的四次挥手关闭
在使用连接池后,其实 只是在系统启动时初始化一批连接,但是之后的访问,均会复用之前创建的连接,直接执行SQL语句,即后续的数据库连接我们只需要关注上述的第三步骤即可,大大减少了 网络及系统性能开销
2、常见的数据库连接池
dbcp
https://commons.apache.org/proper/commons-dbcp/index.html
DBCP是Apache下独立的数据库连接池组件,在Tomcat中使用的连接池组件就是DBCP,支持JDBC3,JDBC4。
详见:https://en.wikipedia.org/wiki/Java_Database_Connectivity
由于在实践过程中发现,在某些特殊情况下会产生很多空连接不能释放,很多服务已经暂停了对于dbcp的支持(tomcat 7.0 及以后已经放弃了对dbcp的支持)
单独使用dbcp需要2个包:commons-dbcp.jar,commons-pool.jar
常见配置项:
<!-- 配置dbcp数据源 -->
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
#连接池配置
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
<!-- 设置自动回收超时连接 -->
spring.datasource.dbcp2.remove-abandoned=10000
<!-- 最小空闲连接数,连接池中容许保持空闲状态的最小连接数量 -->
spring.datasource.dbcp2.min-idle=5
<!-- 最大空闲连接数,超过的空闲连接将被释放 -->
spring.datasource.dbcp2.max-idle=5
<!-- 同一时间可以从池分配的最多连接数量。设置为0时表示无限制。 -->
spring.datasource.dbcp2.max-active=30
<!-- 池启动时创建的连接数量 -->
spring.datasource.dbcp2.initial-size=5
<!-- 自动回收超时时间(以秒数为单位) -->
spring.datasource.dbcp2.remove-abandoned-timeout=200
c3p0
使用c3p0有多种方式,常见方式如:可以通过文件的方式进行配置,配置文件有2种形式:c3p0.properties或c3p0-config.xml文件
c3p0.properties
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql:///127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true
c3p0.user=root
c3p0.password=root
#连接池保持的最小连接数 default : 3
c3p0.minPoolSize=2
#连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15
c3p0.maxPoolSize=10
#连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。default : 0 单位 s
c3p0.maxIdleTime=1800000
#连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3
c3p0.acquireIncrement=3
#连接池为数据源缓存的PreparedStatement的总数。由于PreparedStatement属于单个Connection,所以这个数量应该根据应用中平均连接数乘以每个连接的平均PreparedStatement
##来计算。同时maxStatementsPerConnection的配置无效。default : 0(不建议使用)
c3p0.maxStatements=500
c3p0.initialPoolSize=4
#每60秒检查所有连接池中的空闲连接
c3p0.idleConnectionTestPeriod=60
#连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。default : 30(建议使用)
c3p0.acquireRetryAttempts=5
#两次连接中间隔时间,单位毫秒,连接池在获得新连接时的间隔时间。default : 1000 单位ms(建议使用)
c3p0.acquireRetryDelay=5000
#如果为true,则当连接获取失败时自动关闭数据源,除非重新启动应用程序。所以一般不用。default : false(不建议使用)
c3p0.breakAfterAcquireFailure=true
#配置当连接池所有连接用完时应用程序getConnection的等待时间。为0则无限等待直至有其他连接释放或者创建新的连接,
## 不为0则当时间到的时候如果仍没有获得连接,则会抛出SQLException。
#其实就是acquireRetryAttempts*acquireRetryDelay。default : 0(与上面两个,有重复,选择其中两个都行)
c3p0.checkoutTimeout=100
#c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3
c3p0.numHelperThreads=10
或 c3p0-config.xml(可配置多数据源)
<c3p0-config>
<default-config>
<property name="user">root</property>
<property name="password">123456</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///xxxxx</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
<named-config name="hive">
<property name="user">root</property>
<property name="password">123456</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///xxxxx</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</named-config>
</c3p0-config>
如果要使用default-config则初始化数据源的方式与 c3p0.properties 一样,如果要使用named-config里面配置初始化数据源,则只要使用一个带参数的ComboPooledDataSource构造器就可以了,例:ComboPooledDataSource cpds = new ComboPooledDataSource(“hive”);
在springboot项目中具体使用方式如下:
1.引入maven依赖
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
2.配置文件修改(使用上述两种方式都可)
3.数据源配置文件类
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource")
//配置属性,prefix : 前缀
@ConfigurationProperties(prefix = "c3p0")
public DataSource createDataSource(){
return DataSourceBuilder.create()
.type(ComboPooledDataSource.class) // 设置数据源类型
.build();
}
}
如果要使用default-config则初始化数据源的方式与 c3p0.properties 一样,如果要使用named-config里面配置初始化数据源,则只要使用一个带参数的ComboPooledDataSource构造器就可以了,例:ComboPooledDataSource cpds = new ComboPooledDataSource(“myApp”);
druid
Druid为监控而生的数据库连接池,它是阿里巴巴开源平台上的一个项目。Druid被称作是Java语言中最好的数据库连接池,Druid能够提供强大的监控和扩展功能.它可以替换DBCP和C3P0连接池。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
详情:https://github.com/alibaba/druid/wiki
竞品对比:
可以通过一系列的重要参数对比,尤其性能、监控、诊断、安全、扩展性这些方面druid远远超出竞品。
Spring 整合 Druid
1.引入相关依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
2.配置Druid数据源属性文件
spring:
datasource:
# 数据源基本配置
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test
type: com.alibaba.druid.pool.DruidDataSource
# 数据源其他配置
#启动程序时,在连接池中初始化多少个连接
initialSize: 5
#回收空闲连接时,将保证至少有minIdle个连接.
minIdle: 5
#连接池中最多支持多少个活动会话
maxActive: 20
#程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败 即连接池没有可用连接,单位毫秒,设置-1时表示无限等待
maxWait: 60000
#超过时间限制是否回收
removeAbandoned: true
#超时时间 单位:秒
removeAbandonedTimeoutMillis: 180
##关闭abandon连接时输出错误日志
logAbandoned: true
#检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查
timeBetweenEvictionRunsMillis: 60000
#池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将回收该连接,要小于防火墙超时设置
minEvictableIdleTimeMillis: 300000
#检查池中的连接是否仍可用的 SQL 语句,druid会连接到数据库执行该SQL, 如果正常返回,则表示连接可用,否则表示连接不可用
validationQuery: SELECT 1 FROM DUAL
#当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效)
testWhileIdle: true
#程序 申请 连接时,进行连接有效性检查(低效,影响性能)
testOnBorrow: false
#程序 返还 连接时,进行连接有效性检查(低效,影响性能)
testOnReturn: false
#缓存通过以下两个方法发起的SQL:
#public PreparedStatement prepareStatement(String sql)
#public PreparedStatement prepareStatement(String sql,int resultSetType, int resultSetConcurrency)对于含有游标的数据库开启后效果显著(如:oracle)
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall
#每个连接最多缓存多少个SQL
maxPoolPreparedStatementPerConnectionSize: 20
## 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
#连接属性。比如设置一些连接池统计方面的配置。
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
3.实现Druid的配置类
package com.dp.druid.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @Author YangChao
* @create 2022/4/5 18:31
*/
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){
return new DruidDataSource();
}
//配置Druid的监控
//1、配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
//配置 druid的 监控登录账号密码
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","");//默认就是允许所有访问
initParams.put("deny","127.0.0.2");
bean.setInitParameters(initParams);
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
4.启动springboot应用,访问localhost:8080/druid 进入druid数据源监控系统
ps: 所有数据库连接池所需注意的:
数据库连接池的最小连接数和最大连接数的设置要考虑到下列几个因素:
1) 最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
2) 最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
3) 如果最小连接数与最大连接数相差太大,那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。
小插曲(故事起源):
某日,收到服务告警,显示服务异常下线,从nacos服务监控上看,服务的两个实例均显示为健康状态
,但是前端页面所有请求已经开始报错,从查询日志可以看到报错信息,报错如下:
由于本服务设置有定时任务,每小时的定时任务对于 数据量访问较大,可能存在超时连接不释放问题,
导致最大连接数和 活跃连接数相等(均为 50),导致数据库连接池无可用连接,后通过对连接池想相关参数了解,只需调整对应参数即可,将超时且未释放的连接做abandon处理,开启日志,并且可以追踪到相关不释放的连接请求,进而解决相关问题
To 前端同学:如何通过js简易操作数据库
普通方式:
//1、安装mysql模块: npm install mysql --save
//2、使用node连接mysql数据库
const mysql=require('mysql');
//创建连接
let con=mysql.createConnection({
host:'**.***.**.***',
port:3306,
user:"root",
password:"root",
database:"test"
})
//3、获取连接
con.connect((err)=>{
if (err) throw err;
console.log("连接成功!!!");
})
//4、定义查询语句
let sql="select count(1) as 条数 from account ";
//5、执行查询
con.query(sql,(err,result)=>{
console.log("查询结果:"+result)
});
let insSql= "insert into account values (4,'haha',9.9)"
con.query(insSql,(err,result)=>{
console.log("插入成功!!")
});
//6、关闭连接
con.end((err)=>{
if (err) throw err;
console.log("连接已关闭")
})
连接池方式:
const mysql=require('mysql');
let pool =mysql.createPool({
//最大连接数
connectionLimit:5,
//队伍中等待连接的最大数量,0为不限制。
queryLimit:10,
host:'**.***.**.***',
port:3306,
user:"root",
password:"root",
database:"test"
})
//连接数据库执行查询
pool.getConnection((err,conn)=>{
let sql = "select count(1) as 条数 from account";
conn.query(sql,(err,result)=>{
console.log(result)
})
//连接归还
conn.release();
})