背景:之前的项目做读写分离的时候用的 MybatisPlus的动态数据做的,很多地方使用的@DS直接指定的读库或者写库实现的业务;随着表数据量越来越大,现在打算把比较大的表进行水平拆分,准备使用 ShardingJDBC实现,但是发现两者配合起来并不是那么顺利,网上大部分文章都是直接把整个Sharding的数据源当成MybatisPlus的一个数据源,那么原本使用@DS指定的数据源就无法使用Sharding的分库等逻辑,所以我研究了一下源码,实现了这一逻辑,给后面有需要的朋友提供一个案例,避免浪费不必要的时间

一. 版本选择

目前ShardingJDBC主要有两个版本,一个是ShardingJDBC早期版本,一个是ShardingSphere项目中的ShardingSphere-JDBC

  • Sharding-JDBC:最初由当时的项目发起人在2016年发布。它最早作为一个轻量级的 JDBC 层解决方案,旨在解决数据库分片和读写分离的问题。

  • ShardingSphere:是由 Sharding-JDBC 项目发展而来的,并在2018年正式发布。Apache ShardingSphere 致力于构建更为完整的分布式数据库管理生态系统,包含了 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar等多个组件。

目前独立的ShardingJDBC已经停更,使用到的最多的版本是 4.1.1

<dependency>    <groupId>org.apache.shardingsphere</groupId>    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>    <version>4.1.1</version></dependency>

ShardingSphere项目目前一直处于更新迭代中,ShardingSphere-JDBC 是通过ShardingJDBC 更新迭代过来的,在原有代码的基础进行了一些优化和新功能加入,对于开发者而言,主要是参数的配置发生了一些调整。但是参数的作用和配置方式和以前一样;

这里我为了方便以后会使用到新特性,我直接使用的是 ShardingSphere-JDBC 5.2.1

官方帮助文档:https://www.bookstack.cn/read/shardingsphere-5.1.0-zh/ecf18b21ab3f559c.md

<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId><version>5.2.1</version></dependency>

二. 项目依赖

案例全部的 Maven依赖如下:

<dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>
        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>        </dependency>
        <dependency>            <groupId>com.zaxxer</groupId>            <artifactId>HikariCP</artifactId>            <version>3.4.5</version>        </dependency>
        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>mybatis-plus-boot-starter</artifactId>            <version>3.4.0</version>        </dependency>
        <!-- 读写分离 -->        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>            <version>3.3.2</version>        </dependency>
        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.33</version>        </dependency>
        <!--Shardingjdbc-->        <dependency>            <groupId>org.apache.shardingsphere</groupId>            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>            <version>5.2.1</version>            <exclusions>                <exclusion>                    <groupId>org.yaml</groupId>                    <artifactId>snakeyaml</artifactId>                </exclusion>            </exclusions>        </dependency>        <!-- 添加正确版本的 SnakeYAML  shardingsphere-jdbc里面的依赖版本有问题,会报错-->        <dependency>            <groupId>org.yaml</groupId>            <artifactId>snakeyaml</artifactId>            <version>1.33</version>        </dependency>
        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>

三. 参数配置

application.yml 配置

server:  port: 8080
mybatis-plus:  mapper-locations: classpath*:mybatis/*.xml  type-aliases-package: com.game.sharding.dto  configuration:    map-underscore-to-camel-case: false    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:  application:    name: sharding-jdbc-test  sharding-sphere:    datasource:      names: master,write,read,read2      master:        type: com.zaxxer.hikari.HikariDataSource        driver-class-name: com.mysql.cj.jdbc.Driver        jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai        username: root        password: 123456      write:        type: com.zaxxer.hikari.HikariDataSource        driver-class-name: com.mysql.cj.jdbc.Driver        jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai        username: root        password: 123456      read:        type: com.zaxxer.hikari.HikariDataSource        driver-class-name: com.mysql.cj.jdbc.Driver        jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev_read?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai        username: root        password: 123456      read2:        type: com.zaxxer.hikari.HikariDataSource        driver-class-name: com.mysql.cj.jdbc.Driver        jdbc-url: jdbc:mysql://127.0.0.1:3306/game_dev_read?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai        username: root        password: 123456    rules:      sharding:        tables:          team_msg:		  ## 这里的customer-ds是下面配置的读写分离的数据源名称            actual-data-nodes: customer-ds.team_msg_${0..1}            table-strategy:              standard:                sharding-column: id                sharding-algorithm-name: msg-id # 对应下面的sharding-algorithms        sharding-algorithms:          ## 注意这里名称(例如msg-id)不能用下划线,会加载不了下面的参数导致启动报错          msg-id:            type: INLINE            props:## 使用id取模算法              algorithm-expression: team_msg_${id % 2}	 ## 读写分离相关		        readwrite-splitting:        data-sources:          customer-ds:            load-balancer-name: customer-lb            static-strategy:              write-data-source-name: master              read-data-source-names: read,read2,write        load-balancers:            customer-lb:			    ## 使用自定义的复杂均衡算法                type: CUSTOM    props:      # 显示处理之后的真实sql      sql-show: true

四. 代码配置

最关键的配置就是需要把MybatisPlus的数据源注册为使用 shardingsphere-jdbc 的数据源,并且保证数据源的名称和原来MybatisPlus的数据源一致,shardingSphereDataSource里面其实有一个Map保存了application.yml中所有配置的数据源,这里主要是为了方便后续使用@DS做动态数据源切换,所以把同一个ShardingSphere的数据库注册为4个动态数据源,避免使用@DS找不到对应的数据源;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;import org.apache.commons.lang3.StringUtils;import org.apache.shardingsphere.driver.jdbc.adapter.AbstractDataSourceAdapter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.SpringBootConfiguration;import org.springframework.boot.autoconfigure.AutoConfigureBefore;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Lazy;import org.springframework.context.annotation.Primary;
import javax.annotation.Resource;import javax.sql.DataSource;import java.util.Arrays;import java.util.HashMap;import java.util.Map;
@Configuration@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})public class MyDataSourceConfiguration {
    /**     * mybatisplus 动态数据源配置项     */    @Autowired    private DynamicDataSourceProperties properties;
    /**     * shardingjdbc的数据源     */    @Lazy    @Resource(name = "shardingSphereDataSource")    private AbstractDataSourceAdapter shardingSphereDataSource;
    @Value("${spring.sharding-sphere.datasource.names}")    private String shardingDataSourceNames;
    /**     * 注册动态数据源  这里非常关键,因为我们需要用到@DS注解配置动态选择数据源,同上又要让选择的数据源使用shardingjdbc的数据源     * 所以,这里需要动态的把所有的数据源都注册为  shardingjdbc的数据源     */    @Bean    public DynamicDataSourceProvider dynamicDataSourceProvider() {        if (StringUtils.isBlank(shardingDataSourceNames)) {            throw new RuntimeException("配置 spring.sharding-sphere.datasource.names 不能为空");        }        String[] names = shardingDataSourceNames.split(",");        return new AbstractDataSourceProvider() {            @Override            public Map<String, DataSource> loadDataSources() {                Map<String, DataSource> dataSourceMap = new HashMap<>();                Arrays.stream(names).forEach(name -> dataSourceMap.put(name, shardingSphereDataSource));                return dataSourceMap;            }        };    }
    /**     * 将动态数据源设置为首选数据源     */    @Primary    @Bean    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();        dataSource.setPrimary(properties.getPrimary());        dataSource.setStrict(properties.getStrict());        dataSource.setStrategy(properties.getStrategy());        dataSource.setProvider(dynamicDataSourceProvider);        dataSource.setP6spy(properties.getP6spy());        dataSource.setSeata(properties.getSeata());        return dataSource;    }}

五. 自定义ShardingSphere中的复杂均衡算法

shardingsphere中的负载均衡需要实现ReadQueryLoadBalanceAlgorithm接口并在getType方法中返回自定义的算法名称,官方自带的又RoundRobinReadQueryLoadBalanceAlgorithm,RandomReadQueryLoadBalanceAlgorithm等,这里我们必须自定义算法才能兼容@DS注解实现自由切换数据源;

ShardingSphere使用的是SPI机制加载的,对应的加载源码部分如下:

image

所以如果我们要让自定义的ReadQueryLoadBalanceAlgorithm类生效,需要在项目中的 META-INF的services文件夹中创建org.apache.shardingsphere.readwritesplitting.spi.ReadQueryLoadBalanceAlgorithm 文件,并且把自定义的类填入该文件中

源码中的配置如下:
image

那么我们按照源码的配置直接在自己的项目中创建即可

image

最后自定义的CustomLoadBalanceAlgorithm 实现

public class CustomLoadBalanceAlgorithm implements ReadQueryLoadBalanceAlgorithm {    private Properties props;
    public CustomLoadBalanceAlgorithm() {
    }
    @Override    public void init(Properties props) {        this.props = props;    }
    /**     * 获取数据源     *     * @param name  数据源名称(ShardingJDBC使用的)     * @param writeDataSourceName 写数据源名称     * @param readDataSourceNames 所有配置的复杂均衡中读数据源名称     * @param context 事务上下文对象,可以获取context.isInTransaction() 判断是否需要事务,可通过这个来判断是否使用 写数据源     * @return java.lang.String     */    @Override    public String getDataSource(String name, String writeDataSourceName, List<String> readDataSourceNames, TransactionConnectionContext context) {        // 获取当前MybatisPlus指定的数据源        String dsKey = DynamicDataSourceContextHolder.peek();        if (StringUtils.isNotBlank(dsKey)) {            if (writeDataSourceName.equals(dsKey)) {                return dsKey;            }            if (readDataSourceNames.contains(dsKey)) {                return dsKey;            }            throw new RuntimeException("@DS 配置错误,当前数据源[" + dsKey + "]不在SharingJDBC数据源列表[" + readDataSourceNames + "]中");        }        return writeDataSourceName;    }
    @Override    public String getType() {        return "CUSTOM";    }
    @Override    public boolean isDefault() {        return true;    }
    @Override    @Generated    public Properties getProps() {        return this.props;    }}

那么此时你的ShardingSphere就已经完全适配之前MybatisPlus动态数据源了

image

六. 源码

Gitee: https://gitee.com/luowenjie98/sharing-sphere-mybatisplus-demo

本文转自:https://www.cnblogs.com/lwjQAQ/p/18330274

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐