Druid-数据库连接池

数据库连接池原理介绍

什么是连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

为什么要使用连接池

数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

传统的连接机制与数据库连接池的运行机制区别

不使用连接池流程

下面以访问MySQL为例,执行一个SQL命令,如果不使用连接池,需要经过哪些流程。

undefined

不使用数据库连接池的步骤:

TCP建立连接的三次握手
MySQL认证的三次握手
真正的SQL执行
MySQL的关闭
TCP的四次握手关闭
可以看到,为了执行一条SQL,却多了非常多我们不关心的网络交互。

优点:

实现简单

缺点:

  • 网络IO较多
  • 数据库的负载较高
  • 响应时间较长及QPS较低
  • 应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁
  • 在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)

使用连接池流程

undefined

使用数据库连接池的步骤:

第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。

优点:

  1. 较少了网络开销
  2. 系统的性能会有一个实质的提升
  3. 没了麻烦的TIME_WAIT状态

数据库连接池的工作原理

连接池的工作原理主要由三部分组成,分别为

  1. 连接池的建立
  2. 连接池中连接的使用管理
  3. 连接池的关闭

第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:

当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。

当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。

该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

连接池主要参数

使用连接池时,要配置一下参数

  1. 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
  2. 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
  3. 最大空闲时间
  4. 获取连接超时时间
  5. 超时重试连接次数

连接池需要注意的点

1、并发问题

为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持,像java, c#等等,使用synchronized(java) lock(C#)关键字即可确保线程是同步的。

2、事务处理

我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。

我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。

3、连接池的分配与释放

连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
 对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他(如何能找到最合适的连接文章将在关键议题中指出);如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理捎后我会介绍这个线程的具体实现。

4、连接池的配置与维护

连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
 如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。

Druid数据库连接池

Druid 相对于其他数据库连接池的优点

  1. 强大的监控特性,通过Druid提供的监控功能,可以清楚知道连接池和SQL的工作情况。
    a. 监控SQL的执行时间、ResultSet持有时间、返回行数、更新行数、错误次数、错误堆栈信息;
    b. SQL执行的耗时区间分布。什么是耗时区间分布呢?比如说,某个SQL执行了1000次,其中01毫秒区间50次,110毫秒800次,10100毫秒100次,1001000毫秒30次,1~10秒15次,10秒以上5次。通过耗时区间分布,能够非常清楚知道SQL的执行耗时情况;
    c. 监控连接池的物理连接创建和销毁次数、逻辑连接的申请和关闭次数、非空等待次数、PSCache命中率等。
  2. 方便扩展。Druid提供了Filter-Chain模式的扩展API,可以自己编写Filter拦截JDBC中的任何方法,可以在上面做任何事情,比如说性能监控、SQL审计、用户名密码加密、日志等等。
  3. Druid集合了开源和商业数据库连接池的优秀特性,并结合阿里巴巴大规模苛刻生产环境的使用经验进行优化。

1.Druid数据源是什么?

Druid是阿里巴巴开源的一个数据源,主要用于java数据库连接池,相比spring推荐的DBCP和hibernate推荐的C3P0、Proxool数据库连接池,Druid在市场上占有绝对的优势;

2.为什么选择Druid作为数据库连接池?

这里直接给出一个链接:
大话数据库连接池
文章从市场占有率、性能上比较C3P0、DBCP、HikariCP和Druid,说明了Druid数据源由于有强大的监控特性、可拓展性等特点值得作者推荐。虽说 HikariCP 的性能比 Druid 高,但是因为 Druid 包括很多维度的统计和分析功能,所以大家都选择使用Druid 的更多;

3.如何结合SpringBoot使用?

要先知道如何使用一个工具,当然首先要从文档开始,关于文档可以从官网知道,可以参考https://github.com/alibaba/druid
(1) 添加Druid依赖

    <properties>
        <druid-version>1.1.10</druid-version>
    </properties>
    <dependencies>
......
<!--alibaba druid datasource-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${druid-version}</version>
</dependency>
......
    </dependencies>

(2) 添加配置
springboot支持yml和properties等配置文件,本文采用application.properties配置。

  • application.properties
# DataSource settings
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
#连接池的配置信息
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000druid-monitor.properties
  • druid-monitor.properties
#是否启用StatFilter默认值true
spring.datasource.druid.web-stat-filter.enabled=true
#多个白名单IP以逗号分隔
druid.monitor.allow=127.0.0.1
#多个黑名单IP以逗号分隔
druid.monitor.deny=0.0.0.0
#druid监控管理界面登录帐号
druid.monitor.loginUsername=admin
#druid监控管理界面登录密码
druid.monitor.loginPassword=password
#是否开启重置功能
druid.monitor.resetEnable=false

要知道具体配置有啥影响的话,我找了一篇采坑的文章,可以看一下:

使用druid连接池带来的坑testOnBorrow=false
(3) 添加配置类
为方便以后拓展,这里提供一个数据源配置接口,druid配置也只是这个接口的一个实现类,方便以后切换不同的数据源;

  • DbConfig.java
package com.ijustone.service.core.db.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
public interface DbConfig {

    /**
     * 定义数据源
     *
     * @return
     * @throws Exception
     */
    DataSource  dataSource() throws Exception;

    /**
     * 定义session工厂
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    SqlSessionFactory sessionFactory(DataSource dataSource) throws Exception;

    /**
     * 定义失误管理器
     *
     * @param dataSource
     * @return
     */
    DataSourceTransactionManager transactionManager(DataSource dataSource);

}

下面是druid配置实现类

  • MyDruirdConfig.java
package com.ijustone.service.core.db.config.impl;

import com.alibaba.druid.pool.DruidDataSource;
import com.ijustone.service.core.db.config.DbConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * @author JustOne
 * @create 2018-08-01  22:47
 */
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = MyDruirdConfig.PACKAGE, sqlSessionFactoryRef = "sessionFactory")
public class MyDruirdConfig implements DbConfig {


    public static final String PACKAGE = "com.ijustone.service.**.mapper";

    public static final String MAPPER = "classpath:com/ijustone/service/**/mapper/**/*Mapper.xml";

    @Value("${spring.datasource.url}")
    private String dbUrl;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;

    @Value("${spring.datasource.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.testWhileIdle:true}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.timeBetweenEvictionRunsMillis:60000}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;

    /**
     * 指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个.<br/>
     * 注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
     */
    @Value("${spring.datasource.testOnBorrow:true}")
    private boolean testOnBorrow;

    /**
     * 指明是否在归还到池中前进行检验<br/>
     * 注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
     */
    @Value("${spring.datasource.testOnReturn:false}")
    private boolean testOnReturn;

    @Value("${spring.datasource.minEvictableIdleTimeMillis:300000}")
    private int minEvictableIdleTimeMillis;

    /**
     * 当开启时, 将为每个连接创建一个statement池,并且被方法创建的PreparedStatements将被缓存起来:
     */
    @Value("${spring.datasource.poolPreparedStatements:false}")
    private boolean poolPreparedStatements;

    /**
     * 不限制  statement池能够同时分配的打开的statements的最大数量,如果设置为0表示不限制
     */
    @Value("${spring.datasource.maxOpenPreparedStatements:10}")
    private int maxPoolPreparedStatementPerConnectionSize;

    @Value("${spring.datasource.defaultAutoCommit:false}")
    private boolean defaultAutoCommit;

    @Value("${spring.datasource.filters:stat}")
    private String filters;

    /**
     * 当建立新连接时被发送给JDBC驱动的连接参数
     */
    @Value("${spring.datasource.connectionProperties:druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000}")
    private String connectionProperties;

    /**
     * 定义数据源
     * 注意@Primary注解表示:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
     *
     * @return
     * @throws Exception
     */
    @Bean(name = "dataSource")
    @Primary
    @Override
    public DataSource dataSource() throws Exception {
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(this.username);
        datasource.setPassword(this.password);
        datasource.setDriverClassName(this.driverClassName);

        datasource.setInitialSize(this.initialSize);
        datasource.setMinIdle(this.minIdle);
        datasource.setMaxActive(this.maxActive);
        datasource.setMaxWait(this.maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(this.timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(this.minEvictableIdleTimeMillis);
        datasource.setValidationQuery(this.validationQuery);
        datasource.setTestWhileIdle(this.testWhileIdle);
        datasource.setTestOnBorrow(this.testOnBorrow);
        datasource.setTestOnReturn(this.testOnReturn);
        datasource.setPoolPreparedStatements(this.poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(this.maxPoolPreparedStatementPerConnectionSize);
        datasource.setDefaultAutoCommit(this.defaultAutoCommit);
        datasource.setFilters(this.filters);
        datasource.setConnectionProperties(this.connectionProperties);
        return datasource;
    }

    /**
     * 定义session工厂
     * 注:ualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,
     * 我们修改调用代码,添加@Qualifier注解,需要注意的是@Qualifier的参数名称必须为我们之前定义@Service注解的名称之一!
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "sessionFactory")
    @Primary
    @Override
    public SqlSessionFactory sessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

        sessionFactory.setMapperLocations(resolver.getResources(MyDruirdConfig.MAPPER));
        return sessionFactory.getObject();
    }

    /**
     * 定义事务管理器
     *
     * @param dataSource
     * @return
     */
    @Bean(name = "transactionManager")
    @Override
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

我们配置了Druid的监听器

  • DruidMonitorConfiguration.java
package com.ijustone.service.core.druid.monitor;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.beans.factory.annotation.Value;
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 org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource(value = "classpath:config/druid-monitor.properties")
@ConfigurationProperties
public class DruidMonitorConfiguration {

    @Value("${druid.monitor.allow:127.0.0.1}")
    private String allow;
    @Value("${druid.monitor.deny}")
    private String deny;
    @Value("${druid.monitor.loginUsername:admin}")
    private String loginUsername;
    @Value("${druid.monitor.loginPassword:password}")
    private String loginPassword;
    @Value("${druid.monitor.resetEnable:false}")
    private String resetEnable;

    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        servletRegistrationBean.addInitParameter("allow", this.allow);
        servletRegistrationBean.addInitParameter("deny", this.deny);
        servletRegistrationBean.addInitParameter("loginUsername", this.loginUsername);
        servletRegistrationBean.addInitParameter("loginPassword", this.loginPassword);
        servletRegistrationBean.addInitParameter("resetEnable", this.resetEnable);
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean druidStatFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

4.如何查看监控页面?

访问http://localhost:8010/druid就可以了,如果工程集成了SpringSecurity等权限工程的话是需要额外配置的,这里给一个踩坑链接:(https://www.2cto.com/kf/201803/731528.html).

原文链接1

原文链接2

posted @ 2021-07-25 18:42  蔚蓝的海洋  阅读(2609)  评论(0编辑  收藏  举报