• ShardingSphere水平分片、多表关联、绑定表、广播表


    草在结它的种子,风在摇她的叶子,我们就这样坐着不说话,就十分美好。

    完整代码已上传Gitee

    1、准备服务器

    服务器规划:使用docker方式创建如下容器

    在这里插入图片描述

    • 服务器:容器名server-order0,端口3310
    • 服务器:容器名server-order1,端口3311
    • 服务器:容器名server-user,端口3301

    1.1、创建server-order0容器

    • step1:创建容器:
    docker run -d \
    -p 3310:3306 \
    -v /server/order0/conf:/etc/mysql/conf.d \
    -v /server/order0/data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    --name server-order0 \
    mysql:8.0.29
    
    • step2:登录MySQL服务器:
    #进入容器:
    docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash
    #进入容器内的mysql命令行
    mysql -uroot -p
    #修改默认密码插件
    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
    
    • step3:创建数据库:

    注意:水平分片的id需要在业务层实现,不能依赖数据库的主键自增

    CREATE DATABASE db_order;
    USE db_order;
    CREATE TABLE t_order0 (
      id BIGINT,
      order_no VARCHAR(30),
      user_id BIGINT,
      amount DECIMAL(10,2),
      PRIMARY KEY(id) 
    );
    CREATE TABLE t_order1 (
      id BIGINT,
      order_no VARCHAR(30),
      user_id BIGINT,
      amount DECIMAL(10,2),
      PRIMARY KEY(id) 
    );
    

    1.2、创建server-order1容器

    • step1:创建容器:
    docker run -d \
    -p 3311:3306 \
    -v /server/order1/conf:/etc/mysql/conf.d \
    -v /server/order1/data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    --name server-order1 \
    mysql:8.0.29
    
    • step2:登录MySQL服务器:
    #进入容器:
    docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash
    #进入容器内的mysql命令行
    mysql -uroot -p
    #修改默认密码插件
    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
    
    • step3:创建数据库:server-order0相同

    注意:水平分片的id需要在业务层实现,不能依赖数据库的主键自增

    CREATE DATABASE db_order;
    USE db_order;
    CREATE TABLE t_order0 (
      id BIGINT,
      order_no VARCHAR(30),
      user_id BIGINT,
      amount DECIMAL(10,2),
      PRIMARY KEY(id) 
    );
    CREATE TABLE t_order1 (
      id BIGINT,
      order_no VARCHAR(30),
      user_id BIGINT,
      amount DECIMAL(10,2),
      PRIMARY KEY(id) 
    );
    

    1.3、创建server-user容器

    • step1:创建容器:
    docker run -d \
    -p 3301:3306 \
    -v /server/user/conf:/etc/mysql/conf.d \
    -v /server/user/data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    --name server-user \
    mysql:8.0.29
    

    。。。。

    CREATE TABLE `t_user` (
      `id` bigint NOT NULL AUTO_INCREMENT,
      `uname` varchar(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) 
    

    2、基本水平分片

    2.1、基本配置

    #========================基本配置
    spring:
      application:
        name: sharging-jdbc-demo
      shardingsphere:
      # 内存模式
        mode:
          type: Memory
        # 打印SQl
        props:
          sql-show: true
    

    2.2、数据源配置

    spring:
      shardingsphere:
        # 配置三个数据源
        datasource:
          names: server-user,server-order0,server-order1
          server-order0:
            driver-class-name: com.mysql.jdbc.Driver
            jdbc-url: jdbc:mysql://192.168.1.1:3310/db_order
            password: 123456
            type: com.zaxxer.hikari.HikariDataSource
            username: root
          server-order1:
            driver-class-name: com.mysql.jdbc.Driver
            jdbc-url: jdbc:mysql://192.168.1.1:3311/db_order
            password: 123456
            type: com.zaxxer.hikari.HikariDataSource
            username: root
          server-user:
            driver-class-name: com.mysql.jdbc.Driver
            jdbc-url: jdbc:mysql://192.168.1.1:3301/db_user
            password: 123456
            type: com.zaxxer.hikari.HikariDataSource
            username: root
    

    2.3、标椎分片表配置

    为了方便查看就不贴yaml格式了

    #========================标准分片表配置(数据节点配置)
    # spring.shardingsphere.rules.sharding.tables..actual-data-nodes=值
    # 值由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
    # :逻辑表名
    spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
    spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order0,server-order0.t_order1,server-order1.t_order0,server-order1.t_order1
    

    Order实体类的主键策略:

    @TableId(type = IdType.ASSIGN_ID)//分布式id
    

    测试:保留上面配置中的一个分片表节点分别进行测试,检查每个分片节点是否可用

    /**
         * 水平分片:插入数据测试
         */
    @Test
    public void testInsertOrder(){
        Order order = new Order();
        order.setOrderNo("ShardingSphere001");
        order.setUserId(1L);
        order.setAmount(new BigDecimal(100));
        orderMapper.insert(order);
    }
    

    2.4、行表达式

    优化上一步的分片表配置

    https://shardingsphere.apache.org/document/5.1.1/cn/features/sharding/concept/inline-expression/

    #========================标准分片表配置(数据节点配置)
    # spring.shardingsphere.rules.sharding.tables..actual-data-nodes=值
    # 值由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
    # :逻辑表名
    spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
    spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}
    

    2.5、分片算法配置

    水平分库:

    分片规则:order表中user_id为偶数时,数据插入server-order0服务器user_id为奇数时,数据插入server-order1服务器。这样分片的好处是,同一个用户的订单数据,一定会被插入到同一台服务器上,查询一个用户的订单时效率较高。

    #------------------------分库策略
    # 分片列名称
    spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
    # 分片算法名称
    spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_userid
    
    #------------------------分片算法配置
    # 行表达式分片算法
    # 分片算法类型
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.type=INLINE
    # 分片算法属性配置
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.props.algorithm-expression=server-order$->{user_id % 2}
    
    # 取模分片算法
    # 分片算法类型
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
    # 分片算法属性配置,分片数量
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2
    

    为了方便测试,先设置只在 t_order0表上进行测试(目前还没做分表配置

    xxx.actual-data-nodes=server-order$->{0..1}.t_order0
    

    测试:可以分别测试行表达式分片算法和取模分片算法

    /**
         * 水平分片:分库插入数据测试
         */
    @Test
    public void testInsertOrderDatabaseStrategy(){
    
        for (long i = 0; i < 4; i++) {
            Order order = new Order();
            order.setOrderNo("ShardingSphere001");
            order.setUserId(i + 1);
            order.setAmount(new BigDecimal(100));
            orderMapper.insert(order);
        }
    
    }
    

    水平分表:

    分片规则:order表中order_no的哈希值为偶数时,数据插入对应服务器的t_order0表order_no的哈希值为奇数时,数据插入对应服务器的t_order1表。因为order_no是字符串形式,因此不能直接取模。

    #------------------------分表策略
    # 分片列名称
    spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
    # 分片算法名称
    spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod
    
    
    #------------------------分片算法配置
    # 哈希取模分片算法
    # 分片算法类型
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
    # 分片算法属性配置
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2
    
    

    测试前不要忘记将如下节点改回原来的状态(上边配置了分片+分表策略,这里就可以用四个节点

    xxx.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}
    

    测试:

    /**
         * 水平分片:分表插入数据测试
         */
    @Test
    public void testInsertOrderTableStrategy(){
    
        for (long i = 1; i < 5; i++) {
            Order order = new Order();
            order.setOrderNo("ShardingSphere" + i);
            order.setUserId(1L);
            order.setAmount(new BigDecimal(100));
            orderMapper.insert(order);
        }
    
        for (long i = 5; i < 9; i++) {
            Order order = new Order();
            order.setOrderNo("ShardingSphere" + i);
            order.setUserId(2L);
            order.setAmount(new BigDecimal(100));
            orderMapper.insert(order);
        }
    }
    
    /**
         * 测试哈希取模
         */
    @Test
    public void testHash(){
    
        //注意hash取模的结果是整个字符串hash后再取模,和数值后缀是奇数还是偶数无关
        System.out.println("ShardingSphere001".hashCode() % 2);
        System.out.println("ShardingSphere0011".hashCode() % 2);
    }
    

    查询测试:

    /**
        * 水平分片:查询所有记录
        * 查询了两个数据源,每个数据源中使用UNION ALL连接两个表
        */
    @Test
    public void testShardingSelectAll(){
    
        List<Order> orders = orderMapper.selectList(null);
        orders.forEach(System.out::println);
    }
    
    /**
         * 水平分片:根据user_id查询记录
         * 查询了一个数据源,每个数据源中使用UNION ALL连接两个表
         */
    @Test
    public void testShardingSelectByUserId(){
    
        QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
        orderQueryWrapper.eq("user_id", 1L);
        List<Order> orders = orderMapper.selectList(orderQueryWrapper);
        orders.forEach(System.out::println);
    }
    

    2.6、分布式序列算法

    雪花算法:

    https://shardingsphere.apache.org/document/5.1.1/cn/features/sharding/concept/key-generator/

    水平分片需要关注全局序列,因为不能简单的使用基于数据库的主键自增。

    这里有两种方案:一种是基于MyBatisPlus的id策略;一种是ShardingSphere-JDBC的全局序列配置。

    基于MyBatisPlus的id策略:将Order类的id设置成如下形式

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    

    基于ShardingSphere-JDBC的全局序列配置:和前面的MyBatisPlus的策略二选一

    #------------------------分布式序列策略配置
    # 分布式序列列名称
    spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
    # 分布式序列算法名称
    spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake
    
    # 分布式序列算法配置
    # 分布式序列算法类型
    spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
    # 分布式序列算法属性配置,如果指定了分布式序列为分片id需要配置,这里没有指定所以不配
    #spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.xxx=
    

    此时,需要将实体类中的id策略修改成以下形式:

    //当配置了shardingsphere-jdbc的分布式序列时,自动使用shardingsphere-jdbc的分布式序列
    //当没有配置shardingsphere-jdbc的分布式序列时,自动依赖数据库的主键自增策略
    @TableId(type = IdType.AUTO)
    

    3、多表关联

    3.1、创建关联表

    server-order0、server-order1服务器中分别创建两张订单详情表t_order_item0、t_order_item1

    我们希望同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联,因此这两张表我们使用相同的分片策略。

    那么在t_order_item中我们也需要创建order_nouser_id这两个分片键

    CREATE TABLE t_order_item0(
        id BIGINT,
        order_no VARCHAR(30),
        user_id BIGINT,
        price DECIMAL(10,2),
        `count` INT,
        PRIMARY KEY(id)
    );
    
    CREATE TABLE t_order_item1(
        id BIGINT,
        order_no VARCHAR(30),
        user_id BIGINT,
        price DECIMAL(10,2),
        `count` INT,
        PRIMARY KEY(id)
    );
    

    3.2、创建实体类

    package com.ShardingSphere.shardingjdbcdemo.entity;
    
    @TableName("t_order_item")
    @Data
    public class OrderItem {
        //当配置了shardingsphere-jdbc的分布式序列时,自动使用shardingsphere-jdbc的分布式序列
        @TableId(type = IdType.AUTO)
        private Long id;
        private String orderNo;
        private Long userId;
        private BigDecimal price;
        private Integer count;
    }
    

    3.3、创建Mapper

    package com.ShardingSphere.shargingjdbcdemo.mapper;
    
    @Mapper
    public interface OrderItemMapper extends BaseMapper<OrderItem> {
    
    }
    

    3.4、配置关联表

    t_order_item的分片表、分片策略、分布式序列策略和t_order一致

    #------------------------标准分片表配置(数据节点配置)
    spring.shardingsphere.rules.sharding.tables.t_order_item.actual-data-nodes=server-order$->{0..1}.t_order_item$->{0..1}
    
    #------------------------分库策略
    # 分片列名称
    spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-column=user_id
    # 分片算法名称
    spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-algorithm-name=alg_mod
    
    #------------------------分表策略
    # 分片列名称
    spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-column=order_no
    # 分片算法名称
    spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-algorithm-name=alg_hash_mod
    
    #------------------------分布式序列策略配置
    # 分布式序列列名称
    spring.shardingsphere.rules.sharding.tables.t_order_item.key-generate-strategy.column=id
    # 分布式序列算法名称
    spring.shardingsphere.rules.sharding.tables.t_order_item.key-generate-strategy.key-generator-name=alg_snowflake
    

    3.5、测试插入数据

    同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联

    /**
         * 测试关联表插入
         */
    @Test
    public void testInsertOrderAndOrderItem(){
    
        for (long i = 1; i < 3; i++) {
    
            Order order = new Order();
            order.setOrderNo("ShardingSphere" + i);
            order.setUserId(1L);
            orderMapper.insert(order);
    
            for (long j = 1; j < 3; j++) {
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderNo("ShardingSphere" + i);
                orderItem.setUserId(1L);
                orderItem.setPrice(new BigDecimal(10));
                orderItem.setCount(2);
                orderItemMapper.insert(orderItem);
            }
        }
    
        for (long i = 5; i < 7; i++) {
    
            Order order = new Order();
            order.setOrderNo("ShardingSphere" + i);
            order.setUserId(2L);
            orderMapper.insert(order);
    
            for (long j = 1; j < 3; j++) {
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderNo("ShardingSphere" + i);
                orderItem.setUserId(2L);
                orderItem.setPrice(new BigDecimal(1));
                orderItem.setCount(3);
                orderItemMapper.insert(orderItem);
            }
        }
    
    }
    

    4、绑定表

    需求: 查询每个订单的订单号和总订单金额

    4.1、创建VO对象

    package com.ShardingSphere.shardingjdbcdemo.entity;
    
    @Data
    public class OrderVo {
        private String orderNo;
        private BigDecimal amount;
    }
    

    4.2、添加Mapper方法

    package com.ShardingSphere.shardingjdbcdemo.mapper;
    
    @Mapper
    public interface OrderMapper extends BaseMapper<Order> {
    
        @Select({"SELECT o.order_no, SUM(i.price * i.count) AS amount",
                "FROM t_order o JOIN t_order_item i ON o.order_no = i.order_no",
                "GROUP BY o.order_no"})
        List<OrderVo> getOrderAmount();
    
    }
    

    4.3、测试关联查询

    /**
         * 测试关联表查询
         */
    @Test
    public void testGetOrderAmount(){
    
        List<OrderVo> orderAmountList = orderMapper.getOrderAmount();
        orderAmountList.forEach(System.out::println);
    }
    

    在这里插入图片描述

    可以看到同一个数据源中,查询的次数是t_ordert_order_item的笛卡尔积数量,但是t_order0中的订单数据只会在对应数据源中t_order_item0,不会在t_order_item1中,所以有些关联查询是没有意义的。

    4.4、配置绑定表

    在原来水平分片配置的基础上添加如下配置:

    #------------------------绑定表
    spring.shardingsphere.rules.sharding.binding-tables[0]=t_order,t_order_item
    

    配置完绑定表后再次进行关联查询的测试:

    • 如果不配置绑定表:测试的结果为8个SQL。 多表关联查询会出现笛卡尔积关联。

    • 如果配置绑定表:测试的结果为4个SQL。 多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。

    在这里插入图片描述

    绑定表:指分片规则一致的一组分片表。 使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。

    5、广播表

    4.1、什么是广播表

    指所有的分片数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

    广播具有以下特性:

    (1)插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性

    (2)查询操作,只从一个节点获取

    (3)可以跟任何一个表进行 JOIN 操作

    4.2、创建广播表

    server-order0server-order1server-user服务器中分别创建t_dict

    CREATE TABLE t_dict(
        id BIGINT,
        dict_type VARCHAR(200),
        PRIMARY KEY(id)
    );
    

    4.3、程序实现

    4.3.1、创建实体类
    @TableName("t_dict")
    @Data
    public class Dict {
        //可以使用MyBatisPlus的雪花算法
        @TableId(type = IdType.ASSIGN_ID)
        private Long id;
        private String dictType;
    }
    
    4.3.2、创建Mapper
    @Mapper
    public interface DictMapper extends BaseMapper<Dict> {
    }
    
    4.3.3、配置广播表
    # 广播表
    spring:
      shardingsphere:
        # 配置三个数据源
        datasource:
          names: server-user,server-order0,server-order1
          server-order0:
            driver-class-name: com.mysql.jdbc.Driver
            jdbc-url: jdbc:mysql://192.168.1.1:3310/db_order
            password: 123456
            type: com.zaxxer.hikari.HikariDataSource
            username: root
          server-order1:
            driver-class-name: com.mysql.jdbc.Driver
            jdbc-url: jdbc:mysql://192.168.1.1:3311/db_order
            password: 123456
            type: com.zaxxer.hikari.HikariDataSource
            username: root
          server-user:
            driver-class-name: com.mysql.jdbc.Driver
            jdbc-url: jdbc:mysql://192.168.1.1:3301/db_user
            password: 123456
            type: com.zaxxer.hikari.HikariDataSource
            username: root
        mode:
          type: Memory
        props:
          sql-show: true
        rules:
          sharding:
            # # 广播表
            broadcast-tables:
              - t_dict
            # #数据节点可不配置,默认情况下,向所有数据源广播
            tables:
              t_dict:
                actual-data-nodes: server-user.t_dict,server-order$->{0..1}.t_dict
    

    4.4、测试广播表

    @Autowired
    private DictMapper dictMapper;
    
    /**
         * 广播表:每个服务器中的t_dict同时添加了新数据
         */
    @Test
    public void testBroadcast(){
        Dict dict = new Dict();
        dict.setDictType("type1");
        dictMapper.insert(dict);
    }
    
    /**
         * 查询操作,只从一个节点获取数据
         * 随机负载均衡规则
         */
    @Test
    public void testSelectBroadcast(){
        List<Dict> dicts = dictMapper.selectList(null);
        dicts.forEach(System.out::println);
    }
    

    在这里插入图片描述

    在这里插入图片描述

    笔记出处


    完整代码已上传 Gitee Spring整合常用组件

    到此,本章内容就介绍完啦,如果有帮助到你 欢迎点个赞👍👍吧!!您的鼓励是博主的最大动力! 有问题评论区交流。

  • 相关阅读:
    基于Docker搭建ELK(Elasticsearch、Logstash、Kibana)日志框架
    什么是幂等性?四种接口幂等性方案详解!
    oracle定时任务的使用
    浅谈MySQL执行计划Explain
    C++学习——C++函数的编译、成员函数的调用、this指针详解
    CSS三维倾斜
    @HttpMessageConverter注解的基本介绍
    23ai中的True Cache到底能做啥?
    Java面向对象程序设计|二人间对话示例
    网络通信架构
  • 原文地址:https://blog.csdn.net/weixin_43847283/article/details/127039580