SpringBoot2.x 集成ShardingSphere+S
引言
随着业务和数据量的增加,应用采用微服务部署日益增多,但是绝大多数微服务架构应用也还是采用的单数据库模式,即便是大多数读写分离,本质也还是单数据库,随着业务量和数据量增多,数据库读写效率急剧下降,此时就需要对数据库进行维度拆分,如水平拆分(分表)、垂直拆分(分库)
本文讨论的情况为水平拆分与垂直拆分,以及遇到的各种集成问题
版本
ShardingSphere:4.0.0-RC2-SNAPSHOT
Seata:0.5.1
Mybatis-Plus:2.3.1
DruidDataSource:1.1.10
ShardingSphere目前对应Dev未发布版本
Seata版本为ShardingSphere目前适配版本,在Seata 大于0.5.1之后的版本由于消息协议重构,ShardingSphere还未进行高版本适配
规则
提前将项目对应sql目录文件导入数据库,undo_log表为Seata用于回滚/提交的表
其余t_order_0、t_order_1、t_order_item_0、t_order_item_1为ShardingSphere对应的分表,注意:一定要提前创建好分库分表,因为ShardingSphere内部是需要提前进行分库分表扫描并加入ShardingSphere对应的DataSourceMap
简介
Seata AT 事务模型包含 TM(事务管理器),RM(资源管理器),TC(事务协调器)。
TC为seata-server,可以理解为单独部署的服务器,TM/RM 通过RPC与TC进行交互

ShardingSphere 分布式事务

ShardingSphere SPI供用户扩展XA强一致性事务或者是Base柔性事务,而Seata AT作为一种Base柔性事务的一种实现,本文着重分析和使用Seata
整合SeataAT分析过程

从ShardingSphere 官方图像显示,Seata与ShardingSphere 框架都是对DataSource 进行封装和处理,所以要将Seata事务融入到ShardingSphere 框架中使用,就需要将Seata框架中DataSourceProxy包装给ShardingSphere 框架的ShardingTransactionManager接口。不好理解?那咱们通过代码分析

从上图可知SeataATShardingTransactionManager实现了ShardingTransactionManager,这是一个SPI扩展接口,将SeataAT事务融入到ShardingSphere 框架中
通过查看initSeataRPCClient接口可知

对Seata的TM、RM进行初始化,以上简要概述了ShardingSphere 使用SeataAT事务的流程,下面结合Springboot来具体分析集成各个框架的流程
分析1:ShardingSphere 创建DataSource

在sharding-jdbc-spring-boot-starter工程中SpringBootConfiguration会自动装配参数

通过代码分析,启动之后通过遍历配置的names字段创建DataSource,其中return DataSourceUtil.getDataSource(dataSourceProps.get("type").toString(), dataSourceProps);通过用户配置的连接池类型就行初始化,因为ShardingSphere默认使用的HikariDataSource,而项目需要使用DruidDataSource,所以继续分析创建DataSource的过程

通过代码得知,ShardingSphere框架通过反射,将type字段同级的参数一并传入进行反射调用,所以我们就需要将DruidDataSource所需参数放置到type字段同级
这样就完成了使用DruidDataSource?答案是NO,细心的同学可以在启动日志中发现输出了Init DruidDataSource,通过继续分析发现

DruidDataSource自动创建了DataSource,我们的期望是DataSource由ShardingSphere进行创建,所以我们需要在@SpringBootApplication中排除DruidDataSourceAutoConfigure,至此ShardingSphere创建DataSource的过程完成
分析2:ShardingSphere使用SeataAT事务
通过上诉分析我们已经得知ShardingSphere已经创建好了各个DataSource并将其放入dataSourceMap集合中,通过Seata官网可知,需要使用Seata事务,需要使用Seata提供的DataSourceProxy类,继续通过源码分析

ShardingSphere内部通过SPI扩展,将ShardingTransactionManager接口暴露,在ShardingSphere创建完DataSource之后,紧接着通过扩展ShardingTransactionManager接口,将dataSourceMap集合中的各个DataSource代理给DataSourceProxy,至此SeataAT已经融入ShardingSphere,但是现在使用Seata的@GlobalTransactional是无效的,下文会继续分析
分析3:集成Mybatis-Plus/Mybatis
Mybatis-Plus作为Mybatis的一种增强,引入Mybatis-Plus之后并配置参数

一切看起来是那么的轻松,启动项目...不出意外将出现以下信息

为什么,为什么、为什么要这样对我.我太难了...
话不多说,继续分析原因,因为我们使用Mybatis-Plus,默认会引入Mybatis依赖库,然后DataSourceAutoConfiguration会自动加载,DataSourceAutoConfiguration会查找spring->datasource->url字段,因为我们用的ShardingSphere,并未配置这样的参数,知道原因了那就继续在@SpringBootApplication中排除DataSourceAutoConfiguration
继续启动项目......不出意外出现以下信息

错误提示没有发现sqlSessionFactory,因为我们使用Mybatis-Plus,正常情况下因由Mybatis-Plus进行sqlSessionFactory的创建,继续查看Mybatis-Plus源码


打个断点调试一波,发现确实没有进入,这又是为什么?查看MybatisPlusAutoConfiguration上面的注解发现,因为我们已经排除了DataSourceAutoConfiguration了,知道原因了,就是这个MybatisPlusAutoConfiguration没生效,怎么办?
自己重写一份MybatisPlusAutoConfiguration到项目里面吧,在SpringBootApplication中直接排除MybatisPlusAutoConfiguration

至此Mybatis-Plus集成完毕
分析4:Seata 注解@GlobalTransactional
在上文中说道ShardingSphere使用SeataAT事务,但是官方例子是jdbc直连,不符合Springboot集成特点,所以本段落主要分析和如何使用@GlobalTransactional注解
如果我们使用Seata官方例子不难发现,我们直接使用@GlobalTransactional注解是很方便的,也无需关心@GlobalTransactional 内部是如何实现的,当我们直接在业务Service上面使用@GlobalTransactional注解,会发现这个注解是无效的。这又是为什么?查看下源码


GlobalTransactionScanner实现AbstractAutoProxyCreator,然后根据wrapIfNecessary判断具体的Bean实体是否需要进行Aop包装/代理/增强,包装的条件为是否存在GlobalTransactional注解

回过头我们发现ShardingSphere封住的SeataATShardingTransactionManager类只是初始化了TMClient、RMClient。并没有对Seata的@GlobalTransactional注解进行处理
知道流程之后我们不难发现@GlobalTransactional注解的具体拦截实现类是GlobalTransactionalInterceptor。查看源码

发现实现Aop的MethodInterceptor
既然已经分析到了这里,那么方法自然而然就有了
方案1:重写GlobalTransactionScanner类,然后通过@Bean注入,把里面的TMClient、RMClient这些剔除,因为TMClient、RMClient已经在ShardingSphere的SeataATShardingTransactionManager类里面进行初始化了,此方案为最佳推荐方案,因为GlobalTransactionScanner内部做了代理判断
方案2:直接通过AOP进行处理,简单粗暴,但是如果是线上不推荐该方案,因为没有GlobalTransactionScanner处理的全面

至此Springboot集成ShardingSphere+Seata+Mybatis-Plus+DruidDataSource完毕,若有错误地方欢迎指出,后续文章将继续分析和使用Nacos、Dubbo