java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > shardingsphere-jdbc 分库分表

shardingsphere-jdbc5.5.1分库分表实战

作者:荒古废体

本文主要介绍了shardingsphere-jdbc5.5.1分库分表实战,解决了分表和读写分离的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

本文章主要是:
分享分库分表的demo案例,技术选型,数据量达到4亿用户。后面待补充详情…

背景:

前言:

高性能数据库集群的第一种方式是“读写分离”第二种方式是“数据库分片”

版本依赖:

主要版本:

Springboot 3.3.4

shardingsphere-jdbc 5.5.1

mybatis-plus-spring-boot3-starter 3.5.8

有三种方案:

  1. 只分库不分表
  2. 只分表不分库
  3. 既分库又分表

本帖子只记录单表多库,多库多表的例子。

功能实现

目录结构如下:

maven版本

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atdx</groupId>
    <artifactId>sharding_sphere</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shardingSphere</name>

    <description>分库分表案例</description>

    <properties>
        <java.version>17</java.version>
        <snakeyaml.version>2.2</snakeyaml.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.32</version>
        </dependency>

        <!-- 解决自带 snakeyaml 版本过低的问题-->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>${snakeyaml.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc</artifactId>
            <version>5.5.1</version>
            <!-- 排除冲突的 SnakeYAML -->
            <exclusions>
                <exclusion>
                    <groupId>org.yaml</groupId>
                    <artifactId>snakeyaml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
            <scope>runtime</scope>
        </dependency>

        <!-- MyBatis Plus Spring Boot 3兼容版本 -->
<!--        <dependency>-->
<!--            <groupId>com.baomidou</groupId>-->
<!--            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>-->
<!--            <version>3.5.8</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.yaml</groupId>
                <artifactId>snakeyaml</artifactId>
                <version>${snakeyaml.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

########### V1 实现了 单库,多个表 #############
spring:
  application:
    name: sharding-jdbc-demo
  # 使用ShardingSphere Driver + 独立配置文件
  # 配置数据库的连接信息
  datasource:
    #动态数据源配置
    dynamic:
      #主数据源,默认启用
      primary: master
      datasource:
        #数据源 本地库
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/wedding_bill_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
          username: root
          password: rootroot
          # 分库分表逻辑
        shardingSphere:
          driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
          url: jdbc:shardingsphere:classpath:sharding.yaml
          # 移除username和password,因为在sharding.yaml中配置
#          username: root
#          password: rootroot


# MyBatis Plus 配置
mybatis-plus:
  mapper-locations: classpath:mapper/shard/**/*.xml
  type-aliases-package: com.**.**.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 雪花算法
      id-type: ASSIGN_ID

# 分库分表的密钥 用于手机号接收加密和解密用
sharding:
  sphere:
    secretKey:
      phoneNum: T9ycWqd/VgQ32x0MRxU0cQ==

1,单库多表

sharding.yaml 配置如下

####################### V1 第一版 简单实现单个库,每个库有3个表的 分库逻辑############################################################################
dataSources:
  ds:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/db_user?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot

rules:
  - !SHARDING
    tables:
      t_user:
        actualDataNodes: ds.t_user_$->{0..2}
        tableStrategy:
          standard:
            # 分表字段 phone_num 根据手机号进行分表
            shardingColumn: phone_num
            # 保持一致性
            shardingAlgorithmName: db-user-inline
    shardingAlgorithms:
      db-user-inline:
        type: INLINE
        props:
          # 根据手机号取模 hash 运算
          algorithm-expression: t_user_$->{Math.abs(phone_num.hashCode()) % 3}
    defaultDatabaseStrategy:
      none:

props:
  sql-show: true

自动生成手机号工具类:

package com.at.ss.shardingsphere.utils;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 手机号生成工具类
 * @author daxigua
 */
public class PhoneNumberGenerator {

    // 手机号前缀(中国大陆)
    private static final String[] PHONE_PREFIXES = {
            "130", "131", "132", "133", "134", "135", "136", "137", "138", "139",
            "145", "147", "149", "150", "151", "152", "153", "155", "156", "157", "158", "159",
            "165", "166", "167", "170", "171", "172", "173", "174", "175", "176", "177", "178",
            "180", "181", "182", "183", "184", "185", "186", "187", "188", "189",
            "191", "198", "199"
    };

    private static final Random random = new Random();
    private static final AtomicLong counter = new AtomicLong(1000000000L);

    /**
     * 生成随机手机号
     */
    public static String generateRandomPhone() {
        String prefix = PHONE_PREFIXES[random.nextInt(PHONE_PREFIXES.length)];
        String suffix = String.format("%08d", random.nextInt(100000000));
        return prefix + suffix;
    }

    /**
     * 生成序列化手机号(避免重复)
     */
    public static String generateSequentialPhone() {
        long number = counter.getAndIncrement();
        String prefix = PHONE_PREFIXES[(int) (number % PHONE_PREFIXES.length)];
        String suffix = String.format("%08d", number % 100000000);
        return prefix + suffix;
    }

    /**
     * 批量生成手机号
     */
    public static List<String> generateBatchPhones(int count, boolean sequential) {
        Set<String> phones = new HashSet<>(count) {
        };
        for (int i = 0; i < count; i++) {
            if (sequential) {
                phones.add(generateSequentialPhone());
            } else {
                phones.add(generateRandomPhone());
            }
        }
        return new ArrayList<>( phones);
    }
}

雪花算法ID 工具类

package com.at.ss.shardingsphere.utils;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SnowflakeIdGenerator {

    // ==============================Fields===========================================
    /** 开始时间截 (2024-01-01) */
    private final long startTimestamp = 1704067200000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    // ==============================Constructors=====================================
    /**
     * 构造函数
     */
    public SnowflakeIdGenerator() {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        // 时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;

        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - startTimestamp) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 生成指定数量的ID数组(用于测试分库分表)
     */
    public long[] generateIds(int count) {
        long[] ids = new long[count];
        for (int i = 0; i < count; i++) {
            ids[i] = nextId();
        }
        return ids;
    }

    /**
     * 生成指定数量的ID并放入ArrayList集合
     */
    public List<Long> generateIdList(int count) {
        List<Long> idList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            idList.add(nextId());
        }
        return idList;
    }

    /**
     * 生成指定数量的ID并放入LinkedList集合
     */
    public List<Long> generateIdLinkedList(int count) {
        List<Long> idList = new LinkedList<>();
        for (int i = 0; i < count; i++) {
            idList.add(nextId());
        }
        return idList;
    }

    /**
     * 生成指定数量的ID并放入线程安全的CopyOnWriteArrayList集合
     */
    public List<Long> generateIdConcurrentList(int count) {
        List<Long> idList = new CopyOnWriteArrayList<>();
        for (int i = 0; i < count; i++) {
            idList.add(nextId());
        }
        return idList;
    }

    /**
     * 批量生成ID并放入指定集合(通用方法)
     */
    public <T extends Collection<Long>> T generateIdsToCollection(int count, T collection) {
        for (int i = 0; i < count; i++) {
            collection.add(nextId());
        }
        return collection;
    }

    /**
     * 生成ID并分批放入多个集合(用于分库分表测试)
     */
    public Map<Integer, List<Long>> generateIdsForSharding(int totalCount, int shardCount) {
        Map<Integer, List<Long>> shardMap = new HashMap<>();

        // 初始化每个分片的集合
        for (int i = 0; i < shardCount; i++) {
            shardMap.put(i, new ArrayList<>());
        }

        // 生成ID并根据分片规则分配到不同集合
        for (int i = 0; i < totalCount; i++) {
            long id = nextId();
            int shardIndex = (int) (id % shardCount); // 简单的取模分片
            shardMap.get(shardIndex).add(id);
        }

        return shardMap;
    }

    /**
     * 异步生成ID集合(高性能版本)
     */
    public CompletableFuture<List<Long>> generateIdsAsync(int count) {
        return CompletableFuture.supplyAsync(() -> generateIdList(count));
    }

    /**
     * 流式生成ID集合(Java 8 Stream API)
     */
    public List<Long> generateIdsStream(int count) {
        return Stream.generate(this::nextId)
                .limit(count)
                .collect(Collectors.toList());
    }
}

测试方法:

  /**
     * 测试单库分表,每个库3张表
     */
    @Test
    public void singleDatabaseWithTablePartitioning(){
        SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator();
        List<String> phones = PhoneNumberGenerator.generateBatchPhones(500, false);
        ArrayList<Long> snowflakeIds = snowflakeIdGenerator.generateIdsToCollection(500, new ArrayList<>());
        for (int i = 0; i < 500; i++) {
            User user = new User();
            user.setId(snowflakeIds.get(i));
            user.setUname("张三丰");
            user.setPhoneNum(phones.get(i));
            // 验证分表计算
            String tableName = this.calculateTableName(user.getPhoneNum());
            log.info("手机号 " + user.getPhoneNum() + " 应该分配到表: " + tableName);
            userMapper.insert(user);
            log.info("插入用户ID: " + user.getId() + ", 手机号: " + user.getPhoneNum());
        }
    }
    /**
     * 计算手机号对应的分表
     */
    public String calculateTableName(String phone) {
        String cleanPhone = phone.replaceAll("[^0-9]", "");
        int tableIndex = Math.abs(cleanPhone.hashCode()) % 3;
        return "t_user_" + tableIndex;
    }

2,多库多表

application.yml的配置和上面一样

sharding.yaml 配置如下

######################## V2 第二版 实现10个库,每个库有3个表的 分库逻辑############################################################################
dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_0?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_2:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_3:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_3?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_4:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_4?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_5:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_5?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_6:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_6?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_7:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_7?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_8:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_8?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot
  ds_9:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/db_user_9?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: rootroot

rules:
  - !SHARDING
    tables:
      t_stu:
        actualDataNodes: ds_$->{0..9}.t_stu_$->{0..2}
        # 分库策略 - 基于ID取模
        databaseStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: database-sharding
        # 分表策略
        tableStrategy:
          standard:
            shardingColumn: phone_num
            shardingAlgorithmName: table-sharding

      # 教师表 分库分表策略
      t_tea:
        actualDataNodes: ds_$->{0..9}.t_tea_$->{0..2}
        # 分库策略 - 基于ID取模
        databaseStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: database-sharding
        # 分表策略
        tableStrategy:
          standard:
            shardingColumn: phone_num
            shardingAlgorithmName: table-sharding

    shardingAlgorithms:
      # 分库算法取模
      database-sharding:
        type: CLASS_BASED
        props:
          strategy: standard
          algorithmClassName: com.at.ss.shardingsphere.algorithm.DatabaseShardingAlgorithm
      # 分表算法 - 修正算法名称
      table-sharding:
        type: CLASS_BASED
        props:
          strategy: standard
          algorithmClassName: com.at.ss.shardingsphere.algorithm.MobileHashShardingAlgorithm

props:
  sql-show: true

数据库的雪花ID分库算法如下

package com.at.ss.shardingsphere.algorithm;

import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.util.Collection;

/**
 * @author daxigua 基于雪花算法ID的分库算法
 */
@Slf4j
public class DatabaseShardingAlgorithm implements StandardShardingAlgorithm<Long> {

/*  根据雪花算法 ID 取模进行 分库 */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
        Long id = shardingValue.getValue();
        // 雪花算法ID取模分库
        int databaseIndex = Math.abs(id.intValue() % availableTargetNames.size());
        for (String databaseName : availableTargetNames) {
            if (databaseName.endsWith("_" + databaseIndex)) {
                log.info("最终落到数据库是====>>>> :{}" , databaseName);
                return databaseName;
            }
        }
        throw new IllegalArgumentException("未找到对应的分库,计算的库索引: " + databaseIndex);
    }

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
        // 范围查询返回所有的库
        log.info("范围查询返回所有的库:{}" , availableTargetNames);
        return availableTargetNames;
    }
}

分表的算法如下:

package com.at.ss.shardingsphere.algorithm;

import com.at.ss.shardingsphere.utils.CryptoUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.util.Collection;

/**
 * @author daxigua 根据加密后的手机号进行分表
 *  手机号分表算法
 */
@Slf4j
public class MobileHashShardingAlgorithm implements StandardShardingAlgorithm<String> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        String phoneNum = shardingValue.getValue();
        // 基于手机号计算分片表位置
        int tableIndex = CryptoUtils.calculateTableShard(phoneNum);
        for (String tableName : availableTargetNames) {
            if (tableName.endsWith("_" + tableIndex)){
                log.info("手机号 " + phoneNum + " 应该分配到表: " + tableName);
                return tableName;
            }
        }
        throw new IllegalArgumentException("未找到对应的分表,计算的分表索引: " + tableIndex);
    }


    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<String> rangeShardingValue) {
        // 范围查询返回所有的表,确保能查询到所有的表数据
        log.info("范围查询,返回所有的表 :{} " , availableTargetNames);
        return availableTargetNames;
    }
}

3,sql

--------------------------------V1 - 单库多表进行拆分 【水平拆分】----------------------------------------------------
-- 创建数据库 1个库都,每个都创建3个表
CREATE DATABASE db_user;
USE db_user;

-- 创建表
CREATE TABLE t_user_0(
    id        BIGINT PRIMARY KEY,
    uname     VARCHAR(50),
    phone_num VARCHAR(128)
);

CREATE TABLE t_user_1(
    id        BIGINT PRIMARY KEY,
    uname     VARCHAR(50),
    phone_num VARCHAR(128)
);

CREATE TABLE t_user_2(
    id        BIGINT PRIMARY KEY,
    uname     VARCHAR(50),
    phone_num VARCHAR(128)
);


--------------------------------- V2 - 多库多表 3个库,每个库3个表---------------------------------------------------
-- 创建数据库 10个库都,每个都创建3个表,并且根据雪花ID取模进行分库,根据加密后的手机号取hash取模
CREATE DATABASE db_user_0;
CREATE DATABASE db_user_1;
CREATE DATABASE db_user_2;
CREATE DATABASE db_user_3;
CREATE DATABASE db_user_4;
CREATE DATABASE db_user_5;
CREATE DATABASE db_user_6;
CREATE DATABASE db_user_7;
CREATE DATABASE db_user_8;
CREATE DATABASE db_user_9;

-- 创建表
CREATE TABLE t_stu_0(
    id        BIGINT PRIMARY KEY,
    uname     VARCHAR(50),
    phone_num VARCHAR(128),
    subject_name VARCHAR(50)
);

CREATE TABLE t_stu_1(
    id        BIGINT PRIMARY KEY,
    uname     VARCHAR(50),
    phone_num VARCHAR(128),
    subject_name VARCHAR(50)
);

CREATE TABLE t_stu_2(
    id        BIGINT PRIMARY KEY,
    uname     VARCHAR(50),
    phone_num VARCHAR(128),
    subject_name VARCHAR(50)
);


--------------------------------- V2 - 多库多表 10个库,每个库3个表---------------------------------------------------
-- 主要测试相同的根据手机号分库分表策略
CREATE TABLE t_tea_0(
    id           BIGINT PRIMARY KEY,
    uname        VARCHAR(50),
    phone_num    VARCHAR(128),
    subject_name VARCHAR(50)
);

CREATE TABLE t_tea_1(
    id           BIGINT PRIMARY KEY,
    uname        VARCHAR(50),
    phone_num    VARCHAR(128),
    subject_name VARCHAR(50)
);

CREATE TABLE t_tea_2(
    id           BIGINT PRIMARY KEY,
    uname        VARCHAR(50),
    phone_num    VARCHAR(128),
    subject_name VARCHAR(50)
);

问题

1.为什么要分库分表?

数据量大,就分表,并发高就分库

2. 如何选择 sharing key ?

Sharding key :分表的字段,选择 sharing key 最重要的参考因素:我们的业务是如何访问数据的。

假设app的订单ID 【orderId】作为 sharding key 行不行?公司的营收来源于用户,而用户对于订单最高的访问是 app中“我的订单”页面,此时的查询条件:用户ID。但是我们分片的依据是orderId ,没发查询。强行查询的只能是查询所有的分片,合并查询结果,效率低,没法分页。

如果把userId作为sharing key 行不行?此时用户在“我的订单”页面正好可以使用分片键,一个用户对应的订单信息都在一个分片中,

因为分片是使用User ID,此时效率最高。直接去对应分片查询就行了。

可以理解:产品靠什么赚钱,主要用到查询的条件是什么

3.有使用order ID 进行查询的场景怎么办?

网上说是下面这个方法 ,还未验证
在生成订单ID的时候,可以用户ID的后几位作为订单ID的一部分。比如18位的订单号,第15-18位是用户的后4位,此时按照订单ID查询的时候,可以根据订单ID的用户ID找到分片。

遇到的问题:

1,shardingsphere-jdbc 5.5.1 和 spring-boot-starter-parent 2.7.18【spring-boot-starter-parent 3.3.4】写法不一样

2,shardingsphere-jdbc 5.5.1 如果存在多数据源的情况下,mybatis-plus-spring-boot3-starter 3.5.8 如果低于3.5.8会导致多数据源和分库分表数据源冲突不适配
3,shardingsphere-jdbc 5.5.1 的 snakeyaml 自带版本是2.0,项目配置多库多表不支持,后面需要先排除再引入2.2 多依赖

到此这篇关于shardingsphere-jdbc5.5.1分库分表实战的文章就介绍到这了,更多相关shardingsphere-jdbc 分库分表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文