崛起於Springboot2.X + Mysql分庫分表Sharding-JDBC(7)

SpringBoot2.X心法總綱》java

      (本篇博客已於2019-08-28優化更新)mysql

      序言:噹噹開發的一箇中間件,sharding-jdbc官網API,據說能用它也能讀寫分離,但這一篇博客主要講述的是分庫分表的功能。算法

一、Sharding-JDBC

1.1 歷史由來

    初版的分庫分表並非現有的Sharding-JDBC,而是噹噹的一個內部框架ddframe的數據庫模塊,
dd-rdb的其中一項功能就是分庫,沒有分表功能,當時只是作了簡單的SQL解析。後來隨着ddframe被各個
團隊採用,只分庫的需求漸漸不夠用了,而dd-rdb裏面有大量的數據庫ORM相關的東西,爲了使分庫分表這
一核心需求更加純粹,咱們纔將其中的分片的部分單獨提煉出來並命名爲Sharding-JDBC,用於在Java的
JDBC層面提供一層驅動,無縫的處理這方面的需求。

1.2 使用場景

    對於關係型數據庫數據量很大的狀況,須要進行水平拆庫和拆表,這種場景很適合使用Sharding-JDBC。
舉例說明:假設有一億數據的用戶庫,放在 MySQL 數據庫裏查詢性能會比較低,而採用水平拆庫,將其分爲10
個庫,根據用戶的ID模10,這樣數據就能比較平均的分在10個庫中,每一個庫只有1000w記錄,查詢性能會大大提
升。分片策略類型很是多,大體分爲Hash + Mod、Range、Tag 等。Sharding-JDBC還提供了讀寫分離的能
力,用於減輕寫庫的壓力。此外,Sharding-JDBC 能夠用在 JPA 場景中,如 JPA、Hibernate、Mybatis,
Spring JDBC Template 等任何 Java 的 ORM 框架。Java 的 ORM 框架也都是採用 JDBC 與數據庫交互。
這也是咱們選擇在JDBC層,而非選擇一個ORM框架進行開發的緣由。咱們但願 Sharding-JDBC能夠儘可能的兼容
全部的Java數據庫訪問層,而且無縫的接入業務應用

1.3 生態

    1.3.1 JDBC 相關的核心功能,包括分庫分表、讀寫分離、分佈式主鍵等。
    1.3.2 和數據庫相關,但不屬於 JDBC 範疇的,將以插件的形式提供,包括柔性事務、數據庫的 HA、
數據庫 Metadata 管理、配置動態化等。
    1.3.3 業務或使用友好度相關的,包括多租戶、帳戶安全、Spring 自定義命名空間、Yaml 配置等

二、Springboot2.0集成Sharding-JDBC

2.1 建立Springboot2.0.3項目

      勾選Web,Mysql,Mybatis三個模塊。spring

2.2 添加額外的pom依賴

      com.dangdang sharding-jdbc-core 1.5.4 org.projectlombok lombok, 此外,還須要安裝lombok插件,而後從新啓動,因此提早安裝。sql

2.3 添加application.properties

mybatis.config-locations=classpath:mybatis/mybatis-config.xml
#datasource
spring.devtools.remote.restart.enabled=false
#data source1
spring.datasource.test1.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test1.jdbc-url=jdbc:mysql://127.0.0.1:3306/test_msg1
spring.datasource.test1.username=root
spring.datasource.test1.password=root
#data source2
spring.datasource.test2.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test2.jdbc-url=jdbc:mysql://127.0.0.1:3306/test_msg2
spring.datasource.test2.username=root
spring.datasource.test2.password=root

2.4 編寫配置文件java類

import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSourceFactory;
import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy;
import com.springboot2.mjt08.strategy.ModuloDatabaseShardingAlgorithm;
import com.springboot2.mjt08.strategy.ModuloTableShardingAlgorithm;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
//import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

@Configuration
@MapperScan(basePackages = "com.springboot2.mjt08.mapper", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSourceConfig {

    /**
     * 配置數據源0,數據源的名稱最好要有必定的規則,方便配置分庫的計算規則
     *
     * @return
     */
    @Bean(name = "dataSource0")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    public DataSource dataSource0() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置數據源1,數據源的名稱最好要有必定的規則,方便配置分庫的計算規則
     *
     * @return
     */
    @Bean(name = "dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置數據源規則,即將多個數據源交給sharding-jdbc管理,而且能夠設置默認的數據源,
     * 當表沒有配置分庫規則時會使用默認的數據源
     *
     * @param dataSource0
     * @param dataSource1
     * @return
     */
    @Bean
    public DataSourceRule dataSourceRule(@Qualifier("dataSource0") DataSource dataSource0,
                                         @Qualifier("dataSource1") DataSource dataSource1) {
        Map<String, DataSource> dataSourceMap = new HashMap<>(); //設置分庫映射
        dataSourceMap.put("dataSource0", dataSource0);
        dataSourceMap.put("dataSource1", dataSource1);
        return new DataSourceRule(dataSourceMap, "dataSource0");//設置默認庫,兩個庫以上時必須設置默認庫。默認庫的數據源名稱必須是dataSourceMap的key之一
    }

    /**
     * 配置數據源策略和表策略,具體策略須要本身實現
     *
     * @param dataSourceRule
     * @return
     */
    @Bean
    public ShardingRule shardingRule(DataSourceRule dataSourceRule) {
        //具體分庫分表策略
        TableRule orderTableRule = TableRule.builder("t_order")
                .actualTables(Arrays.asList("t_order_0", "t_order_1"))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
                .dataSourceRule(dataSourceRule)
                .build();

        //綁定表策略,在查詢時會使用主表策略計算路由的數據源,所以須要約定綁定表策略的表的規則須要一致,能夠必定程度提升效率
        List<BindingTableRule> bindingTableRules = new ArrayList<BindingTableRule>();
        bindingTableRules.add(new BindingTableRule(Arrays.asList(orderTableRule)));
        return ShardingRule.builder()
                .dataSourceRule(dataSourceRule)
                .tableRules(Arrays.asList(orderTableRule))
                .bindingTableRules(bindingTableRules)
                .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
                .build();
    }

    /**
     * 建立sharding-jdbc的數據源DataSource,MybatisAutoConfiguration會使用此數據源
     *
     * @param shardingRule
     * @return
     * @throws SQLException
     */
    @Bean(name = "dataSource")
    public DataSource shardingDataSource(ShardingRule shardingRule) throws SQLException {
        return ShardingDataSourceFactory.createDataSource(shardingRule);
    }

    /**
     * 須要手動配置事務管理器
     *
     * @param dataSource
     * @return
     */
    @Bean
    public DataSourceTransactionManager transactitonManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
import com.google.common.collect.Range;

import java.util.Collection;
import java.util.LinkedHashSet;

public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {

    @Override
    public String doEqualSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
        for (String each : databaseNames) {
            if (each.endsWith(Long.parseLong(shardingValue.getValue().toString()) % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    @Override
    public Collection<String> doInSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(databaseNames.size());
        for (Long value : shardingValue.getValues()) {
            for (String tableName : databaseNames) {
                if (tableName.endsWith(value % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    @Override
    public Collection<String> doBetweenSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(databaseNames.size());
        Range<Long> range = (Range<Long>) shardingValue.getValueRange();
        for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : databaseNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }

}
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
import com.google.common.collect.Range;

import java.util.Collection;
import java.util.LinkedHashSet;

public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {

    @Override
    public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
        for (String each : tableNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    @Override
    public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        for (Long value : shardingValue.getValues()) {
            for (String tableName : tableNames) {
                if (tableName.endsWith(value % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    @Override
    public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        Range<Long> range = (Range<Long>) shardingValue.getValueRange();
        for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : tableNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }

}

2.5 編寫controller、entity、service、mapper、*.xml

package com.springboot2.mjt08.entity;

import com.springboot2.mjt08.enums.UserSexEnum;
import java.io.Serializable;

public class UserEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    private Long id;
    private Long order_id;
    private Long user_id;
    private String userName;
    private String passWord;
    private UserSexEnum userSex;
    private String nickName;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }


    public Long getOrder_id() {
        return order_id;
    }

    public void setOrder_id(Long order_id) {
        this.order_id = order_id;
    }

    public Long getUser_id() {
        return user_id;
    }

    public void setUser_id(Long user_id) {
        this.user_id = user_id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public UserSexEnum getUserSex() {
        return userSex;
    }

    public void setUserSex(UserSexEnum userSex) {
        this.userSex = userSex;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

}
@Mapper
public interface User1Mapper {

    List<UserEntity> getAll();

    void update(UserEntity user);

    void insert(UserEntity userEntity);
}
public enum UserSexEnum {
    MAN, WOMAN
}
@Slf4j
@Service
public class User1Service {

    @Autowired
    private User1Mapper user1Mapper;

    public List<UserEntity> getUsers() {
        List<UserEntity> users = user1Mapper.getAll();
        return users;
    }

    //@Transactional(value="test1TransactionManager",rollbackFor = Exception.class,timeout=36000)  //說明針對Exception異常也進行回滾,若是不標註,則Spring 默認只有拋出 RuntimeException纔會回滾事務
    public void updateTransactional(UserEntity user) {
        try {
            user1Mapper.insert(user);
            log.error(String.valueOf(user));
        } catch (Exception e) {
            log.error("find exception!");
            throw e;   // 事物方法中,若是使用trycatch捕獲異常後,須要將異常拋出,不然事物不回滾。
        }

    }
}
@RestController
public class UserController {

    @Autowired
    private User1Service user1Service;

    @RequestMapping("/getUsers")
    public List<UserEntity> getUsers() {
        List<UserEntity> users = user1Service.getUsers();
        return users;
    }

    //測試
    @RequestMapping(value = "/update1")
    public String updateTransactional() {

        UserEntity user2 = new UserEntity();
        user2.setId(16L);
        user2.setUser_id(53L);
        user2.setOrder_id(5L);
        user2.setNickName("chengjian");
        user2.setPassWord("123445");
        user2.setUserName("jj");
        user2.setUserSex(UserSexEnum.WOMAN);
        user1Service.updateTransactional(user2);
        return "test";
    }

}

2.6 解析分庫分表代碼

      上面這張圖意思是user_id或者order_id 除以2,整除的話去那個庫或者去那個表數據庫

2.7 建立數據庫和表

test_msg1
         ===t_order_0
         ===t_order_1
         ===user
    test_msg2
         ===t_order_0
         ===t_order_1
         ===user

      建立兩個庫,個人是test_msg1 test_msg2,而後分別創建兩張數據結構相同的表,可是表名不一樣 ,而後在建立一張user表express

CREATE TABLE `t_order_0` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `order_id` varchar(32) DEFAULT NULL COMMENT '順序編號',
  `user_id` varchar(32) DEFAULT NULL COMMENT '用戶編號',
  `userName` varchar(32) DEFAULT NULL COMMENT '用戶名',
  `passWord` varchar(32) DEFAULT NULL COMMENT '密碼',
  `user_sex` varchar(32) DEFAULT NULL,
  `nick_name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8,分別建立四張表, 

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) DEFAULT NULL COMMENT '名字',
  `age` int(11) NOT NULL COMMENT '年齡',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='用戶表'

2.7 運行測試

      最終,經過user_id 決定分庫,order_id 決定那張表apache


三、配置項說明    

3.1 ShardingDataSourceFactory

      數據分片的數據源建立工廠。api

名稱 數據類型 說明
dataSourceMap Map<String, DataSource> 數據源配置
shardingRuleConfig ShardingRuleConfiguration 數據分片配置規則
props (?) Properties 屬性配置
configMap (?) Map<String, Object> 用戶自定義配置

3.2 ShardingRuleConfiguration

3.3 TableRuleConfiguration

      表分片規則配置對象。安全

名稱 數據類型 說明
logicTable String 邏輯表名稱
actualDataNodes (?) String 由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式。缺省表示使用已知數據源與邏輯表名稱生成數據節點。用於廣播表(即每一個庫中都須要一個一樣的表用於關聯查詢,多爲字典表)或只分庫不分表且全部庫的表結構徹底一致的狀況
databaseShardingStrategyConfig (?) ShardingStrategyConfiguration 分庫策略,缺省表示使用默認分庫策略
tableShardingStrategyConfig (?) ShardingStrategyConfiguration 分表策略,缺省表示使用默認分表策略
logicIndex (?) String 邏輯索引名稱,對於分表的Oracle/PostgreSQL數據庫中DROP INDEX XXX語句,須要經過配置邏輯索引名稱定位所執行SQL的真實分表
keyGeneratorColumnName (?) String 自增列名稱,缺省表示不使用自增主鍵生成器
keyGenerator (?) KeyGenerator 自增列值生成器,缺省表示使用默認自增主鍵生成器

3.4 StandardShardingStrategyConfiguration

      ShardingStrategyConfiguration的實現類,用於單分片鍵的標準分片場景。

名稱 數據類型 說明
shardingColumn String 分片列名稱
preciseShardingAlgorithm PreciseShardingAlgorithm 精確分片算法,用於=和IN
rangeShardingAlgorithm (?) RangeShardingAlgorithm 範圍分片算法,用於BETWEEN

3.5 ComplexShardingStrategyConfiguration

      ShardingStrategyConfiguration的實現類,用於多分片鍵的複合分片場景。

名稱 數據類型 說明
shardingColumns String 分片列名稱,多個列以逗號分隔
shardingAlgorithm ComplexKeysShardingAlgorithm 複合分片算法

3.6 InlineShardingStrategyConfiguration

      ShardingStrategyConfiguration的實現類,用於配置行表達式分片策略。

名稱 數據類型 說明
shardingColumn String 分片列名稱
algorithmExpression String 分片算法行表達式,需符合groovy語法,詳情請參考行表達式

3.7 HintShardingStrategyConfiguration

      ShardingStrategyConfiguration的實現類,用於配置Hint方式分片策略。

名稱 數據類型 說明
shardingAlgorithm HintShardingAlgorithm Hint分片算法

3.8 ShardingPropertiesConstant 

      屬性配置項,能夠爲如下屬性。

名稱 數據類型 說明
sql.show (?) boolean 是否開啓SQL顯示,默認值: false
executor.size (?) int 工做線程數量,默認值: CPU核數

      上面配置說明均來自官網Sharding-Sphere

相關文章
相關標籤/搜索