# dynamic-data-source-demo
**Repository Path**: wangyangbto/dynamic-data-source-demo
## Basic Information
- **Project Name**: dynamic-data-source-demo
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2018-06-22
- **Last Updated**: 2020-12-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 应用层读写分离的改进
## 背景
数据库读写分离是构建高性能 Web 架构不可缺少的一环,其主要提升在于:
1. 主从职责单一,主写从读,可以极大程度地缓解 X 锁和 S 锁的竞争,并且可以进行针对性调优
2. 请求分流,减少主库压力
3. 当读成为 DB 瓶颈时,很容易进行水平拓展
4. 增加冗余,实现高可用,出现故障后可快速恢复,仅丢失少量数据或不丢失数据
## 实现方式
读写分离首先需要 DB 实例的支持,配置主库、从库以及主从同步策略,此步骤一般交给 OP 即可。实例搭建完毕后,我们就可以开发相应模块,以实现真正的读写分离。
业界的实现方式一般分为两种:**DB 中间件** 和 **应用层读写分离**,二者均有各自的优缺点,详情见下表:
### DB 中间件
> 优点:对于应用透明;不限语言
> 缺点:专人部署 + 维护;保证 HA、LB;一般只支持 MySQL
### 应用层读写分离
> 优点:开发简单,团队内部可以自行消化;基于 JDBC 驱动或框架,理论支持任意类型的 DB
> 缺点:通用性差,各应用需要自己实现;手动指定数据源
用不用 DB 中间件需要考虑实际情况,如数据体量和有没有人维护等等,本文讲的是应用层读写分离。
## 当前方案
通过自定义注解 `@DataSourceRoute`,手动声明当前方法操作的数据源,再通过切面拦截该切入点,路由到目标数据源。
因为实际中还要与事务结合,所以又写了一套基于事务路由主从数据源的切面,使用起来较为繁琐。
```java
//annotation
public @interface DataSourceRoute {
AccessType type() default AccessType.MASTER;
}
//aspect
public class DataSourceRouteAspect {
@Before("@annotation(DataSourceRoute)")
public void before(JoinPoint point) {
Method targetMethod = ((MethodSignature) point.getSignature()).getMethod();
DataSourceRoute annotation = targetMethod.getAnnotation(DataSourceRoute.class);
DynamicDataSourceHolder.route(annotation.type());
}
}
//dao
@DataSourceRoute(type=AccessType.SLAVE)
public Housedel findByPK(Long housedelCode) {
return mapper.findByPK(housedelCode);
}
```
## 改进方案
其实总结一下我们使用读写分离的场景会发现,主库一般负责写入(偶尔用来读),从库则全部用来读取。而为了保障数据的正确性,我们在写入操作时一般会加上事务(这也是我推荐的最佳实践),也就是说,大部分事务操作是在写入,大部分非事务操作则是在读取,由此可见读写分离和事务之间是有一定关联的。
既然思路是可行的,那我们不妨思考一下,实际使用中具体有哪些场景呢?
| 序号 | 事务 | 数据源 | 操作
| :-- | :-- | :-- | :-- |
| 1 | 无 | 从库 | 读
| 2 | 无 | 主库 | 读
| 3 | 有 | 从库 | 读
| 4 | 有 | 主库 | 写
第 1 种,无事务从库读取。典型的只读场景,我们的业务场景一般是读多写少,为了方便,可以作为默认选项。
第 2 种,无事务主库读取。主库中读取数据的情况还是比较少见的,一般是因为对数据的实时性要求较高,而 MySQL 的主从复制是异步的,中间会有短暂的时间差,为了保证数据的一致性,会直接从主库读取。
第 3 种,有事务从库读取。前面我们说道,事务一般加在写入操作上,但也有个别情况只读时也需要加入事务,比如在当前只读事务内,不希望其它事务更改数据,从而保证数据前后的一致性。
第 4 种,有事务主库写入。典型的写入场景,数据写入主库后,异步复制到从库。
## 落地
那么如何实现呢?阅读 Spring 的源码会发现,`DataSourceTransactionManager` 是 Spring 用来管理事务的类,我们只需要自定义一个事务管理器,在开启事务之前指定数据源即可。
有了之前的分析,我们可以得到以下规则:默认无事务时路由到从库,**有事务且非只读**时路由到主库。
1. 定义动态数据源
```java
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(Object defaultTargetDataSource, Map