All Projects → drtrang → Dynamic Data Source Demo

drtrang / Dynamic Data Source Demo

Licence: apache-2.0
基于事务的读写分离

Programming Languages

java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to Dynamic Data Source Demo

Spring Boot Angular5
This repository has a sample code base for spring boot and angular 5 integration.
Stars: ✭ 49 (+13.95%)
Mutual labels:  database, spring-boot, spring
Speedment
Speedment is a Stream ORM Java Toolkit and Runtime
Stars: ✭ 1,978 (+4500%)
Mutual labels:  database, spring-boot, spring
Java Interview
At the beginning, it was the repository with questions from Java interviews. Currently, it's more like knowledge base with useful links.
Stars: ✭ 114 (+165.12%)
Mutual labels:  transaction, spring-boot, spring
Spring Postgresql Demo
Spring Boot 2.0 application, backed by PostgreSQL, and designed for deployment to Pivotal Cloud Foundry (PCF)
Stars: ✭ 23 (-46.51%)
Mutual labels:  spring-boot, spring
Journaldev
JournalDev Projects
Stars: ✭ 897 (+1986.05%)
Mutual labels:  spring-boot, spring
Mart Holiday Alarm
🛒 마트쉬는날 🏖
Stars: ✭ 22 (-48.84%)
Mutual labels:  spring-boot, spring
Go Spring
基于 IoC 的 Go 后端一站式开发框架 🚀
Stars: ✭ 744 (+1630.23%)
Mutual labels:  spring-boot, spring
Spring Cloud Examples
Examples of microservice instrastructures
Stars: ✭ 11 (-74.42%)
Mutual labels:  spring-boot, spring
Micro Server
Microserver is a Java 8 native, zero configuration, standards based, battle hardened library to run Java Rest Microservices via a standard Java main class. Supporting pure Microservice or Micro-monolith styles.
Stars: ✭ 929 (+2060.47%)
Mutual labels:  spring-boot, spring
Spring Reactive Sample
Spring 5 Reactive playground
Stars: ✭ 867 (+1916.28%)
Mutual labels:  spring-boot, spring
Mica Example
mica 演示项目
Stars: ✭ 42 (-2.33%)
Mutual labels:  spring-boot, spring
Onetwo
一个基于spring和spring boot的快速开发框架……
Stars: ✭ 16 (-62.79%)
Mutual labels:  spring-boot, spring
Javaquarkbbs
基于Spring Boot实现的一个简易的Java社区
Stars: ✭ 755 (+1655.81%)
Mutual labels:  spring-boot, spring
Localstack Spring Boot
Spring Boot AutoConfiguration for LocalStack
Stars: ✭ 22 (-48.84%)
Mutual labels:  spring-boot, spring
Mica
Spring Cloud 微服务开发核心工具集。工具类、验证码、http、redis、ip2region、xss 等,开箱即用。 🔝 🔝 记得右上角点个star 关注更新!
Stars: ✭ 749 (+1641.86%)
Mutual labels:  spring-boot, spring
Springbootunity
rabbitmq、redis、scheduled、socket、mongodb、Swagger2、spring data jpa、Thymeleaf、freemarker etc. (muti module spring boot project) (with spring boot framework,different bussiness scence with different technology。)
Stars: ✭ 845 (+1865.12%)
Mutual labels:  spring-boot, spring
Jbone
jbone基于Spring Cloud框架开发,旨在为中小企业提供稳定的微服务解决方案,为开发人员提供基础开发骨架,jbone包含微服务中所有常用组件,例如注册中心、服务管理、服务监控、JVM监控、内存分析、调用链跟踪、API网关等等。业务功能包括系统权限的统一管理、单点登录、CMS、电商平台、工作流平台、支付平台等等。
Stars: ✭ 961 (+2134.88%)
Mutual labels:  spring-boot, spring
Devops Service
DevOps Service is the core service of Choerodon. It integrated several open source tools to automate the DevOps process of planning, coding, building, testing, and deployment, operation, monitoring.
Stars: ✭ 36 (-16.28%)
Mutual labels:  spring-boot, spring
Spring Thrift Api Gateway
Gateway for Apache Thrift requests processing that is built on Spring Cloud stack
Stars: ✭ 38 (-11.63%)
Mutual labels:  spring-boot, spring
Spring Boot Websocket Chat Demo
Spring Boot WebSocket Chat Demo with SockJS fallback and STOMP protocol
Stars: ✭ 726 (+1588.37%)
Mutual labels:  spring-boot, spring

应用层读写分离的改进

背景

数据库读写分离是构建高性能 Web 架构不可缺少的一环,其主要提升在于:

  1. 主从职责单一,主写从读,可以极大程度地缓解 X 锁和 S 锁的竞争,并且可以进行针对性调优
  2. 请求分流,减少主库压力
  3. 当读成为 DB 瓶颈时,很容易进行水平拓展
  4. 增加冗余,实现高可用,出现故障后可快速恢复,仅丢失少量数据或不丢失数据

实现方式

读写分离首先需要 DB 实例的支持,配置主库、从库以及主从同步策略,此步骤一般交给 OP 即可。实例搭建完毕后,我们就可以开发相应模块,以实现真正的读写分离。

业界的实现方式一般分为两种:DB 中间件应用层读写分离,二者均有各自的优缺点,详情见下表:

DB 中间件

优点:对于应用透明;不限语言
缺点:专人部署 + 维护;保证 HA、LB;一般只支持 MySQL

应用层读写分离

优点:开发简单,团队内部可以自行消化;基于 JDBC 驱动或框架,理论支持任意类型的 DB
缺点:通用性差,各应用需要自己实现;手动指定数据源

用不用 DB 中间件需要考虑实际情况,如数据体量和有没有人维护等等,本文讲的是应用层读写分离。

当前方案

通过自定义注解 @DataSourceRoute,手动声明当前方法操作的数据源,再通过切面拦截该切入点,路由到目标数据源。

因为实际中还要与事务结合,所以又写了一套基于事务路由主从数据源的切面,使用起来较为繁琐。

//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. 定义动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(Object defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
    }
    
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.get();
    }
}

public final class DynamicDataSourceHolder {
    public static final String MASTER_DATA_SOURCE = "Master";
    public static final String SLAVE_DATA_SOURCE = "Slave";

    private static final ThreadLocal<String> CONTAINER = ThreadLocal.withInitial(
            () -> DynamicDataSourceHolder.SLAVE_DATA_SOURCE
    );

    public static void routeMaster() {
        CONTAINER.set(MASTER_DATA_SOURCE);
    }
    public static void routeSlave() {
        CONTAINER.set(SLAVE_DATA_SOURCE);
    }
    public static String get() {
        return CONTAINER.get();
    }
    public static void clear() {
        CONTAINER.remove();
    }
}
  1. 声明动态数据源
@Configuration
public class SpringDataSourceConfig {

    @Bean(initMethod = "init", destroyMethod = "close")
    public DruidDataSource masterDataSource() {
        return new DruidDataSource();
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    public DruidDataSource slaveDataSource() {
        return new DruidDataSource();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DruidDataSource masterDataSource, DruidDataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = ImmutableMap.builder()
                .put(MASTER_DATA_SOURCE, masterDataSource)
                .put(SLAVE_DATA_SOURCE, slaveDataSource)
                .build();
        return new DynamicDataSource(slaveDataSource, targetDataSources);
    }
}
  1. 重写 Spring 默认的事务管理器
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {

    public DynamicDataSourceTransactionManager(DataSource dataSource) {
        super(dataSource);
    }
    
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        if (!definition.isReadOnly()) {
            DynamicDataSourceHolder.routeMaster();
        }
        super.doBegin(transaction, definition);
    }

    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        DynamicDataSourceHolder.clear();
        super.doCleanupAfterCompletion(transaction);
    }
}
  1. 声明自定义的事务管理器
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class SpringDaoConfig implements TransactionManagementConfigurer {
    @Autowired
    private DynamicDataSource dataSource;

    @Override
    @Bean
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DynamicDataSourceTransactionManager(dataSource);
    }
}

代码贴完了,让我们来看看能不能满足之前的 4 种场景呢?

其中 1、4 的区别仅仅是加不加事务,比较简单,那么待解决的还有 2 和 3。第 2 种因为没有事务,需要我们手动指定数据源,第 3 种则使用 Spring 提供的只读事务即可实现。

序号 事务 数据源 操作 实现方式
1 从库 默认
2 主库 手动指定 DynamicDataSourceHolder.routeMaster()
3 从库 @Transactional(readOnly = true)
4 主库 @Transactional

如此一来,之前的问题都已经解决。我们仅仅通过 Spring 自带的 @Transactional 注解即可指定数据源,对比之前简化不少。

硬广

由于篇幅原因,文章中没有展示具体的执行结果。完整代码已打包成 dynamic-data-source-demo项目,并上传到 Github,项目中提供完整的单元测试,详情大家可以 Clone 到本地自己执行一遍。

dynamic-data-source-demo 项目基于 Spring Boot,集成了 MyBatis、通用 Mapper、PageHelper、Druid、Copiers,可以作为简单的脚手架使用,欢迎大家 Star 或者 Fork 到自己的仓库。

如果有问题,可以在 Github 上提 Issue,或者 QQ 交流,以下是联系方式:
我的 Github 地址:https://github.com/drtrang
项目 Github 地址:https://github.com/drtrang/dynamic-data-source-demo
BeanCopier 工具:https://github.com/drtrang/Copiers
QQ:349096849

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].