Sharding-JDBC 實現分庫分表

一、概述

  1. 分庫分表介紹:當數據量變大以後,單庫單表已經不能滿足需求。此時就需要進行拆分,拆分緯度分爲垂直拆分和水平拆分。
    • 水平拆分:比如 服務器1 上有 user_0, order_0; 服務器2 上有 user_1, order_1。此時 user_0 和 user_1 一起組成了用戶表。
    • 垂直拆分:用戶表 放在服務器1上,訂單表 放在服務器2上。
  2. 此處模擬使用兩個數據庫,每個數據庫建兩張表。庫的拆分使用 city 字段(按城市存不同的庫),表的拆分使用 id 取模。

二、數據準備

  1. 引入 pom :
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1.tmp</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.12</version>
    </dependency>
    
    <dependency>
        <groupId>com.dangdang</groupId>
        <artifactId>sharding-jdbc-config-spring</artifactId>
        <version>1.5.4.1</version>
    </dependency>
    
  2. 新建兩個數據庫(可以同一個服務器上,也可以兩個服務器上)。
    CREATE DATABASE `xjf_0` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
    CREATE DATABASE `xjf_1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
    
  3. 在兩個庫中分別建如下兩張表:
    CREATE TABLE `user_0` (
      `id` BIGINT(64) NOT NULL,
      `city` VARCHAR(20) NOT NULL,
      `name` varchar(20) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB  DEFAULT CHARSET=utf8;
    
    CREATE TABLE `user_1` (
      `id` BIGINT(64) NOT NULL,
      `city` VARCHAR(20) NOT NULL,
      `name` varchar(20) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB  DEFAULT CHARSET=utf8;
    

三、分庫分表配置

  1. 在 resource 目錄下新建 sharding.xml。配置如下,記得數據庫連接修改爲你自己的:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.dangdang.com/schema/ddframe/rdb
                            http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd
                            ">
        <!--======================================================分庫分表===開始=================================================-->
        <!-- inline 表達式報錯解決:在 Spring 的配置文件中,由於 inline 表達式使用了 Groovy 語法, Groovy 語法的變量符與 Spring 默認佔位符
                                   同爲 ${} ,因此需要在配置文件中增加下面這行來解決解析報錯問題-->
        <context:property-placeholder ignore-unresolvable="true" />
    
        <!-- 第一個數據庫 -->
        <bean id="ds_0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" primary="true">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/xjf_0?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
        </bean>
    
        <!-- 第二個數據庫 -->
        <bean id="ds_1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/xjf_1?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
        </bean>
    
        <!-- 配置分庫規則: 根據 city 來分庫,同一個城市的數據存同一個數據庫中 -->
    
        <rdb:strategy id="databaseShardingStrategy" sharding-columns="city"
                      algorithm-class="com.xjf.sharding.algorithm.MySingleKeyDbShardingAlgorithm" />
    
        <!-- 配置分表規則 -->
        <rdb:strategy id="tableShardingStrategy" sharding-columns="id"
                      algorithm-class="com.xjf.sharding.algorithm.MyUserSingleKeyTableShardingAlgorithm" />
    
        <!-- 配置分庫分表數據源 -->
        <rdb:data-source id="dataSource">
            <rdb:sharding-rule data-sources="ds_0, ds_1">
                <rdb:table-rules>
                    <rdb:table-rule logic-table="user" actual-tables="user_${0..1}"
                                    database-strategy="databaseShardingStrategy" table-strategy="tableShardingStrategy">
                        <!-- 使用 Sharding-JDBC 的默認 ID 生成器,基於雪花算法。-->
                        <rdb:generate-key-column column-name="id" />
                    </rdb:table-rule>
                </rdb:table-rules>
            </rdb:sharding-rule>
        </rdb:data-source>
    
        <!--======================================================分庫分表===結束=================================================-->
    
    
        <!-- 給 MyBatis-Plus 配置數據源 -->
        <bean id="mybatisSqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    </beans>
    
  2. 在啓動類上添加對應註解,引入 sharding.xml。
    @ImportResource(locations = "classpath:sharding.xml")
    @MapperScan("com.xjf.sharding.mapper")
    @SpringBootApplication
    public class ShardingApplication {
        public static void main(String[] args) {
            SpringApplication.run(ShardingApplication.class, args);
        }
    }
    

四、自定義分庫和分表算法

  1. 分庫算法,使用 city 來區分:
    public class MySingleKeyDbShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<String> {
        private static Map<String, List<String>> shardingMap = new ConcurrentHashMap<>();
    
        static {
            shardingMap.put("ds_0", Arrays.asList("上海"));
            shardingMap.put("ds_1", Arrays.asList("杭州"));
        }
    
        @Override
        public String doEqualSharding(Collection<String> collection, ShardingValue<String> shardingValue) {
            for (String each : collection) {
                System.err.println("數據庫:" + each);
                System.err.println("添加數據的城市:" + shardingValue.getValue());
                if (shardingMap.get(each).contains(shardingValue.getValue())){
                    return each;
                }
            }
    
            // 默認保存在數據庫 "ds_0" 中
            return "ds_0";
        }
    
        @Override
        public Collection<String> doInSharding(Collection<String> collection, ShardingValue<String> shardingValue) {
            Collection<String> result = new LinkedHashSet<>(collection.size());
    
            for (String each : collection) {
                if (shardingMap.get(each).contains(shardingValue.getValue())){
                    result.add(each);
                }else {
                    result.add("ds_0");
                }
            }
    
            return result;
        }
    
        @Override
        public Collection<String> doBetweenSharding(Collection<String> collection, ShardingValue<String> shardingValue) {
            Collection<String> result = new LinkedHashSet<>(collection.size());
    
            for (String each : collection) {
                if (shardingMap.get(each).contains(shardingValue.getValue())){
                    result.add(each);
                }else {
                    result.add("ds_0");
                }
            }
    
            return result;
        }
    }
    
  2. 分表算法,id 取模:
    public class MyUserSingleKeyTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {
    
        /**
         * 在 where 使用 = 作爲條件分片鍵
         */
        @Override
        public String doEqualSharding(Collection<String> collection, ShardingValue<Long> shardingValue) {
            System.err.println("運行方法: doEqualSharding");
    
            for (String each : collection) {
                System.err.println("表:" + each);
                System.err.println("shardingValue.getValue: " + shardingValue.getValue());
    
                // 配合測試分庫分表,取模是隻有 2 張表。在測試不分庫只分表時是 4 張表。分別對應使用
    //            if (each.endsWith(shardingValue.getValue() % 4 +"")){
                if (each.endsWith(shardingValue.getValue() % 2 +"")){
                    return each;
                }
            }
    
            throw new IllegalArgumentException();
        }
    
        /**
         * 在 where 使用 in 作爲條件分片鍵
         */
        @Override
        public Collection<String> doInSharding(Collection<String> collection, ShardingValue<Long> shardingValue) {
            System.err.println("運行方法: doInSharding");
    
            Collection<String> result = new LinkedHashSet<>(collection.size());
    
            for (Long value : shardingValue.getValues()) {
                for (String tableName : collection) {
                    if (tableName.endsWith(value % 4 + "")){
                        result.add(tableName);
                    }
                }
            }
    
            return result;
        }
    
        /**
         * 在 where 使用 between 作爲條件分片鍵
         */
        @Override
        public Collection<String> doBetweenSharding(Collection<String> collection, ShardingValue<Long> shardingValue) {
            System.err.println("運行方法: doBetweenSharding");
    
            Collection<String> result = new LinkedHashSet<>(collection.size());
    
            Range<Long> range = shardingValue.getValueRange();
            for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
                for (String each : collection) {
                    if (each.endsWith( i % 4 + "")){
                        result.add(each);
                    }
                }
            }
    
            return result;
        }
    }
    

五、測試

  1. 在 controller 中添加插入數據的方法。id 的生成使用 Sharding-JDBC 默認的分佈式主鍵(基於雪花算法),當程序在多個服務器上時,需要分別爲機器在系統環境變量中設置 sharing-jdbc.default.key.generator.worker.id
    @GetMapping("/add3")
    public String add3(){
    
        for (int i = 0; i < 100; i++) {
            User user = new User();
            // 不設置 ID,在 sharding.xml 配置了 Sharding-JDBC 的默認分佈式主鍵生成,是採用雪花算法實現的。
            // 在類 com.dangdang.ddframe.rdb.sharding.keygen.DefaultKeyGenerator 中
    //            user.setId(Long.valueOf(i));
            // 隨機設置城市
            int random = new Random().nextInt();
            if (random % 2 == 0){
                user.setCity("上海");
            }else {
                user.setCity("杭州");
            }
            user.setName("嘉文四世");
    
            userMapper.insert(user);
        }
    
        return "success";
    }
    
  2. 調用方法,可以在分別的兩個數據庫,四張表中查看數據。其中數據總數加起來剛好 100 條。

看《Spring Cloud微服務入門、實戰與進階》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章