作为一个后端开发,用过mycat的应该不在少数吧。说说作者使用mycat遇到的问题!
需要单独的服务,对于我们根本就只有一串连接地址的概念,有时候挂了都不知道(因为是运维部署的,给我们的只是跳板机连的数据库,并不是mycat),高并发出现读延迟(强制主库解决),但总的来说运维成本比较高。
今天出一个客户端分库分表的实战,其实了解以及很久了,一直没有付出行动!
那么今天就来了!
文章于2024-08-15更新,JDK21+Springboot3.2.3+ShardingSphere5.5.0,线上还有使用老版本,旧文章不做删除,放在最后 。

1.ShardingSphere介绍

官网直通车:Apache ShardingSphere 产品定位为 Database Plus,旨在构建异构数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。ShardingSphere 站在数据库的上层视角,关注他们之间的协作多于数据库自身。
ShardingSphere 已于 2020 年 4 月 16 日成为 Apache 软件基金会的顶级项目。
目前有三大组件:

  • ShardingSphere-JDBC 基于协议层面对JDBC接口的封装,是以jar包客户端的形式存在的
  • ShardingSphere-Proxy 独立的代理服务,类似mycat
  • ShardingSphere-SCALING (EXPERIMENTAL) 实现中

今天主要来分析java端的ShardingSphere-JDBC

2.ShardingSphere-JDBC 5.5.0

2.1 介绍

ShardingSphere-JDBC自身定义为轻量级Java框架,可在Java JDBC层提供额外的服务。客户端直接连接到数据库时,它以jar形式提供服务,并且不需要额外的部署。

2.2 项目详细

  • pom.xml
    本文基于JDK21,Springboot-3.2.3,Mybatis,shardingsphere-5.5.0
//主依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.3</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>


<!--Mysql连接-->
<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>3.0.3</version>
</dependency>
<!--shardingsphere-jdbc-->
<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>shardingsphere-jdbc</artifactId>
	<version>5.5.0</version>
</dependency>

orm我这里用的是mybatis。因为用的时候github已经有5.5.0版本但是maven仓库还没有,旧版本中间遇到一些问题,就从github拉的镜像打的5.5.0版本。后续可以先优先看官方版本。

  • 表结构,基础创建sql,其他表名自己修改
CREATE TABLE `shard_test0` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `create_tiime` datetime(3) DEFAULT CURRENT_TIMESTAMP(3),
  `update_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

主库:shard_demo 从库:shard_demo1,shard_demo2
读写表测试:shard_test0(user_id,name)
分库测试:user_info(user_id,name),分表形同分库,文章就不贴了
全局表:user_mobile(user_id,mobile)

  • 准备如下sql
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
		PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
		"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ShardMapper">
	<!--分表插入-->
	<insert id="insertByUserId" parameterType="Map">
		insert into shard_test0(`user_id`,`name`) values (#{user_id},unix_timestamp())
	</insert>
	<!--分表查询-->
	<select id="selectByUserId" parameterType="Integer" resultType="Map">
		select * from shard_test0 where user_id=#{userId}
	</select>
	<!--分库插入 || 全局表-->
	<insert id="insertUserByUserId" parameterType="Map">
		insert into user_info(`user_id`,`name`) values (#{user_id},unix_timestamp())
	</insert>
	<!--分库插入 || 绑定表-->
	<insert id="insertMobileByUserId" parameterType="Map">
		insert into user_mobile(`user_id`,`mobile`) values (#{user_id},unix_timestamp())
	</insert>

	<!--关联查询-->
	<select id="selectUserByUserId" parameterType="Integer" resultType="Map">
		select * from user_info u
		left join user_mobile um on u.user_id=um.user_id
		where u.user_id=#{userId}
	</select>
</mapper>
  • 查询和插入的代码先贴这里
    @GetMapping("select")
    public Object select(@RequestParam Integer userId) {
        //强制读主库
//        HintManager hintManager = HintManager.getInstance();
//        hintManager.setWriteRouteOnly();
//		读写分离
//        Map<String, Object> objectMap = shardMapper.selectByUserId(userId);
//        hintManager.close();

        //分表分库 全局表
        Map<String, Object> objectMap = shardMapper.selectUserByUserId(userId);


        return objectMap;
    }

//    @Transactional
    @GetMapping("insert")
    public Object insert() {
        Map<String, Object> resMap = new HashMap<>();
        for (int i = 0; i < 10; i++) {
            resMap.put("user_id", i);
            //读写分离
//            shardMapper.insertByUserId(resMap);

            //分表分库
            shardMapper.insertUserByUserId(resMap);
            // 全局表
            shardMapper.insertMobileByUserId(resMap);
//            if (i == 2) i = 1 / 0;  //@Transactional 默认多个库生效
        }
        return 1;
    }

2.3 默认yaml配置

server:
  port: 12233

mybatis:
  mapper-locations: classpath:mapper/*/*.xml
  executor-type: SIMPLE
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

spring:
  jackson:
    default-property-inclusion: non_null
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    #指定驱动
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    #指向新的yaml配置文件
    url: jdbc:shardingsphere:classpath:config-shard.yaml
  • 相应的配置新建一个配置文件config-shard.yaml,相应配置全部写在哪个配置文件,这里贴一下通用的配置
mode:
  type: Standalone
  repository:
    type: JDBC

#数据源逻辑名称,读写分离时需要用到
dataSources:
  ds0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo
    username: root
    password: 123456
    hikari:
      # 最小连接
      minimum-idle: 5
      # 最大连接数
      maximum-pool-size: 30
      # 获取连接超时时间
      connection-timeout: 5000
      # 空闲连接最大存活的时间
      idle-timeout: 60000
      # 连接最大存活时间
      max-lifetime: 120000
      # 连接有效性检测时间
      validation-timeout: 60000
      # 检测连接是否有效
      connection-test-query: SELECT 1
  ds1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo1
    username: root
    password: 123456
  ds2:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo2
    username: root
    password: 123456

rules:
  - !SINGLE
    tables:
      - "*.*"  #加载指定单表
    # 默认数据源,仅在执行 CREATE TABLE 创建单表时有效。缺失值为空,表示随机单播路由。
    defaultDataSource: ds0

props:
  sql-show: true #开启日志    

接下来写的全是rules下的内容。

2.4 单库分表

作用:三个库中的同一张表,通过取模的方式把数据落到不同的库中。官网直通车

  • shardingsphere分表分库配置
rules:
	- !SHARDING
	tables:
	  user_info:
	    actualDataNodes: ds$->{0..2}.user_info
	#        databaseStrategy:  #分库策略
	#          standard:
	#            shardingColumn: user_id
	#            shardingAlgorithmName: database_inline
	#        tableStrategy:   #分表策略
	#          standard:
	#            shardingColumn: user_id
	#            shardingAlgorithmName: user_info_inline
	defaultDatabaseStrategy: # 默认库分片策略
	  standard:
	    shardingColumn: user_id
	    shardingAlgorithmName: default_inline
	#    defaultTableStrategy:  # 默认表分片策略
	#    defaultKeyGenerateStrategy: # 默认的分布式序列策略
	#    defaultShardingColumn: # 默认分片列名称
	shardingAlgorithms:  #分片算法
	  default_inline:
	    type: INLINE
	    props:
	      algorithm-expression: ds$->{user_id % 3}
	  database_inline:
	    type: INLINE
	    props:
	      algorithm-expression: ds$->{user_id % 3}
	#    keyGenerators: #分布式序列算法配置
	#      snowflake:
	#        type: SNOWFLAKE
	#    auditors:  #分片审计算法配置
	#      sharding_key_required_auditor:
	#        type: DML_SHARDING_CONDITIONS
  • 分别打开查询和插入方法中这两行代码,测试插入和查询日志
//分表分库 全局表
 Map<String, Object> objectMap = shardMapper.selectUserByUserId(userId);
 //分表分库
shardMapper.insertUserByUserId(resMap);
  • 输出日志
//插入日志 根据取模分布到三个库了
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]
Actual SQL: ds2 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]

//查询日志 分别查1,2,3 
Actual SQL: ds1 ::: select * from user_info u
		left join user_mobile um on u.user_id=um.user_id
		where u.user_id=? ::: [1]
Actual SQL: ds2 ::: select * from user_info u
		left join user_mobile um on u.user_id=um.user_id
		where u.user_id=? ::: [2]
Actual SQL: ds0 ::: select * from user_info u
		left join user_mobile um on u.user_id=um.user_id
		where u.user_id=? ::: [3]

通过日志是可以看到能够正常分片插入和分片查询的,功能测试正常。

2.5 广播表(全局表)

作用:全局唯一ID,业务配置冗余,或者是多个库的关联表,插入的数据均分布到多个库中

#数据源配置省略
rules:
  - !BROADCAST
    tables: # 广播表规则列表
      - user_mobile
  • 打开这个代码
// 全局表
shardMapper.insertMobileByUserId(resMap);
  • 测试日志输出
//插入日志
Actual SQL: ds0 ::: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp()) ::: [9]
Actual SQL: ds2 ::: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp()) ::: [9]
Actual SQL: ds1 ::: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp()) ::: [9]

//查询日志输出
这里不贴了,旧文章有

可以看到数据会往两个数据源都插入一份,能够正常关联查询(分片查询实测),而单表查询应该是基于随机算法。

2.6 读写分离

作用:利用数据库的主从复制实现读写分离,提高数据库读取性能,一主多从和多主多从

  • yaml配置,ds0是写库,ds1、ds2是读库
rules:
  - !SINGLE
    tables:
      - "*.*"  #加载指定单表
    # 默认数据源,仅在执行 CREATE TABLE 创建单表时有效。缺失值为空,表示随机单播路由。
    defaultDataSource: ds0
  - !READWRITE_SPLITTING  #读写分离规则
    dataSources:
      readwrite_ds:  #读写分离逻辑数据源名称
        writeDataSourceName: ds0  #写库
        readDataSourceNames:      #从库
          - ds1
          - ds2
        # 事务内读请求的路由策略,可选值:PRIMARY(路由至主库)、FIXED(同一事务内路由至固定数据源)、DYNAMIC(同一事务内路由至非固定数据源)。默认值:DYNAMIC
        transactionalReadQueryStrategy: PRIMARY
        loadBalancerName: load_name  #指定均衡器名称
    loadBalancers:
      load_name:  #负载均衡算法名称 random  round_robin轮询 weight权重
        type: round_robin
#        props:
#          ds1: 9   #权重配置
#          ds2: 1
  • 打开这两行测试代码
Map<String, Object> objectMap = shardMapper.selectByUserId(userId);

shardMapper.insertByUserId(resMap);
  • 测试日志输出
//查询日志
Actual SQL: ds2 ::: select * from shard_test0 where user_id=? ::: [3]
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [4]

//插入日志
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp()) ::: [8]
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp()) ::: [9]

读写分离实测OK,查询默认是轮询算法,如需配置随机算法可参考按需配置。

  • 打开下面代码,强制路由(也支持分片,有实际用途的可查官网)
//读写分离 强制读主库
HintManager hintManager = HintManager.getInstance();
hintManager.setWriteRouteOnly();
Map<String, Object> objectMap = shardMapper.selectByUserId(userId);
hintManager.close();
  • 测试日志
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [5]
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [6]

实测可强制读取主库,其他的还实测了分片插入的事务是正常生效的 ,其他按需就自行去测试吧。

2.7 贴下完整配置

  • 默认的yaml
server:
  port: 12233

mybatis:
  mapper-locations: classpath:mapper/*/*.xml
  executor-type: SIMPLE
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

#https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/
spring:
  jackson:
    default-property-inclusion: non_null
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    url: jdbc:shardingsphere:classpath:config-shard.yaml
    #这块是测试mybatis的
#    driver-class-name: com.mysql.cj.jdbc.Driver
#    url: jdbc:mysql://192.168.168.199:3306/shard_demo
#    username: root
#    password: 123456
  • config-shard.yaml
mode:
  type: Standalone
  repository:
    type: JDBC

#数据源逻辑名称
dataSources:
  ds0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo
    username: root
    password: 123456
    hikari:
      # 最小连接
      minimum-idle: 5
      # 最大连接数
      maximum-pool-size: 30
      # 获取连接超时时间
      connection-timeout: 5000
      # 空闲连接最大存活的时间
      idle-timeout: 60000
      # 连接最大存活时间
      max-lifetime: 120000
      # 连接有效性检测时间
      validation-timeout: 60000
      # 检测连接是否有效
      connection-test-query: SELECT 1
  ds1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo1
    username: root
    password: 123456
  ds2:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo2
    username: root
    password: 123456


rules:
  - !SINGLE
    tables:
      - "*.*"  #加载指定单表
    # 默认数据源,仅在执行 CREATE TABLE 创建单表时有效。缺失值为空,表示随机单播路由。
    defaultDataSource: ds0
  - !READWRITE_SPLITTING  #读写分离规则
    dataSources:
      readwrite_ds:  #读写分离逻辑数据源名称
        writeDataSourceName: ds0  #写库
        readDataSourceNames:      #从库
          - ds1
          - ds2
        # 事务内读请求的路由策略,可选值:PRIMARY(路由至主库)、FIXED(同一事务内路由至固定数据源)、DYNAMIC(同一事务内路由至非固定数据源)。默认值:DYNAMIC
        transactionalReadQueryStrategy: PRIMARY
        loadBalancerName: load_name  #指定均衡器名称
    loadBalancers:
      load_name:  #负载均衡算法名称 random  round_robin轮询 weight权重
        type: round_robin
#        props:
#          ds1: 9   #权重配置
#          ds2: 1
#  #分表分库 https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/
#  - !SHARDING
#    tables:
#      user_info:
#        actualDataNodes: ds$->{0..2}.user_info
##        databaseStrategy:  #分库策略
##          standard:
##            shardingColumn: user_id
##            shardingAlgorithmName: database_inline
##        tableStrategy:   #分表策略
##          standard:
##            shardingColumn: user_id
##            shardingAlgorithmName: user_info_inline
#    defaultDatabaseStrategy: # 默认库分片策略
#      standard:
#        shardingColumn: user_id
#        shardingAlgorithmName: default_inline
##    defaultTableStrategy:  # 默认表分片策略
##    defaultKeyGenerateStrategy: # 默认的分布式序列策略
##    defaultShardingColumn: # 默认分片列名称
#    shardingAlgorithms:  #分片算法
#      default_inline:
#        type: INLINE
#        props:
#          algorithm-expression: ds$->{user_id % 3}
#      database_inline:
#        type: INLINE
#        props:
#          algorithm-expression: ds$->{user_id % 3}
##    keyGenerators: #分布式序列算法配置
##      snowflake:
##        type: SNOWFLAKE
##    auditors:  #分片审计算法配置
##      sharding_key_required_auditor:
##        type: DML_SHARDING_CONDITIONS
#
#  - !BROADCAST
#    tables: # 广播表规则列表
#      - user_mobile

props:
  sql-show: true

# @Transactional 默认多个库生效  如配置其他的事务方式需要引入shardingsphere-transaction-xa-core
#transaction:
#  defaultType: XA  # 事务模式,可选值 LOCAL/XA/BASE
#  providerType: Atomikos  #XA/BASE 指定模式下的具体实现

3.总结

总体来说用起来还是比较舒服的,完美的实现了分库分表,读写分离。
这里贴一下要注意的问题点:
1.有些配置里面的<table-name>是一定要写对应的表名的,有些这种配置就可以随便定义
2.分片键配置的行内表达式如果有比较复杂的,可手写代码实现分片规则
3.读延迟问题,可在读之前执行HintManager.getInstance().setWriteRouteOnly();这个代码强制读主库,但是如果还有后续查询记得清除线程变量。

总结一下和mycat的区别:

SharingSphere-JDBCMycat
工作层面JDBC协议,接口封装Mysql协议,JDBC协议
运行方式Jar包,客户端独立服务,服务端
开发方式代码,配置数据量连接地址
运维方式运维单独维护
性能多线程并发独立服务,需要看服务器配置
支持语言仅支持Java支持JDBC协议的语言

2.老版本ShardingSphere-JDBC 5.1.5

2.1 介绍

ShardingSphere-JDBC自身定义为轻量级Java框架,可在Java JDBC层提供额外的服务。客户端直接连接到数据库时,它以jar形式提供服务,并且不需要额外的部署。

2.2 项目详细

  • pom.xml
    本文基于springboot-2.5.6,shardingsphere-5.1.5
<!--Mysql连接-->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.2.0</version>
</dependency>
<!--shardingsphere-jdbc-->
<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
	<version>5.1.1</version>
</dependency>

orm我这里用的是mybatis。

  • 表结构
    主库:demo_test 从库:demo_test1,demo_test2
    分表:shard_test0、shard_test1,字段user_id,name
    主从表:user_info(user_id,name),user_mobile(user_id,mobile)

  • 插入代码和sql

@GetMapping("insert")
public Object insert() {
    Map<String, Object> resMap = new HashMap<>();
    for (int i = 0; i < 10; i++) {
        resMap.put("user_id", i);
        shardMapper.insertByUserId(resMap);
    }
    return 1;
}
  • sql
<!--分表插入-->
<insert id="insertByUserId" parameterType="Map">
	insert into shard_test(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分表查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
	select * from shard_test where user_id=#{userId}
</select>
<!--分库插入 || 全局表-->
<insert id="insertUserByUserId" parameterType="Map">
	insert into user_info(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分库插入 || 绑定表-->
<insert id="insertMobileByUserId" parameterType="Map">
	insert into user_mobile(`user_id`,`mobile`) values (#{user_id},unix_timestamp())
</insert>

<!--关联查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
	select * from user_info u
	left join user_mobile um on u.user_id=um.user_id
	where u.user_id=#{userId}
</select>

2.3 单库分表

作用:一个库两张表,通过取模的方式数据落到不同的表中。官网直通车

  • shardingsphere配置
spring:
  shardingsphere:
    datasource:
      names: ds0 #数据源名称
      ds0:
        type: com.zaxxer.hikari.HikariDataSource #使用的数据池
        driver-class-name: com.mysql.jdbc.Driver #驱动
        jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test #连接地址
        username: root #账号
        password: 123456 #密码
    rules: #规则
      sharding: 
        tables:
          shard_test: #表名
          #分片表配置由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
            actual-data-nodes: ds0.shard_test$->{0..1} 
            table-strategy: #分表策略
              standard: #单分片键的场景
                sharding-column: user_id #分片键 就是根据那个字段分片
                sharding-algorithm-name: my_table #分片算法名称 详细在下面
        sharding-algorithms: #分片算法
          my_table: #上面自定义的算法名称
            type: INLINE
            props:
              #分片算法计算规则
              algorithm-expression: shard_test$->{user_id % 2}     props:
      sql-show: true #开启日志
  • 测试插入和查询日志
//插入日志 根据取模分布到ds0库的shard_test0和shard_test1表了
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into shard_test(`user_id`,`name`) values (?,unix_timestamp())

Actual SQL: ds0 ::: insert into shard_test1(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Logic SQL: select * from shard_test where user_id=?


//user_id  1和2的查询日志
Actual SQL: ds0 ::: select * from shard_test1 where user_id=? ::: [1]
Logic SQL: select * from shard_test where user_id=?

Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [2]

通过日志是可以看到能够正常分片插入和分片查询的。但是这种单库分表一般不怎么推荐,毕竟服务器压力还是在

2.4 分库单表

作用:将数据分片到不同的数据库中的同一张表中
数据库ds0,ds1表user_info

spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test
        username: root
        password: 123456
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test1
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          user_info: #表名
           #分片表配置由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
            actual-data-nodes: ds$->{0..1}.user_info
			#分库分片键 和分表配置不同的就是这个key
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: my_database
        sharding-algorithms:
          my_database:
            type: INLINE
            props:
              #分片键算法
              algorithm-expression: ds$->{user_id % 2}
    props:
      sql-show: true
  • 插入日志
Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [6]
insert into user_info(`user_id`,`name`) values (?,unix_timestamp())
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]

根据user_id取模分布到不同的数据库中了

2.5 绑定表

作用:分库后的关联查询,避免出现笛卡尔积

#数据源配置省略了
rules:
  sharding:
    tables:
      user_info: #user_info分库策略
        actual-data-nodes: ds$->{0..1}.user_info
        database-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: my_database
      user_mobile:  #user_mobile分库策略
        actual-data-nodes: ds$->{0..1}.user_mobile
        database-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: my_database
    sharding-algorithms:
      my_database: #使用的都是my_database这个分片算法
        type: INLINE
        props:
          algorithm-expression: ds$->{user_id % 2}
    #绑定表 这里是关键 如果又分表又分库 关联查询会出现笛卡尔积       
    binding-tables: user_info,user_mobile
  • 测试日志输出
//往两个表插入数据
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]
Logic SQL: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp())
Actual SQL: ds1 ::: insert into user_mobile(`user_id`,`mobile`) values (?, unix_timestamp()) ::: [7]
insert into user_info(`user_id`,`name`) values (?,unix_timestamp())

Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp())
Actual SQL: ds0 ::: insert into user_mobile(`user_id`,`mobile`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into user_info(`user_id`,`name`) values (?,unix_timestamp())

//查询关联查询日志user_id 2和3
Actual SQL: ds0 ::: select * from user_info u
		left join user_mobile um on u.user_id=um.user_id
		where u.user_id=? ::: [2]
Actual SQL: ds1 ::: select * from user_info u
		left join user_mobile um on u.user_id=um.user_id
		where u.user_id=? ::: [3]

关联查询能正常路由到指定的数据库

2.6 广播表(全局表)

作用:全局唯一ID,业务配置冗余,插入的数据均分布到两个库中

#数据源配置省略
rules:
  sharding:
    #只需要声明表名
    broadcast-tables: shard_test0
  • 测试日志输出
//插入日志
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Actual SQL: ds1 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Logic SQL: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp())

//查询日志输出
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [5]
Logic SQL: select * from shard_test0 where user_id=?

//第三次查询
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [5]

可以看到数据会往两个数据源都插入一份,而查询应该是基于随机算法

2.7 读写分离

作用:利用数据库的主从复制实现读写分离,提高数据库读取性能,一主多从和多主多从

  • yaml配置
rules:
  #读写分离
  readwrite-splitting:
    data-sources:
      ds0: #上面定义的数据源名字 也可随便定义不重复的
        type: Static #类型Static,Dynamic
        props:
          #写库
          write-data-source-name: ds0
          #读库 多个从库用逗号分隔
          read-data-source-names: ds1,ds2
#          ds1: #多个主库需要加这个配置 和分库配置
#            type: Static
#            props:
#              write-data-source-name: ds1
#              read-data-source-names: ds3
  • 测试日志输出
//查询日志
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [7]
Logic SQL: select * from shard_test0 where user_id=?
Actual SQL: ds2 ::: select * from shard_test0 where user_id=? ::: [8]

默认是轮询算法,如需配置随机算法可参考下面配置

  rules:
    readwrite-splitting:
      data-sources:
        ds0:
          type: Static
          props:
            write-data-source-name: ds0
            read-data-source-names: ds1,ds2
          #复制均衡算法名称
          load-balancer-name: load_name
      load-balancers:
        load_name: #名称
          #算法 round_robin/轮询 random/随机 权重
          type: random
          props:
            default: 0 #要有这个 不然启动报错

以上就是本章的全部内容了。

上一篇:mysql第九话 - mysql主从复制集群实现
下一篇:通信框架之Netty第一话 - NIO的超神发展之路

天行健,君子以自强不息

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐