Java动态数据源配置、动态连接池配置、多数据源负载均衡
大家好,今天给大家推荐一个自产的连接池插件。废话不多说,本文接口分为以下主题:
1. 插件开发背景;
2. 插件提供的能力;
3. 插件的使用介绍;
4. 插件的使用实例;
5. 插件的源码。
PS: 该插件已经提交到中央仓库,可以在maven配置中直接引用。截止当前时间,最新版本v1.1.0。获取该项目最新消息及源码,
请移步: 源码地址
<dependency>
<groupId>com.xieahui.springboot</groupId>
<artifactId>spring-boot-easy-connection-pool</artifactId>
<version>1.1.0-RELEASE</version>
</dependency>
1. 插件的开发背景?
由于公司筹建数据中台服务,数据中台服务其中有个环节需要整合数据以及对外提供数据。数据中台项目中有两个特别明确的服务,一个是进数据的服务、一个是出数据的服务。这种服务通常都有一个特点,需要对外提供各种各样的数据,由于不同类型的数据存放在不同的数据源中,所以会导致该服务需要对接多种多样的数据源。如果想要设计一套以不变应万变的数据服务,那么动态管理数据源就变得尤其重要了。常规MySql数据负载的解决方案较多,但是向我们的Clickhouse、Impala等数据库可以通用的支持动态构建数据库连接池的组件就不是特别多了。基于这个场景,我决定自研这套支持JDBC驱动、可以动态从配置文件中读取、动态从数据库中读取、可以根据业务标识手动设置的、独立的、分组的动态数据源构建连接池插件。
2. 插件提供的能力?
- 支持动态从properties中获取数据配置;
- 支持动态从db中获取数据库配置;
- 可根据配置动态构建连接池;
- 可以从配置文件或db中动态构建分组连接池;
- 分组连接池支持负载均衡策略;
- 随机负载均衡;
- 轮询负载均衡;
- 支持根据实际业务动态扩展。
3. 插件的使用介绍?
插件简介
基于上述项目背景,统一封装了多数据源连接池动态化配置插件,连接池使用的是业界号称"最快的"Hikari(SpringBoot2默认的连接池);只需在项目中引入插件,在属性文件中添加必要的数据源连接配置信息及连接池参数,就可以通过注解进而动态切换数据源;无论是读写分离、主从配置还是一主多从的数据源均可通过此动态配置生成DataSource连接池。 项目中各部分注释比较详细,对于二次开发扩展也非常方便,同时在启动过程会打印出详细的连接池配置流程以及连接池中的表信息:
插件功能特点
1. 动态支持多数据源连接池配置
2. 理论上支持无限多个数据源连接池
3. 通过注解切换数据源
4. 对于数据库读写分离、一主多从等业务场景非常适用
5. 代码逻辑简单,扩展来自原生Spring接口;注释详细,易于二次开发
使用实例
和该项目配置的实例项目 可以点击查看
V1.1.0 - 更新说明
主要增加如下:
1. 更新数据库脚本;
2. 更新注解TargetDataSource;
3. 更新逻辑指定配置DynamicDbSource;
4. 增加数据源连接分组;
5. 增加负载均衡策略:
A. 随机负载均衡策略;
B. 轮询负载均衡策略;
新增功能演示实例
该版本对应的演示实例:
easy-connection-pool-demo -> easy-jdbctemplate -> com.xieahui.easy.jdbctemplate.controller.GroupDSController
更新数据库脚本
新增分组名称字段group_name、负载均衡类型字段balance_type、增加连接池名称pool_name非空唯一限制。 脚本路径:
resources->script->db_entity.sql
更新注解TargetDataSource
更新注解TargetDataSource适配分组、负载逻辑; 新增注解配置分组groupName、注解配置连接池名称poolName、注解配置负载均衡策略balanceType
更新逻辑指定配置DynamicDbSource
更新逻辑指定配置DynamicDbSource适配分组、负载逻辑; 新增指定(分组名称)方法setGroupName、指定(分组,具体数据源名称)方法setGroupNamePoolName、指定(分组,数据源名称,负载策略)方法setGroupNamePoolNameBalanceType
增加数据源连接分组
数据源加载优先级:
db -> properties
数据源使用优先级:
properties -> properties-group -> db -> db-group
增加负载均衡策略
分组后会涉及到分组数据源的选取策略,1.1.0版本实现了随机、轮询两种较常见的负载均衡策略。默认使用轮询策略。
V1.0.1 - 更新说明
新增从默认数据库中获取数据源连接信息,使用说明。
1. 开启从数据库中加载数据源属性设置:
spring.datasource.db.open=true
2. 创建数据库数据源表
最新脚本路径:
resources->script->db_entity.sql
CREATE TABLE `db_entity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`driver_class_name` varchar(45) DEFAULT 'com.mysql.cj.jdbc.Driver' COMMENT '驱动类',
`jdbc_url` varchar(45) DEFAULT NULL COMMENT '数据库连接地址<jdbc:mysql://ip:port/db>,必填!',
`pool_name` varchar(45) NOT NULL COMMENT '连接池名称,必填!',
`username` varchar(45) DEFAULT NULL COMMENT '用户名',
`password` varchar(45) DEFAULT NULL COMMENT '密码',
`minimum_idle` int(11) DEFAULT '5' COMMENT '最小空闲连接数',
`maximum_pool_size` int(11) DEFAULT '10' COMMENT '最大连接数',
`connection_test_query` varchar(45) DEFAULT 'SELECT 1' COMMENT '测试连接是否有效SQL',
`group_name` varchar(45) DEFAULT NULL COMMENT '分组名',
`balance_type` varchar(45) DEFAULT NULL COMMENT '负载均衡类型',
PRIMARY KEY (`id`),
UNIQUE KEY `pool_name_UNIQUE` (`pool_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
该表结构在resources/script/db_entity.sql文件中
3. 添加动态数据源注解
设置在执行的方法上:
@TargetDataSource
4. 动态指定数据源
// DynamicDbSource.set("db3");
@TargetDataSource
public List<MyDb3> findAll() throws InterruptedException {
//动态数据源设置
DynamicDbSource.set("db3");
TimeUnit.SECONDS.sleep(60);
return myDb3Dao.findAll();
}
PS: DynamicDbSource.set("连接池名称"),可以根据自己的实际业务逻辑设置数据源名称。例如我们需要根据请求的pk获取当前连接对应的数据源配置, 获取到名字后在这里设置为数据源名字即可。
5. 实例地址
https://github.com/xieyucan/easy-connection-pool-demo/blob/master/easy-jdbctemplate/src/main/java/com/xieahui/easy/jdbctemplate/service/MyDb3Service.java
启动信息
系统优先使用注解方法上的属性配置,注解方法上没有配置的情况下会读取DynamicDbSource设置的数据源配置。系统启动时会日志中会打印出当前创建连接 池的情况,以及连接池中的数据表。
com.zaxxer.hikari.HikariDataSource : HikariCP1 - Starting...
com.zaxxer.hikari.HikariDataSource : HikariCP1 - Start completed.
c.x.s.config.DynamicDataSourceRegister : *** Create DataSource Default Success! ***
c.x.s.config.DynamicDataSourceRegister : ***Print-Tables-Start***:
c.x.s.config.DynamicDataSourceRegister : db
c.x.s.config.DynamicDataSourceRegister : db_entity
c.x.s.config.DynamicDataSourceRegister : hibernate_sequence
c.x.s.config.DynamicDataSourceRegister : student
c.x.s.config.DynamicDataSourceRegister : ***Print-Tables-End***.
com.zaxxer.hikari.HikariDataSource : db3 - Starting...
com.zaxxer.hikari.HikariDataSource : db3 - Start completed.
c.x.s.config.DynamicDataSourceRegister : *** Create DataSource db3 Success! ***
c.x.s.config.DynamicDataSourceRegister : ***Print-Tables-Start***:
c.x.s.config.DynamicDataSourceRegister : my_db3
c.x.s.config.DynamicDataSourceRegister : ***Print-Tables-End***.
com.zaxxer.hikari.HikariConfig : HikariCP2 - idleTimeout has been set but has no effect because the pool is operating as a fixed size pool.
com.zaxxer.hikari.HikariDataSource : HikariCP2 - Starting...
com.zaxxer.hikari.HikariDataSource : HikariCP2 - Start completed.
c.x.s.config.DynamicDataSourceRegister : *** Create DataSource db1 Success! ***
c.x.s.config.DynamicDataSourceRegister : ***Print-Tables-Start***:
c.x.s.config.DynamicDataSourceRegister : hibernate_sequence
c.x.s.config.DynamicDataSourceRegister : student
c.x.s.config.DynamicDataSourceRegister : teacher
c.x.s.config.DynamicDataSourceRegister : user
c.x.s.config.DynamicDataSourceRegister : ***Print-Tables-End***.
c.x.s.config.DynamicDataSourceRegister : Dynamic DataSource Registry
在MyBatis中的用法
演示项目 easy-mybatis
演示用例 easy-mybatis-MyBatisApplicationTest
在JPA中的用法
演示项目 easy-jpa
演示用例 easy-jpa-JpaApplicationTest
在JdbcTemplate中的用法
演示项目 easy-jdbctemplate
演示用例 easy-jpa-JdbcTemplateApplicationTest
使用说明
插件是基于SpringBoot开发maven管理的,使用步骤如下:
1.添加插件maven依赖
<dependency>
<groupId>com.xieahui.springboot</groupId>
<artifactId>spring-boot-easy-connection-pool</artifactId>
<version>1.0.1-RELEASE</version>
</dependency>
2.在启动类上添加注解开启动态数据源
@EnableDynamicDataSource
3.必要的连接属性配置 说明:spring.datasource.names属性配置数据资源名(如果连接的数据源较少,请在此处移除掉)。默认数据源使用了JPA,使用的持久层技术有JdbcUtils,Hibernate,IBatis和MyBatis,JdbcTemplate,Jpa(现有项目最常用的持久层技术是JPA+JdbcTemplate;持久层技术方案众多,好坏只有自己去品)。能解决业务场景问题、开发效率高用的开心就好。
server.port=8080
#db,数据源名字
spring.datasource.names=db1,db2,db3
spring.datasource.type=com.zaxxer.hikari.util.DriverDataSource
#jpa
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
#mysql-1
#表示使用基于DriverManager的配置
spring.datasource.hikari.jdbcUrl=jdbc:mysql://localhost:3306/db
#数据库连接用户名
spring.datasource.hikari.username=root
#数据库连接密码
spring.datasource.hikari.password=root
#数据库连接驱动
spring.datasource.hikari.driverClassName=com.mysql.cj.jdbc.Driver
#池中维护的最小空闲连接数,如果空闲连接低于此值并且总连接数小于maximumPoolSize,则HC将快速有效的添加其他连接
spring.datasource.hikari.minimumIdle=10
#池中维护的最大连接数
spring.datasource.hikari.maximumPoolSize=15
#控制连接是否自动提交事务
spring.datasource.hikari.autoCommit=true
#空闲连接闲置时间
spring.datasource.hikari.idleTimeout=30000
#连接池名称
spring.datasource.hikari.poolName=HikariCP1
#连接最长生命周期,如果不等于0且小于30秒则会被重置回30秒
spring.datasource.hikari.maxLifetime=1800000
#从池中获取连接的最大等待时间默认30000毫秒,如果小于250毫秒则被重置会30秒
spring.datasource.hikari.connectionTimeout=30000
#测试连接有效性最大超时时间,默认5秒如果小于250毫秒,则重置回5秒
spring.datasource.hikari.validationTimeout=5000
#测试连接是否有效SQL,PS:不同数据库的测试SQL有可能不一样!
spring.datasource.hikari.connectionTestQuery=SELECT 1
#控制默认情况下从池中获取的Connections是否处于只读模式
spring.datasource.hikari.readOnly=false
#是否在其自己的事务中隔离内部池查询,例如连接活动测试。通常使用默认值,默认值:false
spring.datasource.hikari.isolateInternalQueries=false
#此属性控制是否注册JMX管理Bean
spring.datasource.hikari.registerMbeans=false
#此属性控制是否可以通过JMX(Java Management Extensions,即Java管理扩展,它提供了一种在运行时动态管理资源的体系结构)挂起和恢复池
spring.datasource.hikari.allowPoolSuspension=false
#mysql-2
spring.datasource.db1.hikari.jdbcUrl=jdbc:mysql://localhost:3306/db1
spring.datasource.db1.hikari.username=root
spring.datasource.db1.hikari.password=root
spring.datasource.db1.hikari.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.db1.hikari.minimumIdle=10
spring.datasource.db1.hikari.maximumPoolSize=15
spring.datasource.db1.hikari.autoCommit=true
spring.datasource.db1.hikari.idleTimeout=30000
spring.datasource.db1.hikari.poolName=HikariCP2
spring.datasource.db1.hikari.maxLifetime=1800000
spring.datasource.db1.hikari.connectionTimeout=30000
spring.datasource.db1.hikari.validationTimeout=5000
spring.datasource.db1.hikari.connectionTestQuery=SELECT 1
spring.datasource.db1.hikari.readOnly=false
spring.datasource.db1.hikari.isolateInternalQueries=false
spring.datasource.db1.hikari.registerMbeans=false
spring.datasource.db1.hikari.allowPoolSuspension=false
#mysql-2
spring.datasource.db2.hikari.jdbcUrl=jdbc:mysql://localhost:3306/db2
spring.datasource.db2.hikari.username=root
spring.datasource.db2.hikari.password=root
spring.datasource.db2.hikari.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.db2.hikari.minimumIdle=10
spring.datasource.db2.hikari.maximumPoolSize=15
spring.datasource.db2.hikari.autoCommit=true
spring.datasource.db2.hikari.idleTimeout=30000
spring.datasource.db2.hikari.poolName=HikariCP3
spring.datasource.db2.hikari.maxLifetime=1800000
spring.datasource.db2.hikari.connectionTimeout=30000
spring.datasource.db2.hikari.validationTimeout=5000
spring.datasource.db2.hikari.connectionTestQuery=SELECT 1
spring.datasource.db2.hikari.readOnly=false
spring.datasource.db2.hikari.isolateInternalQueries=false
spring.datasource.db2.hikari.registerMbeans=false
spring.datasource.db2.hikari.allowPoolSuspension=false
#clickHouse-1
spring.datasource.db3.hikari.jdbcUrl=jdbc:clickhouse://
spring.datasource.db3.hikari.username=root
spring.datasource.db3.hikari.password=root
spring.datasource.db3.hikari.driverClassName=ru.yandex.clickhouse.ClickHouseDriver
spring.datasource.db3.hikari.minimumIdle=5
spring.datasource.db3.hikari.maximumPoolSize=10
spring.datasource.db3.hikari.autoCommit=true
spring.datasource.db3.hikari.idleTimeout=30000
spring.datasource.db3.hikari.poolName=HikariCP4
spring.datasource.db3.hikari.maxLifetime=1800000
spring.datasource.db3.hikari.connectionTimeout=30000
spring.datasource.db3.hikari.validationTimeout=5000
spring.datasource.db3.hikari.connectionTestQuery=SELECT 1
spring.datasource.db3.hikari.readOnly=false
spring.datasource.db3.hikari.isolateInternalQueries=false
spring.datasource.db3.hikari.registerMbeans=false
spring.datasource.db3.hikari.allowPoolSuspension=false
4.代码中应用 由于数据源动态切换是使用Aspect+注解完成的,所以调用时需要将Bean交给Spring的IOC容器管理。只有这样Spring才能通过AOP加强,触发我们的切换逻辑。
Controller:
@Resource
private StudentService studentService;
@GetMapping("/fcn")
public List<String> findCHNames() {
return studentService.findClickHouseColumnName();
}
Service:
@Resource
private JdbcTemplate jdbcTemplate;
@TargetDataSource("db3")
public List<String> findClickHouseColumnName() {
String sql = "show tables";
List<String> strings = jdbcTemplate.queryForList(sql, String.class);
return strings;
}
4. 插件的使用实例?
简介
EasyConnectionPoolDemo是SpringBoot动态配置多数据源连接池spring-boot-easy-connection-pool项目的实例。
EasyConnectionPoolDemo项目中分别演示了使用常见Jpa、JdbcTemplate、MyBatis作为持久层框架的项目案例。
按理说spring-boot-easy-connection-pool处理的是DataSource,对各种持久层框架而言是透明的,使用场景也是很类似,比较简单。但是为了更加具体详细的说明演示 ,就做了这个项目。此项目中分别使用了两个数据、两个表进行演示;理论上而言:如果项目中添加了支持JDBC协议驱动的数据源配置,则可实现数据源动态切换逻辑。 该连接池项目还有一个特点:在启动阶段会打印详细的连接信息以及连接表信息,如下图所示:
V1.0.1 - 更新说明
新增从默认数据库中获取数据源连接信息,使用说明。
1. 实例地址
https://github.com/xieyucan/easy-connection-pool-demo/blob/master/easy-jdbctemplate/src/main/java/com/xieahui/easy/jdbctemplate/service/MyDb3Service.java
准备
该项目演示使用的是MySql数据库,所以运行项目的前提是要有MySql数据库。新建数据库及表
- easy_pool_demo - student
- db1 - user
easy_pool_demo作为了默认数据库,建表语句在项目根目录的script中。
MyBatis框架中的用法
MyBatis是一款非常优秀的持久层框架,尤其是在互联网企业中使用的人群超级多。
演示项目 easy-mybatis
演示用例 easy-mybatis-MyBatisApplicationTest
JPA中的用法
JPA是SpringBoot默认集成的持久层框架,如果使用过Hibernate,那么对JPA则很快上手。使用JPA开发CRUD,可以说是非常的迅速、顺手。 在管理项目中做功能开发没有比JPA开发效率更高的了,如果你觉得使用起来不是很方便,那一定是没有找到正确的用法。这个项目中只是为了演示spring-boot-easy-connection-pool项目 如何支持JPA的开发,并没有对JPA技术进行详细介绍。
演示项目 easy-jpa
演示用例 easy-jpa-JpaApplicationTest
JdbcTemplate中的用法
JdbcTemplate很早之前就在SpringJdbc中出现了。由于它只对原生数据连接操作使用模板模式进行简单包装(如果没有任何封装,那么使用起来是非常蹩脚的),所以更接近原生操作;这也注定了它比其他几个框架更加灵活且学习成本更低。我们则更容易根据自己的业务场景、使用习惯封装出独特的持久层方案。
演示项目 easy-jdbctemplate
演示用例 easy-jpa-JdbcTemplateApplicationTest
以上,如有问题可以邮件联系我。祝好!
5. 插件的源码?
插件源码在github上,代码量很少并且有详细的注释。如果对您有所帮助,辛苦您帮我点个start。感谢!