|
| 1 | +## ApiBoot DataSource Switch |
| 2 | + |
| 3 | +顾名思义,`DataSource Switch`是用于数据源选择切换的框架,这是一款基于`Spring AOP`切面指定注解实现的,通过简单的数据源注解配置就可以完成访问时的自动切换,`DataSource Switch`切换过程中是线程安全的。 |
| 4 | + |
| 5 | +### 添加依赖 |
| 6 | + |
| 7 | +使用`DataSource Switch`很简单,在`pom.xml`配置文件内添加如下依赖: |
| 8 | + |
| 9 | +```xml |
| 10 | +<!--ApiBoot DataSource Switch--> |
| 11 | +<dependency> |
| 12 | + <groupId>org.minbox.framework</groupId> |
| 13 | + <artifactId>api-boot-starter-datasource-switch</artifactId> |
| 14 | +</dependency> |
| 15 | +``` |
| 16 | +`ApiBoot`所提供的依赖都不需要添加版本号,具体查看[ApiBoot版本依赖](https://github.com/hengboy/api-boot/blob/1.x/README.md#%E6%B7%BB%E5%8A%A0%E7%89%88%E6%9C%AC%E4%BE%9D%E8%B5%96) |
| 17 | + |
| 18 | +### 集成数据源实现 |
| 19 | + |
| 20 | +目前`ApiBoot DataSource Switch`集成了`Druid`、`HikariCP`两种数据源实现依赖,在使用方面也有一定的差异,因为每一个数据源的内置参数不一致。 |
| 21 | + |
| 22 | +- `Druid`:参数配置前缀为`api.boot.datasource.druid` |
| 23 | +- `HikariCP`:参数配置前缀为`api.boot.datasource.hikari` |
| 24 | + |
| 25 | +**具体使用请查看下面功能配置介绍。** |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +### 配置参数 |
| 30 | + |
| 31 | +| 参数名 | 参数默认值 | 是否必填 | 参数描述 | |
| 32 | +| --------------------------------------------------------- | ------------------------ | -------- | ------------------------------------------------------------ | |
| 33 | +| `api.boot.datasource.primary` | master | 否 | 主数据源名称 | |
| 34 | +| `api.boot.datasource.druid.{poolName}.url` | 无 | 是 | 数据库连接字符串 | |
| 35 | +| `api.boot.datasource.druid.{poolName}.username` | 无 | 是 | 用户名 | |
| 36 | +| `api.boot.datasource.druid.{poolName}.password` | 无 | 是 | 密码 | |
| 37 | +| `api.boot.datasource.druid.{poolName}.driver-class-name` | com.mysql.cj.jdbc.Driver | 否 | 驱动类型 | |
| 38 | +| `api.boot.datasource.druid.{poolName}.filters` | stat,wall,slf4j | 否 | Druid过滤 | |
| 39 | +| `api.boot.datasource.druid.{poolName}.max-active` | 20 | 否 | 最大连接数 | |
| 40 | +| `api.boot.datasource.druid.{poolName}.initial-size` | 1 | 否 | 初始化连接数 | |
| 41 | +| `api.boot.datasource.druid.{poolName}.max-wait` | 60000 | 否 | 最大等待市场,单位:毫秒 | |
| 42 | +| `api.boot.datasource.druid.{poolName}.validation-query` | select 1 from dual | 否 | 检查sql | |
| 43 | +| `api.boot.datasource.druid.{poolName}.test-while-idle` | true | 否 | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 | |
| 44 | +| `api.boot.datasource.druid.{poolName}.test-on-borrow` | false | 否 | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 | |
| 45 | +| `api.boot.datasource.druid.{poolName}.test-on-return` | false | 否 | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 | |
| 46 | +| `api.boot.datasource.hikari.{poolName}.url` | 无 | 是 | 数据库连接字符串 | |
| 47 | +| `api.boot.datasource.hikari.{poolName}.username` | 无 | 是 | 用户名 | |
| 48 | +| `api.boot.datasource.hikari.{poolName}.password` | 无 | 是 | 密码 | |
| 49 | +| `api.boot.datasource.hikari.{poolName}.driver-class-name` | com.mysql.cj.jdbc.Driver | 否 | 数据库驱动类全限定名 | |
| 50 | +| `api.boot.datasource.hikari.{poolName}.property` | 无 | 否 | HikariCP属性配置 | |
| 51 | + |
| 52 | +`HikariCP`数据源是`SpringBoot2.x`自带的,配置参数请访问[HikariCP](https://github.com/brettwooldridge/HikariCP)。 |
| 53 | + |
| 54 | +### 单主配置 |
| 55 | + |
| 56 | +`ApiBoot DataSource Switch`支持单主数据源的配置,`application.yml`配置文件如下所示: |
| 57 | + |
| 58 | +```yaml |
| 59 | +api: |
| 60 | + boot: |
| 61 | + datasource: |
| 62 | + # 配置使用hikari数据源 |
| 63 | + hikari: |
| 64 | + # master datasource config |
| 65 | + master: |
| 66 | + url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai |
| 67 | + username: root |
| 68 | + password: 123456 |
| 69 | +``` |
| 70 | +
|
| 71 | +### 修改主数据源名称 |
| 72 | +
|
| 73 | +`master`为默认的主数据源的`poolName`,这里可以进行修改为其他值,不过需要对应修改`primary`参数,如下所示: |
| 74 | + |
| 75 | +```yaml |
| 76 | +api: |
| 77 | + boot: |
| 78 | + datasource: |
| 79 | + # 主数据源,默认值为master |
| 80 | + primary: main |
| 81 | + # 配置使用hikari数据源 |
| 82 | + hikari: |
| 83 | + # main datasource config |
| 84 | + main: |
| 85 | + url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai |
| 86 | + username: root |
| 87 | + password: 123456 |
| 88 | +``` |
| 89 | + |
| 90 | +在上面配置主数据源的`poolName`修改为`main`。 |
| 91 | + |
| 92 | +### 主从配置 |
| 93 | + |
| 94 | +如果你的项目内存在`单主单从`、`一主多从`的配置方式,如下所示: |
| 95 | + |
| 96 | +```yaml |
| 97 | +api: |
| 98 | + boot: |
| 99 | + datasource: |
| 100 | + # 配置使用hikari数据源 |
| 101 | + hikari: |
| 102 | + # master datasource config |
| 103 | + master: |
| 104 | + url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai |
| 105 | + username: root |
| 106 | + password: 123456 |
| 107 | + # 默认值为【com.mysql.cj.jdbc.Driver】 |
| 108 | + #driver-class-name: com.mysql.cj.jdbc.Driver |
| 109 | + # slave 1 datasource config |
| 110 | + slave_1: |
| 111 | + url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf8&serverTimezone=Asia/Shanghai |
| 112 | + username: root |
| 113 | + password: 123456 |
| 114 | + # slave 2 datasource config |
| 115 | + slave_2: |
| 116 | + url: jdbc:mysql://localhost:3306/resources?characterEncoding=utf8&serverTimezone=Asia/Shanghai |
| 117 | + username: root |
| 118 | + password: 123456 |
| 119 | +``` |
| 120 | + |
| 121 | +在上面是`一主多从`的配置方式,分别是`master`、`slave_1`、`slave_2`。 |
| 122 | + |
| 123 | +### 多类型数据库配置 |
| 124 | + |
| 125 | +`ApiBoot DataSource Switch`提供了一个项目内连接多个不同类型的数据库,如:`MySQL`、`Oracle`...等,如下所示: |
| 126 | + |
| 127 | +```yaml |
| 128 | +api: |
| 129 | + boot: |
| 130 | + # 主数据源,默认值为master |
| 131 | + primary: mysql |
| 132 | + hikari: |
| 133 | + mysql: |
| 134 | + url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai |
| 135 | + username: root |
| 136 | + password: 123456 |
| 137 | + oracle: |
| 138 | + url: jdbc:oracle:thin:@172.16.10.25:1521:torcl |
| 139 | + username: root |
| 140 | + password: 123456 |
| 141 | + driver-class-name: oracle.jdbc.driver.OracleDriver |
| 142 | +``` |
| 143 | + |
| 144 | +在上面配置中,`master`主数据源使用的`MySQL`驱动连接`MySQL`数据库,而`slave`从数据源则是使用的`Oracle`驱动连接的`Oracle`数据库。 |
| 145 | + |
| 146 | +### 动态创建数据源 |
| 147 | + |
| 148 | +`ApiBoot DataSource Switch`内部提供了动态创建数据源的方法,可以通过注入`ApiBootDataSourceFactoryBean`来进行添加,如下所示: |
| 149 | + |
| 150 | +```java |
| 151 | +@Autowired |
| 152 | +private ApiBootDataSourceFactoryBean factoryBean; |
| 153 | +
|
| 154 | +public void createNewDataSource() throws Exception { |
| 155 | + // 创建Hikari数据源 |
| 156 | + // 如果创建Druid数据源,使用DataSourceDruidConfig |
| 157 | + DataSourceHikariConfig config = new DataSourceHikariConfig(); |
| 158 | + // 数据库连接:必填 |
| 159 | + config.setUrl("jdbc:mysql://localhost:3306/resources"); |
| 160 | + // 用户名:必填 |
| 161 | + config.setUsername("root"); |
| 162 | + // 密码:必填 |
| 163 | + config.setPassword("123456"); |
| 164 | + // 数据源名称:必填(用于@DataSourceSwitch注解value值使用) |
| 165 | + config.setPoolName("dynamic"); |
| 166 | +
|
| 167 | + // 创建数据源 |
| 168 | + DataSource dataSource = factoryBean.newDataSource(config); |
| 169 | + Connection connection = dataSource.getConnection(); |
| 170 | + System.out.println(connection.getCatalog()); |
| 171 | + connection.close(); |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +### 自动切换 |
| 176 | + |
| 177 | +`ApiBoot DataSource Switch`的数据源自动切换主要归功于`Spring`的`AOP`,通过切面`@DataSourceSwitch`注解,获取注解配置的`value`值进行设置当前线程所用的数据源名称,从而通过`AbstractRoutingDataSource`进行数据源的路由切换。 |
| 178 | + |
| 179 | +我们沿用上面**一主多从**的配置进行代码演示,配置文件`application.yml`参考上面配置,代码示例如下: |
| 180 | + |
| 181 | +#### 从数据源示例类 |
| 182 | + |
| 183 | +```java |
| 184 | +@Service |
| 185 | +@DataSourceSwitch("slave") |
| 186 | +public class SlaveDataSourceSampleService { |
| 187 | + /** |
| 188 | + * DataSource Instance |
| 189 | + */ |
| 190 | + @Autowired |
| 191 | + private DataSource dataSource; |
| 192 | +
|
| 193 | + /** |
| 194 | + * 演示输出数据源的catalog |
| 195 | + * |
| 196 | + * @throws Exception |
| 197 | + */ |
| 198 | + public void print() throws Exception { |
| 199 | + // 获取链接 |
| 200 | + Connection connection = dataSource.getConnection(); |
| 201 | + // 输出catalog |
| 202 | + System.out.println(this.getClass().getSimpleName() + " ->" + connection.getCatalog()); |
| 203 | + // 关闭链接 |
| 204 | + connection.close(); |
| 205 | + } |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +#### 主数据源示例类 |
| 210 | + |
| 211 | +```java |
| 212 | +@Service |
| 213 | +@DataSourceSwitch("master") |
| 214 | +public class MasterDataSourceSampleService { |
| 215 | + /** |
| 216 | + * DataSource Instance |
| 217 | + */ |
| 218 | + @Autowired |
| 219 | + private DataSource dataSource; |
| 220 | + /** |
| 221 | + * Slave Sample Service |
| 222 | + */ |
| 223 | + @Autowired |
| 224 | + private SlaveDataSourceSampleService slaveDataSourceSampleService; |
| 225 | +
|
| 226 | + /** |
| 227 | + * 演示输出主数据源catalog |
| 228 | + * 调用从数据源类演示输出catalog |
| 229 | + * |
| 230 | + * @throws Exception |
| 231 | + */ |
| 232 | + public void print() throws Exception { |
| 233 | + Connection connection = dataSource.getConnection(); |
| 234 | + System.out.println(this.getClass().getSimpleName() + " ->" + connection.getCatalog()); |
| 235 | + connection.close(); |
| 236 | + slaveDataSourceSampleService.print(); |
| 237 | + } |
| 238 | +} |
| 239 | +``` |
| 240 | + |
| 241 | +- 在`主数据源`的示例类内,我们通过`@DataSourceSwitch("master")`注解的`value`进行定位连接`master`数据源数据库。 |
| 242 | +- 同样在`从数据库`的示例类内,我们也可以通过`@DataSourceSwitch("slave")`注解的`value`进行定位连接`slave`数据源数据库。 |
| 243 | + |
| 244 | +#### 单元测试示例 |
| 245 | + |
| 246 | +在上面的测试示例中,我们使用交叉的方式进行验证`数据源路由`是否可以正确的进行切换,可以编写一个单元测试进行验证结果,如下所示: |
| 247 | + |
| 248 | +```java |
| 249 | +@Autowired |
| 250 | +private MasterDataSourceSampleService masterDataSourceSampleService; |
| 251 | +@Test |
| 252 | +public void contextLoads() throws Exception { |
| 253 | + masterDataSourceSampleService.print(); |
| 254 | +} |
| 255 | +``` |
| 256 | + |
| 257 | +运行上面测试方法,结果如下所示: |
| 258 | + |
| 259 | +```sh |
| 260 | +MasterDataSourceSampleService ->test |
| 261 | +2019-04-04 10:20:45.407 INFO 7295 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting... |
| 262 | +2019-04-04 10:20:45.411 INFO 7295 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed. |
| 263 | +SlaveDataSourceSampleService ->oauth2 |
| 264 | +``` |
| 265 | + |
| 266 | +单次执行数据源切换没有任何的问题,`master`数据源获取`catalog`输出后,调用`slave`示例类进行输出`catalog`。 |
| 267 | + |
| 268 | +> `ApiBoot DataSource Switch`会在项目启动时首先初始化`master`节点`DataSource`实例,其他实例会在第一次调用时进行初始化。 |
| 269 | + |
| 270 | +### 压力性能测试 |
| 271 | + |
| 272 | +单次执行单线程操作没有问题,不代表多线程下不会出现问题,在开头说到过`ApiBoot DataSource Switch`是线程安全的,所以接下来我们来验证这一点,我们需要添加压力测试的依赖,如下所示: |
| 273 | + |
| 274 | +```xml |
| 275 | +<dependency> |
| 276 | + <groupId>org.databene</groupId> |
| 277 | + <artifactId>contiperf</artifactId> |
| 278 | + <version>2.3.4</version> |
| 279 | + <scope>test</scope> |
| 280 | +</dependency> |
| 281 | +``` |
| 282 | + |
| 283 | +接下来把上面的单元测试代码改造下,如下所示: |
| 284 | + |
| 285 | +```java |
| 286 | +// 初始化压力性能测试对象 |
| 287 | +@Rule |
| 288 | +public ContiPerfRule i = new ContiPerfRule(); |
| 289 | +
|
| 290 | +@Autowired |
| 291 | +private MasterDataSourceSampleService masterDataSourceSampleService; |
| 292 | +/** |
| 293 | +* 开启500个线程执行10000次 |
| 294 | +*/ |
| 295 | +@Test |
| 296 | +@PerfTest(invocations = 10000, threads = 500) |
| 297 | +public void contextLoads() throws Exception { |
| 298 | + masterDataSourceSampleService.print(); |
| 299 | +} |
| 300 | +``` |
| 301 | + |
| 302 | +> 测试环境: |
| 303 | +> |
| 304 | +> 硬件:i7、16G、256SSD |
| 305 | +> |
| 306 | +> 系统:OS X |
| 307 | +> |
| 308 | +> 整个过程大约是10秒左右,`ApiBoot DataSource Switch`并没有发生出现切换错乱的情况。 |
| 309 | + |
| 310 | +### 注意事项 |
| 311 | + |
| 312 | +1. 在使用`ApiBoot DataSource Switch`时需要添加对应数据库的依赖 |
| 313 | +2. 如果使用`Druid`连接池,不要配置使用`druid-starter`的依赖,请使用`druid`依赖。 |
| 314 | +3. 配置`poolName`时不要添加特殊字符、中文、中横线等。 |
0 commit comments