美文网首页springbootDruidSpring杂谈
Spring杂谈之好用的连接池

Spring杂谈之好用的连接池

作者: 逐梦々少年 | 来源:发表于2019-11-24 17:38 被阅读0次
    日常开发中,数据库连接池是个必不可少的配置,使用优秀的数据库连接池,可以有效的提高数据库访问效
    率,降低连接异常等,本篇就来学习一下Spirngboot自带连接池和阿里Druid两个最常见的连接池
    

    什么是HikariCP

    HikariCP是由日本程序员开源的一个数据库连接池组件,代码非常轻量,并且速度非常的快。根据官方提供的数据,在i7,开启32个线程32个连接的情况下,进行随机数据库读写操作,HikariCP的速度是现在常用的C3P0数据库连接池的数百倍。在SpringBoot2.0中,官方默认也是使用的HikariCP作为数据库连接池,可见HikariCP连接池的目的就是为了极致的数据库连接性能体验,下面附上一张HikariCP和其他连接池的比较图:


    HikariCP连接池性能图.png

    从图中的结果可以看出来,HikariCP从性能来说的确一骑绝尘,那么HikariCP是如何做到这么极致的性能呢?主要依托于HikariCP自身所做的优化机制

    HikariCP优化机制

    字节码精简

    HikariCP优化了代码,尽量减少了生成的字节码,使得cpu可以加载更多程序代码

    优化了拦截和代理机制

    HikariCP对拦截器机制和代理机制进行了代码优化处理,例如Statement proxy只有100行代码,大大减少了代码量,只有其他连接池例如BoneCP的十分之一

    自定义数组

    HikariCP针对数组操作进行了自定义数组--FastStatementList,用来替代jdk的ArrayList,优化了get、remove等方法,避免了每次调用get的时候进行范围检查,也避免了每次remove操作的时候会将数据从头到尾进行扫描的性能问题

    自定义集合

    同样的,针对jdk自带的集合类,HikariCP专门封装了无锁的集合类型 ,用来提高在使用中的读写并发的效率,减少并发造成的资源竞争问题

    CPU时间片算法优化

    HikariCP也对cpu时间片分配算法进行了优化,尽可能使得一个时间片内完成相关的操作

    使用HikariCP

    了解了HikariCP以后,我们开始使用吧,首先找到HikariCP的坐标:

    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>2.7.6</version>
    </dependency>
    

    然后配置HikariCP对应的配置文件,用来读取/加载连接池配置:

    /**
     * HikariCP连接池配置
     */
    @Configuration
    public class DataSourceConfig {
    
        @Value("${spring.datasource.url}")
        private String dataSourceUrl;
    
        @Value("${spring.datasource.username}")
        private String user;
    
        @Value("${spring.datasource.password}")
        private String password;
    
        @Bean
        public DataSource primaryDataSource() {
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl(dataSourceUrl); //数据源url
            config.setUsername(user); //用户名
            config.setPassword(password); //密码
            config.addDataSourceProperty("cachePrepStmts", "true"); //是否自定义配置,为true时下面两个参数才生效
            config.addDataSourceProperty("prepStmtCacheSize", "250"); //连接池大小默认25,官方推荐250-500
            config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); //单条语句最大长度默认256,官方推荐2048
            config.addDataSourceProperty("useServerPrepStmts", "true"); //新版本MySQL支持服务器端准备,开启能够得到显著性能提升
            config.addDataSourceProperty("useLocalSessionState", "true");
            config.addDataSourceProperty("useLocalTransactionState", "true");
            config.addDataSourceProperty("rewriteBatchedStatements", "true");
            config.addDataSourceProperty("cacheResultSetMetadata", "true");
            config.addDataSourceProperty("cacheServerConfiguration", "true");
            config.addDataSourceProperty("elideSetAutoCommits", "true");
            config.addDataSourceProperty("maintainTimeStats", "false");
    
            HikariDataSource ds = new HikariDataSource(config);
            return ds;
        }
    }
    

    接着我们在SpringBoot的application.properties 文件中进行配置:

    server.port=8090
    
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driverClassName=com.mysql.jdbc.Driver
    
    spring.datasource.max-idle=10
    spring.datasource.max-active=15
    spring.datasource.max-lifetime=86430000
    spring.datasource.log-abandoned=true
    spring.datasource.remove-abandoned=true
    spring.datasource.remove-abandoned-timeout=60
    spring.datasource.initialize=false
    spring.datasource.sqlScriptEncoding=UTF-8
    

    配置完毕,此时我们启动工程,即可看到控制台已经将我们配置的HikariCP数据库连接池信息打印出来了

    阿里巴巴Druid

    提到大名鼎鼎的Druid连接池,相信很多人都不陌生,因为该连接池是阿里开源的优秀的连接池,几乎已经成为现在使用最多的连接池之一。我们先打开Druid的官方github:

    https://github.com/alibaba/druid 
    

    可以看到此项目已经有19.4k的star数,并且是2019最受欢迎的开源之一,经历过真实线上双十一的考验,可以说是个很成熟的开源连接池,而Druid连接池专为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Logging能诊断Hack应用行为等。

    快速使用

    Druid 0.1.18 之后版本都发布到maven中央仓库中,所以你只需要在项目的pom.xml中加上dependency就可以了,中央仓库坐标如下:

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

    这里我们使用了springBoot,由于默认支持的数据连接池只有四种:dbcp,dbcp2, tomcat, hikariCP,并不包含druid,所以我们这里也可以选择直接使用阿里官方编写的druid-spring-boot-starter,并且我们添加对应的mybatis和pageHelper的依赖:

    <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- Mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>
    <!--Mybatis 分页插件 pagehelper -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.1.1</version>
    </dependency>
    <!-- Druid连接池包 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.1</version>
    </dependency>
    

    在application.properties中 进行数据源配置:

    # 数据库访问配置
    # 主数据源,默认的
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/test
    spring.datasource.username=root
    spring.datasource.password=123456
    
    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    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=5000
    # 合并多个DruidDataSource的监控数据
    #spring.datasource.useGlobalDataSourceStat=true
    

    配置完毕以后,由于这里我们使用了mybatis,所以我们还要在application.properties中配置一下mybatis相关:

    #mybatis
    #entity扫描的包名
    mybatis.type-aliases-package=com.xiaolyuh.domain.model
    #Mapper.xml所在的位置
    mybatis.mapper-locations=classpath*:/mybaits/*Mapper.xml
    #开启MyBatis的二级缓存
    mybatis.configuration.cache-enabled=true
    
    #pagehelper
    pagehelper.helperDialect=mysql
    pagehelper.reasonable=true
    pagehelper.supportMethodsArguments=true
    pagehelper.params=count=countSql
    

    准备就绪后,我们来编写一个测试类:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DataSourceTests {
    
        @Autowired
        ApplicationContext applicationContext;
    
        @Test
        public void testDataSource() throws Exception {
            // 获取配置的数据源
            DataSource dataSource = applicationContext.getBean(DataSource.class);
    System.out.println(dataSource.getClass().getName());
        }
    }
    

    将测试方法运行起来,即可在控制台中看到对应的数据源的输出信息

    Druid开启监控统计功能

    druid最强大的功能就是自身提供了对sql的数据监控功能,并且内置了很多详细的拦截器,可以实现多个角度的拦截处理,那么如何开启监控?在Druid中内置提供了一个StatViewServlet用于展示Druid的统计信息,这个StatViewServlet的用途包括:

    • 提供监控信息展示的html页面
    • 提供监控信息的JSON API

    如果是ssm工程,则可以在web.xml中配置StatViewServlet,如下:

    <servlet>
          <servlet-name>DruidStatView</servlet-name>
          <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
      </servlet>
      <servlet-mapping>
          <servlet-name>DruidStatView</servlet-name>
          <url-pattern>/druid/*</url-pattern>
      </servlet-mapping>
    

    配置完毕以后,启动工程则可以按照配置的监控地址访问监控信息,默认为:http://ip:port/project-name/druid/index.html

    同样的,在StatViewServlet中我们可以添加访问密码的设置,只需要配置Servlet的 loginUsernameloginPassword这两个初始参数 即可,例如:

    <!-- 配置 Druid 监控信息显示页面 -->  
    <servlet>  
        <servlet-name>DruidStatView</servlet-name>  
        <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>  
        <init-param>  
        <!-- 允许清空统计数据 -->  
        <param-name>resetEnable</param-name>  
        <param-value>true</param-value>  
        </init-param>  
        <init-param>  
        <!-- 用户名 -->  
        <param-name>loginUsername</param-name>  
        <param-value>druid</param-value>  
        </init-param>  
        <init-param>  
        <!-- 密码 -->  
        <param-name>loginPassword</param-name>  
        <param-value>druid</param-value>  
        </init-param>  
    </servlet>  
    <servlet-mapping>  
        <servlet-name>DruidStatView</servlet-name>  
        <url-pattern>/druid/*</url-pattern>  
    </servlet-mapping> 
    

    此时访问监控页面的时候就需要输入我们设置的用户名和密码了,如果还想针对用户有敏感信息配置和访问权限控制,我们还可以配置allowdeny参数,例如:

    <servlet>
          <servlet-name>DruidStatView</servlet-name>
          <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
        <init-param>
            <param-name>allow</param-name>
            <param-value>128.242.127.1/24,128.242.128.1</param-value>
        </init-param>
        <init-param>
            <param-name>deny</param-name>
            <param-value>128.242.127.4</param-value>
        </init-param>
      </servlet>
    

    这里的访问规则为:

    1.deny配置优先于allow,即deny为优先拒绝,即使在allow中配置了白名单,但是只要存在于deny中,一样也会被拒绝访问

    2.如果allow没有配置,或者配置为空,默认为全部都可以访问,不进行白名单限制

    使用SpringBoot配置监控

    由于我们这里使用的是SpringBoot,所以我们仅需要在application.properties 添加配置统计拦截的filters:

    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    spring.datasource.druid.filters=stat,wall,log4j
    

    这里的配置是通过别名方式配置扩展支持的插件,如下:

    • 监控统计用的filter:stat
    • 日志用的filter:log4j
    • 防御sql注入的filter:wall

    接着我们需要在application.properties继续添加WebStatFilterStatViewServlet的配置项:

    # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
    #启动项目后访问 http://127.0.0.1:8080/druid
    #是否启用StatFilter默认值true
    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/*
    #缺省sessionStatMaxCount是1000个
    spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
    #关闭session统计功能
    spring.datasource.druid.web-stat-filter.session-stat-enable=false
    #配置principalSessionName,使得druid能够知道当前的session的用户是谁
    #如果你session中保存的是非string类型的对象,需要重载toString方法
    spring.datasource.druid.web-stat-filter.principalSessionName=xxx.user
    #如果user信息保存在cookie中,你可以配置principalCookieName,使得druid知道当前的user是谁
    spring.datasource.druid.web-stat-filter.principalCookieName=xxx.user
    #druid 0.2.7版本开始支持profile,配置profileEnable能够监控单个url调用的sql列表。
    spring.datasource.druid.web-stat-filter.profile-enable=false
    
    # StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
    #启动项目后访问 http://127.0.0.1:8080/druid
    #是否启用StatViewServlet默认值true
    spring.datasource.druid.stat-view-servlet.enabled=true
    spring.datasource.druid.stat-view-servlet.urlPattern=/druid/*
    #禁用HTML页面上的“Reset All”功能
    spring.datasource.druid.stat-view-servlet.resetEnable=false
    #用户名
    spring.datasource.druid.stat-view-servlet.loginUsername=admin
    #密码
    spring.datasource.druid.stat-view-servlet.loginPassword=admin
    #IP白名单(没有配置或者为空,则允许所有访问)
    spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1
    #IP黑名单 (存在共同时,deny优先于allow)
    spring.datasource.druid.stat-view-servlet.deny=192.168.1.73
    

    接着我们启动工程,访问http://localhost/druid ,输入配置的用户名:admin以及密码:admin,即可看到druid的监控页面:

    druid监控页面.png
    慢sql日志打印

    在开发过程中,往往会遇到sql时间过长问题,为了定位慢sql,我们往往会定义固定时长作为慢sql的时长,而Druid支持慢sql查询,在Druid中内置提供了一个StatFilter,用于统计监控信息 ,我们可以利用这个StatFilter来统计慢sql:

    StatFilter的别名是stat,这个别名映射配置信息保存在druid-xxx.jar!/META-INF/druid-filter.properties
    

    我们需要在Spring中加入以下配置:

     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="filters" value="stat" />
      </bean>
    

    当然如果需要我们可以同时开启多个Filter进行组合使用,中间用,隔开即可,如下:

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="filters" value="stat,log4j" />
      </bean>
    

    而如果开启慢sql的记录,我们需要先定义slowSqlMillis 来配置sql慢查询的标准,如下:

    <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
        <property name="slowSqlMillis" value="10000" />
        <property name="logSlowSql" value="true" />
    </bean>
    

    配置完毕以后,所有的超过10s的sql都会在监控页面的慢sql模块记录,可以查看具体的sql以及执行时间等,快速定位开发过程中的慢sql

    DruidDataSource配置

    如果我们根据业务的不同,需要更改不同的配置,这个时候我们就需要参考DriudDataSource的配置,通用的配置项如下:

    配置 缺省值 描述
    name 如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this)
    url 连接数据库的url
    username 连接数据库的用户名
    password 连接数据库的密码
    driverClassName 根据url自动识别 可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
    initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
    maxActive 8 最大连接池数量
    maxIdle 8 已经弃用
    minIdle 最小连接池数量
    maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁
    poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
    maxPoolPreparedStatementPerConnectionSize -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
    validationQuery 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
    validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。
    testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
    keepAlive false 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作
    timeBetweenEvictionRunsMillis 60s 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
    numTestsPerEvictionRun 30分钟 已经弃用
    minEvictableIdleTimeMillis 连接保持空闲而不被驱逐的最小时间
    connectionInitSqls 物理连接初始化的时候执行的sql
    exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
    filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
    proxyFilters 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
    Druid常见配置与问题

    除了我们已经了解的druid常见知识以外,开发中经常还会遇到很多其他常见需求,如开启druid的防sql注入功能、记录每次执行的sql、数据库加密、log输出执行的sql等常见需求,这个时候我们就可以在官方的github的文档中查找,官方已经给我们整理好了一些开发常见的问题,地址如下:

    https://github.com/alibaba/druid/wiki/常见问题
    

    总结

    在实际开发过程中,我们往往会根据自身需求或者项目本身来选择最适合的连接池,这里我们将常见的数据连接池从可扩展性、可靠稳定性、性能、可运维性以及自身功能几个方向进行了比较,可供参考:


    连接池比较图.jpg

    相关文章

      网友评论

        本文标题:Spring杂谈之好用的连接池

        本文链接:https://www.haomeiwen.com/subject/ptolwctx.html