目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

连接池

HikariCP

HikariCP来自于日语,意为:光。 它本身作为 SpringBoot 2.X 默认的连接池。
HikariCP-bench

"Fast"源于深度优化和大量小改进

  • 字节码级别优化(很多方法通过 JavaAssist 生成)
  • 大量小改进:用 FastStatementList 代替 ArrayList、无锁集合 ConcurrentBag 、 代理类的优化(用 invokestatic 代替 invokevirtual)

SpringBoot 中的配置

  • 默认使用 HikariCP
  • application.properties 配置格式 spring.datasource.hikari.*
  • 如果是 SpringBoot 1.x 需要在 maven 中移除默认的 tomcat-jdbc 依赖,并且引入 HikariCP 的 Maven 依赖,并且在 application.properties 配置文件中指定默认的连接池类型 spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 暴露Actuator  
management.endpoints.web.exposure.include=*  
  
# 如果你的终端支持ANSI,设置彩色输出会让日志更具可读性  
# DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项)  
# ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐  
spring.output.ansi.enabled=ALWAYS  
  
# 通用配置  
spring.datasource.url=jdbc:h2:mem:testdb  
spring.datasource.username=sa  
spring.datasource.password=  
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver #(可选),因为springboot会根据spring.datasource.url 自动判断,选择合适的驱动。  
  
# 连接池配置  
spring.datasource.hikari.maximumPoolSize=5  
spring.datasource.hikari.minimumIdle=5  
spring.datasource.hikari.idleTimeout=600000  
spring.datasource.hikari.connectionTimeout=30000  
spring.datasource.hikari.maxLifetime=1800000

常用配置说明

namedescriptiondefault_value
maximumPoolSize此属性控制允许池达到的最大大小,包括空闲和使用中的连接。基本上,此值将确定到数据库后端的最大实际连接数。合理的值最好由您的执行环境确定。当池达到此大小并且没有空闲连接可用时,对 getConnection()的调用将 connectionTimeout 在超时之前最多阻塞毫秒。10
minimumIdle此属性控制 HikariCP 尝试在池中维护的最小空闲连接数。如果空闲连接下降到该值以下,并且池中的总连接数少于 maximumPoolSize ,则 HikariCP 将尽最大努力快速而有效地添加其他连接。但是,为了获得最佳性能和对峰值需求的响应能力,我们建议不要设置此值,而应让 HikariCP 充当固定大小的连接池。 默认值:与 maximumPoolSize 相同10
maximumPoolSize
idleTimeout连接允许在池中闲置的最长时间600000(10 分钟)
connectionTimeout此属性控制客户端(即您)等待来自池的连接的最大毫秒数。如果超过此时间而没有可用的连接,则会抛出 SQLException。可接受的最低连接超时为 250 ms。30000(30 秒)
maxLifetime用来设置一个 connection 在连接池中的存活时间1800000,即 30 分钟。如果设置为 0,表示存活时间无限大。如果不等于 0 且小于 30 秒则会被重置回 30 分钟。
更多配置官网

Alibaba Druid

 “Druid 连接池是阿 ⾥巴巴开源的数据库连接池项 ⽬。Druid 连接池为监控而生,内置强 ⼤的监控功能,监控特性不影响性能。功能强 ⼤,能防 SQL 注 ⼊,内置 Logging 能诊断 Hack 应用行为。”
WiKi

  • 详细的监控
  • ExceptionSorter,针对主流数据的返回码都有支持
  • SQL 防注入
  • 内置加密配置
  • 众多扩展点,方便进行定制

SpringBoot 方式

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>geektime.spring.data</groupId>
	<artifactId>druid-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>druid-demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
			<!-- 需要手动排除默认的连接池 HikariCP -->
			<exclusions>
				<exclusion>
					<artifactId>HikariCP</artifactId>
					<groupId>com.zaxxer</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- 引入 druid-spring-boot-starter -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

application.properties

spring.output.ansi.enabled=ALWAYS

spring.datasource.url=jdbc:h2:mem:foo
spring.datasource.username=sa
spring.datasource.password=n/z7PyA5cvcXvs8px8FVmBVpaRyNsvJb3X7YfS38DJrIg25EbZaZGvH4aHcnc97Om0islpCAPc3MqsGvsrxVJw==

spring.datasource.druid.initial-size=5
spring.datasource.druid.max-active=5
spring.datasource.druid.min-idle=5
# Filter配置
spring.datasource.druid.filters=conn,config,stat,slf4j

# 密码加密
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${public-key}
spring.datasource.druid.filter.config.enabled=true

spring.datasource.druid.test-on-borrow=true
spring.datasource.druid.test-on-return=true
spring.datasource.druid.test-while-idle=true

# druid-spring-boot-starter  默认就会开启统计功能的filter  所以注释掉了
#spring.datasource.druid.filter.stat.enabled=true

# 开启慢sql日志 (默认是监控那些执行超过3秒的sql语句)
spring.datasource.druid.filter.stat.log-slow-sql=true
# 默认抓取超过100毫秒的sql语句
spring.datasource.druid.filter.stat.slow-sql-millis=100

public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALS8ng1XvgHrdOgm4pxrnUdt3sXtu/E8My9KzX8sXlz+mXRZQCop7NVQLne25pXHtZoDYuMh3bzoGj6v5HvvAQ8CAwEAAQ==

启动类

package geektime.spring.data.druiddemo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@SpringBootApplication
@Slf4j
public class DruidDemoApplication implements CommandLineRunner {
	@Autowired
	private DataSource dataSource;
	@Autowired
	private JdbcTemplate jdbcTemplate;

	public static void main(String[] args) {
		SpringApplication.run(DruidDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		log.info(dataSource.toString());
	}
}

控制台输出 发现 Connections 中包含 5 个连接

CreateTime:"2020-01-07 13:33:11",
	ActiveCount:0,
	PoolingCount:5,
	CreateCount:5,
	DestroyCount:0,
	CloseCount:0,
	ConnectCount:0,
	Connections:[
		{ID:1409712092, ConnectTime:"2020-01-07 13:33:11", UseCount:0, LastActiveTime:"2020-01-07 13:33:11"},
		{ID:1083759991, ConnectTime:"2020-01-07 13:33:11", UseCount:0, LastActiveTime:"2020-01-07 13:33:11"},
		{ID:1135523296, ConnectTime:"2020-01-07 13:33:11", UseCount:0, LastActiveTime:"2020-01-07 13:33:11"},
		{ID:36127072, ConnectTime:"2020-01-07 13:33:11", UseCount:0, LastActiveTime:"2020-01-07 13:33:11"},
		{ID:1698182759, ConnectTime:"2020-01-07 13:33:11", UseCount:0, LastActiveTime:"2020-01-07 13:33:11"}
	]
}

# 如果有慢sql会显示
2020-01-08 17:36:04.426 ERROR 2004 --- [       Thread-3] c.alibaba.druid.filter.stat.StatFilter   : slow sql 209 millis. select id from foo where id = 1 for update[]

Druid Filter

  • ⽤于定制连接池操作的各种环节
  • 可以继承 FilterEventAdapter 以便 ⽅便地实现 Filter
package geektime.spring.data.druiddemo;

import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.filter.FilterEventAdapter;
import com.alibaba.druid.proxy.jdbc.ConnectionProxy;
import lombok.extern.slf4j.Slf4j;

import java.util.Properties;

@Slf4j
public class ConnectionLogFilter extends FilterEventAdapter {
    /**
     * 复写的方法名字 代表 扩展点所在的位置
     */
    @Override
    public void connection_connectBefore(FilterChain chain, Properties info) {
        log.info("BEFORE CONNECTION!");
    }

    @Override
    public void connection_connectAfter(ConnectionProxy connection) {
        log.info("AFTER CONNECTION!");
    }
}
  • 修改 META-INF/druid-fifilter.properties 增加 Filter 配置
druid.filters.conn=geektime.spring.data.druiddemo.ConnectionLogFilter

Spring 方式

引入依赖

<dependency>  
 <groupId>com.alibaba</groupId>  
 <artifactId>druid</artifactId>  
 <version>1.1.0</version>  
 </dependency>

application.yml

server:  
 port: 8091  
druid:  
 allow:  
 ip: 127.0.0.1  
 login:  
 user_name: root  
 password: root  
spring:  
 datasource:  
 driver-class-name: com.mysql.jdbc.Driver  
 url: jdbc:mysql://192.168.31.201:3306/shop?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false  
 username: root  
 password: 123456  
 type: com.alibaba.druid.pool.DruidDataSource  
 initial-size: 1  
 min-idle: 1  
 max-idle: 5  
 max-wait-millis: 60000  
 timeBetweenEvictionRunsMillis: 60000  
 # 配置一个连接在池中最小生存的时间,单位是毫秒  
 minEvictableIdleTimeMillis: 300000  
 validationQuery: SELECT 1 FROM DUAL  
 testWhileIdle: true  
 testOnBorrow: true  
 testOnReturn: false  
 maxPoolPreparedStatementPerConnectionSize: 20  
 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙  
 filters: stat,wall  
 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录  
 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

DruidConfig

package com.xdclass.couponapp.config;  
  
import com.alibaba.druid.pool.DruidDataSource;  
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 javax.sql.DataSource;  
import java.util.HashMap;  
import java.util.Map;  
  
@Configuration  
public class DruidConfig {  
  
  
 @Value("${druid.login.user_name}")  
 private String userName;  
  
 @Value("${druid.login.password}")  
 private String password;  
  
 @Bean(name = "default_datadatasource")  
 @ConfigurationProperties(prefix = "spring.datasource")  
 public DataSource druidDataSource(){  
 return new DruidDataSource();  
 }  
  
 @Bean  
 public ServletRegistrationBean druidServlet() {  
 ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();  
 servletRegistrationBean.setServlet(new StatViewServlet());  
 servletRegistrationBean.addUrlMappings("/druid/*");  
 Map<String, String> initParameters = new HashMap<>(3);  
 // 用户名  
 initParameters.put("loginUsername", userName);  
 // 密码  
 initParameters.put("loginPassword", password);  
 // 禁用HTML页面上的“Reset All”功能  
 initParameters.put("resetEnable", "false");  
 servletRegistrationBean.setInitParameters(initParameters);  
 return servletRegistrationBean;  
 }  
  
 @Bean  
 public FilterRegistrationBean filterRegistrationBean() {  
 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();  
 filterRegistrationBean.setFilter(new WebStatFilter());  
 filterRegistrationBean.addUrlPatterns("/*");  
 filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");  
 return filterRegistrationBean;  
 }  
}

Druid 监控台

# http://localhost:8091/druid  
 root  
 root

Durid使用注意事项

  • 没有特殊情况,不要在生产环境打开监控的Servlet,负载情况以日志输出为基准。
  • 没有连接泄漏可能的情况下,不要开启removeAbandoned。(大量和使用ORM框架的情况下。)
  • testXxx 的使用需要注意,性能开销比较大。
  • 务必配置合理的超时时间。

作者:Soulboy