精华内容
下载资源
问答
  • Druid连接池

    2021-05-02 19:31:08
    连接池 **概念**:首先我们先了解什么是连接池。我们在编写JDBC程序时通常的步骤是注册驱动、获取连接、获得执行SQL语句对象、增删改查数据,也就是说每一个对数据库记录的操作都要执行一遍这些步骤。一个java程序的...

    连接池

    1.概念:首先我们先了解什么是连接池。我们在编写JDBC程序时通常的步骤是注册驱动、获取连接、获得执行SQL语句对象、增删改查数据,也就是说每一个对数据库记录的操作都要执行一遍这些步骤。一个java程序的业务逻辑层就是通过调用每一个数据库表格对应的数据管理类来完成业务操作,也就是说一个java程序有多个数据管理类而且这些类需要多次调用。如果每次调用执行数据管理类需要执行之前的那些操作那样的话执行效率很低,而我们不难发现其实每一次执行JDBC类其中有些操作是相同的,如注册驱动、获取连接、获得执行SQL语句对象,那这些获得的资源就可以把它们包装成工具类,用来使用,这样整个java程序执行的效率就会大大提升。由此,连接池的概念诞生。连接池简单来说就是储存和管理连接对象,它会在整个程序启动前就创建一定数量的连接对象,其他类调用连接池的某些方法获得连接、归还连接。
    2.连接池的实现原理:连接池,储存和管理连接对象。储存,我们可以将创建的连接对象储存在数组或者集合中。而管理,就是定义方法从存放连接的数组或集合中获取连接。为了方便使用连接池,将连接池包装成工具类,即连接池的成员变量、方法都用静态修饰。

    Druid连接池

    1.概念:Druid是由阿里巴巴开发的开源的JDBC连接池、监控组件(但是在这里仅介绍它连接池的功能)。阿里巴巴将连接池一系列方法包装成一个工具供我们方便直接使用。
    2.下载架包:要使用Druid这个工具,引入相关架包。

    下载链接:https://repo1.maven.org/maven2/com/alibaba/druid/

    下载最新版本,将架包引入java程序即可使用Druid连接池。

    3.Druid使用关键类及相关操作:Druid这个组件还有其他功能的类,但是这里作为初学者,仅介绍连接池的相关的关键类。

    • 关键类DruidDataSource:就是我们需要的连接池
    • 获取DruidDataSource对象:
    DataSource dataSource = new DataSource();
    
    • 配置连接池:获得连接池后,还需我们配置连接数据库的参数
    		dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/practise?useUnicode=true&characterEncoding=utf8&useSSL=true");
            dataSource.setUsername("root");
            dataSource.setPassword("1234");
    
    • 获取连接对象:通过调用连接池的getConnection方法获取连接对象
    Connection connection = dataSource.getConnection();
    
    • 释放资源

      我们用完连接后需要释放资源,之前通常我们都是通过连接对象的close方法直接销毁连接对象来释放资源,而现在我们使用了连接池了,我们使用完连接对象后需要的是归还连接池。而在实际开发中为了方便使用连接池,我们通常会在连接池中强化Connection类的close方法,从而实现从连接池获得连接对象调用close方法直接执行归还连接的操作。Druid连接池就已经帮我们强化Connection类的close方法,我们可以直接通过调用从连接池获得的连接对象的close方法完成归还连接的操作。
    • 配置文件配置连接池
      之前我们配置连接池是通过某些方法来配置连接池,但是在实际开发中我们这样配置连接池,如果需要更改连接数据库的一些参数,那就很麻烦。我们就需要通过资源文件来配置连接池。
      1)编写资源文件
      这里以编写properties资源文件为例
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/practise?useUnicode=true&characterEncoding=utf8&useSSL=true
    username=root
    password=1234
    

    注意:这里driverClassName、url等参数名应原样编写,大小写不能错误,因为配置连接池就是通过调用properties类来获取参数,而properties类获取资源就是就是通过参数名来获取的。
    2)资源文件配置连接池
    编写好资源文件后,我们就需要通过资源文件来配置连接池。首先我们需要将资源文件写入内存中,然后用过DruidDataSourceFactor.createDataSource(properties)此方法创建连接池,参数为Properties类对象。

    Properties properties = new Properties();
    //将资源文件写入内存中,参数为资源文件的路径
    properties.load(new FileInputStream("src/main/java/com/mysql/jdbc/mysql.properties"));
    //创建并配置连接池
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    
    • 再次包装
      在创建连接池后,我们不能发现整个程序只需要一个连接池那我们则可以再次包装一个工具类,这个类只创建一个连接池,定义一个方法调用连接池的getConnection方法来返回一个连接对象。
    public class DruidJdbc{
    
    
        /**
         * 创建一个连接池
         */
        private static final DataSource dataSource;
    	//初始化创建并配置连接池
        static {
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream("src/main/java/com/mysql/jdbc/mysql.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            DataSource dataSource1;
            try{
                dataSource1 = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
                dataSource1 = null;
            }
            dataSource = dataSource1;
        }
    
    
        /**
         * 返回连接池
         * */
        public static DataSource getDataSource(){
            return dataSource;
        }
    
    
        /**
         * 获取连接对象
         */
        public static Connection getConnection(){
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
                return null;
            }  
        }
    
    }
    
    

    总结

    以上就是Druid连接池简单用法。通过连接池可以简化我们在编写JDBC的代码,同时也大大提高程序的执行效率。其实我们再回头看看每个编写的JDBC程序会发现,大多JDBC程序基本就一个SQL语句不同,其他代码基本相同,于是就有Dbutils工具,它又更好地帮我们简化了编写JDBC的代码,感兴趣的可以了解一下。
    展开全文
  • druid连接池

    千次阅读 2021-03-07 19:17:56
    druid连接池和mysql的连接关系可以简单理解为下面的案例。有10个接线员A和远程10个接口员B在通讯,如果不用连接池,那么每用一次就要拨号、建立链接、通话、挂断,非常浪费时间。所以如果10个接线员A提前就和B接通,...

    近期一直报连接池连接的错误,主要就是那个什么last packet 多少ms以前。

    所以就研究了下。

    druid连接池和mysql的连接关系可以简单理解为下面的案例。

    有10个接线员A和远程10个接口员B在通讯,如果不用连接池,那么每用一次就要拨号、建立链接、通话、挂断,非常浪费时间。所以如果10个接线员A提前就和B接通,然后一直不挂断,这样需要时只要喊话一下就行了,效率高,真正的基于TCP连接可以参考

    但是这样会导致一个问题,如果B偷懒,偷偷的将电话挂断,而A不知道,再讲话就会出错了。

    针对这种情况,druid提供了几种解决方案:

    1、在连接时加入探测。

    Xml代码

    #空闲时检测

    #获取连接时检测

    #放回连接池时检测

    2、在空闲时检测链接。

    Xml代码

    注:每隔60毫秒检测一下,连接池在池中最小生存的时间是300秒。

    3、druid连接池基本配置:

    Xml代码main.db.initialSize=10#初始化连接个数

    main.db.minIdle=5#如果10个连接被干掉了,起码起来5个

    main.db.maxActive=50#最多到50个

    main.db.maxWait=60000#连接超过60秒报超时异常,一般是网络问题或者服务器响应太慢。

    4、完整的案例:

    Xml代码

    init-method="init" destroy-method="close">

    4.2、d

    展开全文
  • Druid 连接池

    2021-08-15 18:00:17
    本文简单介绍了Druid数据库连接池的使用,主要是与Spring Boot框架整合使用。通过使用连接池可以大大提高与数据库交互的效率。

    前言:本文简单介绍了Druid数据库连接池的使用,主要是与Spring Boot框架整合使用。通过使用连接池可以大大提高与数据库交互的效率。

    Druid 官网

    Druid 官方文档

    Druid官方文档(中文)

    简介

    Apache Druid是一个实时分析型数据库,旨在对大型数据集进行快速的查询分析("OLAP"查询)。Druid最常被当做数据库来用以支持实时摄取、高性能查询和高稳定运行的应用场景,同时,Druid也通常被用来助力分析型应用的图形化界面,或者当做需要快速聚合的高并发后端API,Druid最适合应用于面向事件类型的数据。

    导入数据源

    Maven Druid

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
    </dependency>
    

    数据库连接池配置

    com.alibaba.druid.pool.DruidDataSource

    spring

    <!--数据库连接池配置 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1" />
        <property name="minIdle" value="1" />
        <property name="maxActive" value="50" />
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="30000" />
    
        <property name="filters" value="stat,wall" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />
        
        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <!-- 这里建议配置为TRUE,防止取到的连接不可用 -->
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
    </bean>
    

    springboot

    application.yml

    # 指定数据源类型(通用配置方式)
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        username: ${jdbc.username}
        password: ${jdbc.password}
        type: com.alibaba.druid.pool.DruidDataSource
        
        #druid 数据源专有配置
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入(防火墙)
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        # 合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    

    导入Log4j依赖

    不导入可能会报java.lang.ClassNotFoundException: org.apache.log4j.Priority错误

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    

    绑定DruidDataSource

    需要自行添加到容器中并绑定属性

    @Configuration
    public class DruidConfig {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource druidDataSource() {
            return new DruidDataSource();
        }
    }
    

    配置后台监控页面

    Druid具有监控功能,有一个后台监控页面,配置后即可使用

    监控页面页面:http://localhost:8080/druid/

    	// 后台监控
        @Bean
        public ServletRegistrationBean statViewServlet() {
            ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");   // 设置后台路径
            HashMap<String, String> initParams = new HashMap<>();
            initParams.put("loginUsername", "admin");   // 登录账号
            initParams.put("loginPassword", "123456");  // 登录密码
            initParams.put("allow", "");    // 允许所有访问
            bean.setInitParameters(initParams);     // 设置初始化参数
            return bean;
        }
    

    配置web监控过滤器

    	// web监控过滤器
        @Bean
        public FilterRegistrationBean webStatFilter() {
            FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
            bean.setFilter(new WebStatFilter());
            Map<String, String> initParams = new HashMap<>();
            initParams.put("exclusions", "*.js, *.css, /druid/*");  // 过滤排除
            bean.setInitParameters(initParams);
            return bean;
        }
    

    项目仓库

    参考资料

    【狂神说Java】SpringBoot最新教程IDEA版通俗易懂

    展开全文
  • Druid连接池核心原理

    2021-08-29 14:07:27
    一、什么是Druid连接池Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。 竞品对比 从...

    一、什么是Druid连接池?
    Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。

    竞品对比
    在这里插入图片描述从上表可以看出,Druid连接池号称是业界最优秀的连接池,在性能、监控、诊断、安全、扩展性这些方面远远超出竞品。

    说明:
    ExceptionSorter:官方的说明这是Druid连接池稳定性的保证,用于处理重大的不可恢复的异常,它是一个接口,不同的数据库有不同的实现类,mysql的处理类是 MySqlExceptionSorter,它的isExceptionFatal(SQLException e)方法中识别重大异常,至于如何保证Druid稳定性的,后面会有介绍;
    Filter:这是Druid中的又一大特色,提供了强大的扩展功能,如连接池监控(连接池配置信息、SQL执行、并发、慢查询、执行时间区间分布等,由StatFilter实现)、防止SQL注入(WallFilter)、连接池信息日志输出(LogFilter)等,Druid内置的实现如下:
    在这里插入图片描述当然,我们自己也可以扩展,Druid支持通过SPI的方式加载;
    以上信息都是摘自官网,更多详细介绍参见官网:https://github.com/alibaba/druid/wiki/Druid%E8%BF%9E%E6%8E%A5%E6%B1%A0%E4%BB%8B%E7%BB%8D

    二、Druid工作原理
    本文是基于Druid 1.1.22版本,主要对连接池的初始化以及获取连接的工作流程介绍,这也是Druid的核心,至于其它功能,如监控、sql处理、事务等这里不做详细说明,有兴趣的同学可以自行了解。

    1、连接池初始化
    1.1 自动初始化

    Druid初始化方法位于com.alibaba.druid.pool.DruidDataSource#init,这个方法就是初始化的核心,我们的项目启动之后是如何自动进行初始化的?
    对于Springboot项目,Druid 的maven依赖如下:
    在这里插入图片描述
    在spring.factories文件中:
    在这里插入图片描述
    该文件中配置了Druid的自动配置类:com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure,
    在这里插入图片描述
    如上图,初始化DataSource这个bean的时候就会执行init()方法,这也就是为什么项目启动后会自动进行Druid的初始化,当然,如果项目不是Springboot,可以在定义DataSource的时候手动调用init()方法。
    返回的是DruidDataSourceWrapper类,继承了DruidDataSource类,是DataSource的包装类,主要作用是容错:正常Druid默认都是读取spring.datasource.druid.xxx(DruidDataSource、DruidAbstractDataSource类中的属性都是读取的这个前缀下的配置)下的配置,但是如果没有配置spring.datasource.druid前缀,则就读取spring.datasource的配置,主要是用户名、密码、url、数据库驱动类这几个必填配置,其它配置参数即使不配也没关系,有默认值。

    1.2 初始化流程
    Druid初始化大体分为以下几个部分:

    1.2.1 初始化filters
    filters的来源主要有两个:一是通过系统参数配置:-Ddruid.filters=xxx,是个列表;二是通过SPI机制加载;主要是提供给用户扩展使用的,在获取连接的时候会通过责任链模式进行调用;

    1.2.2 参数校验
    主要是校验参数配置的合法性,如必须 maxActive>0、maxActive >= minIdle、maxEvictableIdleTimeMillis >= minEvictableIdleTimeMillis等等;

    1.2.3 初始化ExceptionSorter和validConnectionChecker

    • ExceptionSorter

      上面说过ExceptionSorter是Druid稳定性的保证,那么它是如何保证的呢?ExceptionSorter 的作用是:在数据库服务器重启、网络抖动、连接被服务器关闭等异常情况下,连接发生了不可恢复异常,将连接从连接池中移除,保证连接池在异常发生时情况下正常工作。ExceptionSorter是连接池稳定的关键特性,没有ExceptionSorter 的连接池,不能认为是有稳定性保障的连接池。
      对于mysql数据库,实现类是MySqlExceptionSorter,该类中的isExceptionFatal方法主要是筛选出特定的异常,如CommunicationsException、以及指定的errorCode等,并最终通过com.alibaba.druid.pool.DruidPooledConnection#handleException(java.lang.Throwable, java.lang.String)方法(该方法很多地方都会调用到,比如sql执行的时候、事务提交的时候、异常回滚的时候等)将DruidAbstractDataSource类中的onFatalError属性设置成true,在进行连接池维护的时候最终会通过onFatalError属性判断是否发生了不可逆的重大异常(如数据库挂了、网络中断异常等),并将连接剔除,保证连接池的稳定;

    • ValidConnectionChecker

      ValidConnectionChecker是个接口,Druid初始化的时候会根据数据库的驱动类来判断数据库的类型,并为每种数据库分别生成对应的ValidConnectionChecker,如mysq数据库的实现是MySqlValidConnectionChecker。这个类很重要,用来检测连接池中连接的可用性,如果检测连接不可用,则close掉;可用的话就继续放回连接池中。具体检测的逻辑放到后面DestroyConnectionThread线程里说,因为需要配合一起才更好理解;

    1.2.4 初始化连接池
    DruidConnectionHolder类型的数组,有3个池子(容量都是maxActive):
    connections:存放正常连接,我们常说的连接池就是指这个数组
    evictConnections:存放需要抛弃的连接;
    keepAliveConnections:存放需要进行活性检测的连接
    并生成initialSize个连接存放到connections中;

    1.2.5 开启3个守护线程:
    createAndLogThread();
    createAndStartCreatorThread();
    createAndStartDestroyThread();

    1.2.5.1 LogStatsThread线程
    线程名:Druid-ConnectionPool-Log-当前对象hashCode,打印线程池当前配置信息,每timeBetweenLogStatsMillis执行一次,如果没有配置则该线程不执行,逻辑比较简单,主要就是封装当前线程池的信息,然后打印log,就不详细说了。

    1.2.5.2 CreateConnectionThread线程
    线程名:Druid-ConnectionPool-Create-当前对象hashCode,项目启动后,该线程大部分情况下都处于WAITING状态,可以用jstack命令查看。
    这个线程用于创建连接并put进连接池中,它的工作流程如图:
    在这里插入图片描述
    虽然该线程在初始化时就开启了,并且线程内部就是一个死循环,但是大多数情况下这个线程是不工作的,处于WAITING状态,所以不用担心线程在空轮询消耗cpu。它的等待通过empty.await()实现,empty是Condition类型(条件锁,这是jdk并发包中用于线程间协作的工具),与之相对的另一个Condition类型的变量notEmpty,它们俩保证了用户线程和CreateConnectionThread线程的良好协作:初始化完成后CreateConnectionThread线程在empty上阻塞住,当连接池中连接不够时(比如用户线程拿走一个连接时或者关闭掉不可用连接后)就会唤醒该线程,同时notEmpty.await()阻塞用户线程;如果连接池满了,那么就会唤醒用户线程来拿连接:notEmpty.signal,同时阻塞CreateConnectionThread线程,所以这就是一个生产者-消费者模式。

    1.2.5.3 DestroyConnectionThread线程
    线程名:Druid-ConnectionPool-Destroy-当前对象hashCode,这个线程中涉及到3个池子:
    connections:所有连接的连接池;
    keepAliveConnections:存放需要进行可用性检测的连接的池子;
    evictConnections:存放需要抛弃的连接的池子
    这个线程就是围绕着三个池子进行工作,将connections连接池中的连接经过各种条件判断进行分类,最终分成上述三个池子,最后分别对keepAliveConnections和evictConnections遍历处理,该丢弃的丢弃,该检测的检测,检测可用继续放回connections中,检测不可用丢弃。如果经过连接的丢弃之后发现连接池中的连接不够用了(池中连接的数量小于minIdle个,则唤醒CreateConnectionThread线程补充连接,注意这个功能只有当keepAlive=true时才会有)
    在这里插入图片描述

    • 连接检测

    在1.2.3中说到的ValidConnectionChecker类的作用时,用来检测连接池中连接是否可用的,具体检测的逻辑就在DestroyConnectionThread线程中,上面说到keepAliveConnections这个池子,如果最终keepAliveConnections中存在需要进行检测的连接的话,就开始通过ValidConnectionChecker类进行检测了。源码位于com.alibaba.druid.pool.DruidAbstractDataSource#validateConnection方法,详细流程如图:
    在这里插入图片描述
    总结:
    1、只有mysql有ping方式检测连接可用性,而且默认情况下mysql是采用ping方式(其它数据库都是采用validateQuery);
    2、如果要想通过配置的validateQuery来进行检测怎么办呢?根据MySqlValidConnectionChecker类的构造方法可以知道,需要配置jvm参数:-Ddruid.mysql.usePingMethod=false;
    3、所以如果是mysql数据库,仅仅配置validateQuery是不行的,还要添加Ddruid.mysql.usePingMethod=false配置;
    4、如果池中原本有超过minIdle个连接,那么经过回收之后,池中至少会保持minIdle个连接在里面;

    2、获取连接
    在这里插入图片描述
    (1)获取连接的时候也会先去检查一下连接池是否已经初始化,如果没初始化就先初始化,一开始说了项目启动后会自动初始化,那么对于百事通项目来说,此时连接池是已经初始化了还是没初始化?答案是没有初始化……因为百事通项目启动时给排除了这个类的自动加载:
    在这里插入图片描述
    所以没有加载Druid的自动配置类DruidDataSourceAutoConfigure,也就不会进行自动初始化了,那么就会在获取连接的时候再初始化。这个逻辑本身没什么问题,但是我们最好手动提前给初始化,尽量不要将初始化后置到获取连接的时候,比如帐号服务配置的initialSize=50,如果是在获取连接的时候(即实际查询的时候)再初始化,就会在查询之前先创建50个连接,还有一些其它一系列的操作(见前面初始化流程),这就可能耗费很长时间影响查询性能,甚至可能造成超时报错,所以需要在定义DataSource的时候手动调用一下初始化方法:
    在这里插入图片描述
    疑问:
    那么为什么我们要排除DruidDataSourceAutoConfigure类的自动加载呢?是因为我们项目使用了shardingsphere,它与shardingsphere的数据源配置冲突了,为啥会冲突呢?
    原因在前面1.1其实已经说过了:Druid默认都是读取spring.datasource.druid.xxx(DruidDataSource、DruidAbstractDataSource类中的属性都是读取的这个前缀下的配置)下的配置,但是如果没有配置spring.datasource.druid前缀,则就读取spring.datasource的配置,主要是用户名、密码、url、数据库驱动类这几个必填配置,但是使用了shardingsphere后,配置前缀变成了spring.shardingsphere.datasource.xxx,这时Druid显然读取不到用户名、密码、url、数据库驱动类这些必填配置了,就会报错,所以才要排除Druid的自动配置。不过不用担心排除了Druid的自动配置导致spring容器中没有DataSource数据源了,shardingsphere的自动配置类里也会加载数据源:见org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration

    (2)如果自定义了Filter(Druid内置也实现了几个Filter),先执行Filter责任链
    在这里插入图片描述
    (3)然后就是从池中获取连接,如果获取不到的话,就唤醒CreateConnectionThread线程(empty.signal())创建连接,上面流程图已经很详细展示了整个流程,就不再细说了。

    这里针对项目中遇到的问题做个总结:

    问题1:数据库挂了之后,连接池中的连接变成了CLOSE_WAIT状态,会不会影响数据库操作?如果影响,需要等多久才能正常?

    (1)连接池中有连接(minIdle个,CLOSE_WAIT状态),极端情况下线程需要等待1分钟(timeBetweenEvictionRunsMillis),因为在第1次请求的进行连接检测的时候,就会唤醒CreateConnectionThread线程,这时候会生成一个正常连接,下次循环的时候就会get到这个正常连接使用;所以第1次请求会报错,后面再请求就正常了;
    (2)连接池中没有连接,如果数据库挂的时候池中没有连接,那么获取连接的时候就会等待超时:连接池的配置maxWait是指Druid在等待获取连接时的超时时间,如果没有获取到,就会默认重试一次(重试次数notFullTimeoutRetryCount指定,默认0),所以Druid获取连接等待的超时时间是maxWait2=30002=6000ms;
    另外,在获取连接的同时,会唤醒CreateConnectionThread线程进行创建连接(这个过程是异步的),创建连接的时候可能也有超时时间,这个超时时间由jdbc参数connectTimeout(默认0,永不超时)指定,我们没有配置,但是配置了socketTimeout=9000(这个不是建立连接的超时时间,是socket read/write的超时时间),如果jdbc没有设置connectTimeout,就会以操作系统默认的,linux系统建立TCP连接默认超时时间127s。

    问题2:查询数据库时网关超时报错,观察日志显示查询请求之后会批量创建几十个连接,这是怎么回事?

    这个问题原因前面2中已经说过了,就是因为项目启动时,排除了Druid的自动配置导致连接池没有初始化,而且也没有手动调用初始化方法,解决办法就是定义DataSource的时候进行手动初始化。

    问题3:正常查询的过程中,连接被关闭了,为什么?

    在线程DestroyConnectionThread的代码中,有一段这样的逻辑:

    if (isRemoveAbandoned()) {
        removeAbandoned();
    }
    
    public int removeAbandoned() {
            int removeCount = 0;
    
            long currrentNanos = System.nanoTime();
    
            List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
    
            activeConnectionLock.lock();
            try {
                Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
    
                for (; iter.hasNext();) {
                    DruidPooledConnection pooledConnection = iter.next();
    
                    if (pooledConnection.isRunning()) {
                        continue;
                    }
    
                    long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
    
    				// removeAbandonedTimeoutMillis默认300s,被借出时间超过则close该连接(先放入abandonedList中)
                    if (timeMillis >= removeAbandonedTimeoutMillis) {
                        iter.remove();
                        pooledConnection.setTraceEnable(false);
                        abandonedList.add(pooledConnection);
                    }
                }
            } finally {
                activeConnectionLock.unlock();
            }
    		//close长期不归还的连接
            if (abandonedList.size() > 0) {
                for (DruidPooledConnection pooledConnection : abandonedList) {
                    final ReentrantLock lock = pooledConnection.lock;
                    lock.lock();
                    try {
                        if (pooledConnection.isDisable()) {
                            continue;
                        }
                    } finally {
                        lock.unlock();
                    }
    
                    JdbcUtils.close(pooledConnection);
                    pooledConnection.abandond();
                    removeAbandonedCount++;
                    removeCount++;
    
                    if (isLogAbandoned()) {
                        StringBuilder buf = new StringBuilder();
                        buf.append("abandon connection, owner thread: ");
                        buf.append(pooledConnection.getOwnerThread().getName());
                        buf.append(", connected at : ");
                        buf.append(pooledConnection.getConnectedTimeMillis());
                        buf.append(", open stackTrace\n");
    
                        StackTraceElement[] trace = pooledConnection.getConnectStackTrace();
                        for (int i = 0; i < trace.length; i++) {
                            buf.append("\tat ");
                            buf.append(trace[i].toString());
                            buf.append("\n");
                        }
    
                        buf.append("ownerThread current state is " + pooledConnection.getOwnerThread().getState()
                                   + ", current stackTrace\n");
                        trace = pooledConnection.getOwnerThread().getStackTrace();
                        for (int i = 0; i < trace.length; i++) {
                            buf.append("\tat ");
                            buf.append(trace[i].toString());
                            buf.append("\n");
                        }
    
                        LOG.error(buf.toString());
                    }
                }
            }
    
            return removeCount;
        }
    

    默认情况下removeAbandoned=false,可是如果设置了removeAbandoned=true并且指定了removeAbandonedTimeoutMillis(默认5分钟)的话,就会对被借出去的连接进行检测:如果连接从被get出去到当前的时间间隔超过removeAbandonedTimeoutMillis的话,就会将此连接close,这个机制主要是为了防止应用从池子中拿到连接后由于某种故障或者没有close而导致长时间没有归还到连接池,从而导致连接泄露。此功能默认关闭,一般也不需要开启。所以如果一定要设置这个参数的话,建议removeAbandonedTimeoutMillis要设置大于sql执行的时间,否则就会出现sql还在执行却被close的情况。

    上面介绍的是连接池初始化流程和获取连接流程的工作原理,这里附上两张完整的Druid工作流程图供大家参考:

    Druid工作原理-连接池初始化流程:
    Druid工作原理-连接池初始化流程
    Druid工作原理-获取连接流程:
    Druid工作原理-获取连接流程
    3、连接回收
    Duid连接池原理-连接回收流程
    说明:
    1、其实连接回收的中级目的是将可用的连接放回池中,以达到连接循环利用的目的,这样可以节省性能。从以上回收的过程来看,在回收时会先进行一系列连接的校验,比如连接的可用性校验、连接超时校验等待,如果不符合校验条件,就直接将连接close掉了:见方法discardConnection(holder),此时也就不能将连接放回池中。

    2、肯能有同学注意到了DruidDataSource类中也有一个close()方法,那么它跟上面说的连接回收方法DruidPooledConnection.close()有什么区别呢?看下DruidDataSource.close()方法的代码就能很清楚了:

    public void close() {
            if (LOG.isInfoEnabled()) {
                LOG.info("{dataSource-" + this.getID() + "} closing ...");
            }
    
            lock.lock();
            try {
                if (this.closed) {
                    return;
                }
    
                if (!this.inited) {
                    return;
                }
    
                this.closing = true;
    
                if (logStatsThread != null) {
                    logStatsThread.interrupt();
                }
    
                if (createConnectionThread != null) {
                    createConnectionThread.interrupt();
                }
    
                if (destroyConnectionThread != null) {
                    destroyConnectionThread.interrupt();
                }
    
                if (createSchedulerFuture != null) {
                    createSchedulerFuture.cancel(true);
                }
    
                if (destroySchedulerFuture != null) {
                    destroySchedulerFuture.cancel(true);
                }
    
                for (int i = 0; i < poolingCount; ++i) {
                    DruidConnectionHolder connHolder = connections[i];
    
                    for (PreparedStatementHolder stmtHolder : connHolder.getStatementPool().getMap().values()) {
                        connHolder.getStatementPool().closeRemovedStatement(stmtHolder);
                    }
                    connHolder.getStatementPool().getMap().clear();
    
                    Connection physicalConnection = connHolder.getConnection();
                    try {
                        physicalConnection.close();
                    } catch (Exception ex) {
                        LOG.warn("close connection error", ex);
                    }
                    connections[i] = null;
                    destroyCountUpdater.incrementAndGet(this);
                }
                poolingCount = 0;
                unregisterMbean();
    
                enable = false;
                notEmpty.signalAll();
                notEmptySignalCount++;
    
                this.closed = true;
                this.closeTimeMillis = System.currentTimeMillis();
    
                disableException = new DataSourceDisableException();
    
                for (Filter filter : filters) {
                    filter.destroy();
                }
            } finally {
                lock.unlock();
            }
    
            if (LOG.isInfoEnabled()) {
                LOG.info("{dataSource-" + this.getID() + "} closed");
            }
        }
    

    DruidDataSource.close()方法是对连接池进行关闭回收,当然它也会关闭物理连接和所有Statement(这很好理解,连接池都回收了,池中的连接以及相关Statement肯定也要关闭)。所以这来两个类的close()方法作用是不同的:

    • DruidDataSource.close()

    DruidDataSource.close()方法是回收连接池的,主要用于系统关闭时或者Spring容器销毁时调用,可以通过@Bean注解指定:
    在这里插入图片描述一般我们根本用不到这个功能,也不需要进行连接池的手动回收,当发生以上场景时基本上应用系统进程停了或者故障挂了,那么连接池也会随着进程被回收了;

    • DruidPooledConnection.close()
      该方法才是对连接池中的连接进行回收,它跟DruidDataSource.close()一个是描述连接池中连接的生命周期的结束,一个是描述连接池的生命周期的结束。
    展开全文
  • 在我们平时开发中,使用数据库连接池时使用阿里的Druid连接池已经比较常见了,但是我们在集成到Springboot时似乎非常简单,只需要简单的配置即可使用,那么Druid是怎么加载的呢,本文就从源码层面进行揭秘 ...
  • Druid连接池耗尽问题排查总结

    千次阅读 2021-06-11 20:11:52
    生产环境有个关键应用所有节点,每隔几个月就会发生一次Druid连接池耗尽问题,重启后恢复正常。 目前我们还没有定位到问题原因,这边文章主要记录定位过程的一些思路和方法,后续如果找到根本原因,再做更新。 思路 ...
  • 最近参与一个新项目,从老项目拷贝过来的代码,同样用的druid连接池 + PG。但是新项目却经常出现阻塞的情况,有时候甚至可以阻塞半个小时。一时间傻眼了。。 问题排查 问题背景 我们一共有6个开发同学,由于项目时间...
  • import java.sql.{Connection, PreparedStatement, SQLException, ...import com.alibaba.druid.pool.DruidDataSourceFactory import com.sm.common.conf.ConfigManager import com.sm.constants.Constants impo.
  • 第一步:导入两个jar包第二步:创建一个properties文件第三步:在properties文件中添加配置:连接驱动、数据库名字、数据库用户名和密码等driverClassName=...
  • Druid连接池对象

    2021-10-05 10:44:49
    配置文件 ...url=jdbc:mysql://...//初始化连接数量 initialSize=5 maxActive=10 //最大等待时间 maxWait=3000 定义一个JdbcPollutils类 public class JdbcPollutils { //定义成员变量 private static D..
  • 文章目录在数据源文件中中创建一个properties文件首先先创建一个数据源文件夹在数据源文件夹下创建源文件源文件中写入如下内容将源文件使用在工具类DButil类上,代码如下 ...#连接数据库的用户名 username
  • //创建数据库连接池 dataSource=(DruidDataSource) DruidDataSourceFactory.createDataSource(properties); }catch(Exception e) {//TODO Auto-generated catch block e.printStackTrace(); } }/*** 获取数据库连接...
  • Druid连接池不是springboot官方默认的连接池,springboot官方默认的连接池是HikariCP,我们要整合Druid连接池,就需要导入Druid连接池的jar包以及相关的配置。 1.在pom.xml中引入druid数据源 <dependency> ...
  • 数据库连接池: 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起...
  • Druid连接池原理

    2021-02-07 16:07:35
    一、数据库连接池初始化 (1)、 如果设置了maxWait或者构造函数参数传入的为true,则创建的ReentrantLock为公平锁,否者为非公平锁 (2)、 如果设置了initialSize>=1,则会启动是创建initialSize个数数据库物理...
  • quartz默认使用的是c3p0的连接池,记得有篇文章测试过c3p0 ,Proxool ,Druid ,Tomcat Jdbc Pool这四种连接池的性能。大致给出的测试数据为Druid >Tomcat Jdbc Pool >c3p0 >Proxool,以上仅供参考。最近项目...
  • MyBatis整合druid连接池

    2021-06-19 18:48:25
    MyBatis整合druid连接池: 1,引入druid依赖pom.xml <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.14</version> &...
  • 项目使用spring+springmvc+hibernate,数据库使用oracle11.2.0.1.0,允许的最大连接数为300,数据库服务器...druid连接池异常信息如下:[org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-...
  • 作者:董添使用Druid连接池和PreparedStatement Cache后,应用访问数据库的效率提高了,针对连接池的监控手段也变多了,但是随之而来也出现了一些新的问题,最近发现在给表增加字段后,应用不断地抛出"ORA-17401:...
  • Druid连接池官方配置

    2021-01-16 15:22:59
    Druid连接池配置官网地址 Druid连接池配置 更新时间: 2020-03-23 本文介绍如何通过JDBC Druid连接池连接AnalyticDB for MySQL。 注意事项 使用Druid连接池连接AnalyticDB for MySQL时,建议配置keepAlive=true,...
  • Druid 连接池配置今天碰到了Druid配置的相关问题,参数有点多,所以先码。Druid 是阿里开发的开源数据库连接池,通过池技术提升访问数据库的效率,至于原理,既然是池化技术,跟线程池差不多。下面为 可配置 参数...
  • Druid是阿里巴巴的开源连接池组件,是世界上最好的连接池之一。Druid能对数据库连接进行有效管理和重用,最大化程序执行的效率。连接池负责创建和管理连接,程序只负责取用与归还。 以下是我画的示意图: 下面我...
  • -- Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> 第二步配置application.yml文件,指定druid连接池,连接...
  • 使用Druid连接池连接数据库,首先先写配置资源,同时需要导druid-1.1.10.jar包 以及数据库驱动mysql-connector-java-8.0.15.jar包 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/表...
  • Druid连接池泄露

    2021-06-22 23:33:51
    连接池中连接数同一时间被用光 当超过指定时间时 还未等到连接 就会报以上错误 //报错信息 Could not get JDBC Connection; nested exception is ...
  • Druid 连接池连接 SQL Server遇到的问题代码展示问题列表 代码展示 /** * JDBC工具类 */ public class JDBCUtils { private static DruidDataSource ds;//连接池 //静态代码块,初始化类 static { try { ...
  • 我们继续接着上一篇讲解如何配置druid连接池连接mysql数据库,我这里用的数据库是8.0,用5.7或其他mysql版本也是一样的。关于数据库的安装,大家可以在百度搜索。1、添加druid连接池依赖druid是阿里巴巴开源的一款...
  • jdk8 \ mysql 5.7 \ alibaba druid 1.1.4 \ springBoot1.5.4.RELEASE项目环境 :使用率很低的项目 大约只有不到十个接口 操作数据库的间隔最长在12个小时以上项目启动连接没有任何问题 有时会在20分钟-30分钟之后就...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,325
精华内容 22,130
关键字:

druid连接池