Sharding-JDBC分片策略詳解(二)

童話述說我的結局 發表於 2020-12-04

一、分片策略

   https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sharding/

 

       Sharding-JDBC 中的分片策略有兩個維度:分庫(資料來源分片)策略和分表策略。分庫策略表示資料路由到的物理目標資料來源,分表分片策略表示資料被路由到的目標表。分表策略是依賴於分庫策略的,也就是說要先分庫再分表,當然也可以不分庫只分表。 Mycat 不一樣,Sharding-JDBC 沒有提供內建的分片演算法,而是通過抽象成介面,讓開發者自行實現,這樣可以根據業務實際情況靈活地實現分片。

 1.1、分片策略

包含分片鍵和分片演算法,分片演算法是需要自定義的。可以用於分庫,也可以用於分表。由於分片演算法和業務實現緊密相關,因此Sharding-JDBC並未提供內建分片演算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供介面讓應用開發者自行實現分片演算法。Sharding-JDBC 提供了 5 種分片策略(介面),策略全部繼承自 ShardingStrategy,可以根據情況選擇實現相應的介面。

Sharding-JDBC分片策略詳解(二)

     StandardShardingStrategy

 

標準分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支援。

StandardShardingStrategy只支援單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片演算法。

    • PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。
    • RangeShardingAlgorithm是可選的,用於處理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。

ComplexShardingStrategy

複合分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支援。

ComplexShardingStrategy支援多分片鍵,由於多分片鍵之間的關係複雜,因此Sharding-JDBC並未做過多的封裝,而是直接將分片鍵值組合以及分片操作符交於演算法介面,完全由應用開發者實現,提供最大的靈活度。

InlineShardingStrategy

Inline表示式分片策略。使用Groovy的Inline表示式,提供對SQL語句中的=和IN的分片操作支援。

InlineShardingStrategy只支援單分片鍵,對於簡單的分片演算法,可以通過簡單的配置使用,從而避免繁瑣的Java程式碼開發,如: tuser${user_id % 8} 表示t_user表按照user_id按8取模分成8個表,表名稱為t_user_0到t_user_7。

HintShardingStrategy

通過Hint而非SQL解析的方式分片的策略。

NoneShardingStrategy

不分片的策略。

 1.1.1 、行表示式分片策略

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

 

對應 InlineShardingStrategy 類。只支援單分片鍵,提供對=和 IN 操作的支援。行內表示式的配置比較簡單。

               例如:

${begin..end}表示範圍區間

${[unit1, unit2, unit_x]}表示列舉值

t_user_$->{u_id % 8} 表示 t_user 表根據 u_id 模 8,而分成 8 張表,表名稱為 t_user_0 到 t_user_7。

行表示式中如果出現連續多個${ expression }或$->{ expression }表示式,整個表示式最終的結果將會根據每個子表示式的結果進行笛卡爾組合。

例如,以下行表示式:

${['db1', 'db2']}_table${1..3}

最終會解析為:

db1_table1, db1_table2, db1_table3,

db2_table1, db2_table2, db2_table3

1.1.2 、標準分片策略(StandardShardingStrategy

對應 StandardShardingStrategy 類。

標準分片策略只支援單分片鍵,提供了提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 兩個分片演算法,分別對應於 SQL 語句中的=, IN 和 BETWEEN AND。

如果要使用標準分片策略,必須要實現 PreciseShardingAlgorithm,用來處理=和 IN 的分片。RangeShardingAlgorithm 是可選的。如果沒有實現,SQL 語句會發到所有的資料節點上執行。

 玩起來也特別簡單

測試類

Sharding-JDBC分片策略詳解(二)

 

 

mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.config-location=classpath:mybatis-config.xml
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class test {
    @Resource
    UserService userService;

    /**
     * 先執行插入
     */
    @Test
    public void insert(){

        userService.insert();
    }

    @Test
    public void select(){
        UserInfo userInfo1= userService.getUserInfoByUserId(532299550304501761L);
        System.out.println("------userInfo1:"+userInfo1);

        UserInfo userInfo2= userService.getUserInfoByUserId(532299547905359872L);
        System.out.println("------userInfo2:"+userInfo2);
    }

}
@Configuration
@MapperScan(basePackages = "com.ghy.shardingjdbccostom.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
    @Bean
    @Primary
    public DataSource shardingDataSource() throws SQLException {
        // 配置真實資料來源
        Map<String, DataSource> dataSourceMap = new HashMap<>();

        // 配置第一個資料來源
        DruidDataSource dataSource1 = new DruidDataSource();
        dataSource1.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource1.setUrl("jdbc:mysql://localhost:3306/ds0?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dataSource1.setUsername("root");
        dataSource1.setPassword("root");
        dataSourceMap.put("ds0", dataSource1);

        // 配置第二個資料來源
        DruidDataSource dataSource2 = new DruidDataSource();
        dataSource2.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource2.setUrl("jdbc:mysql://localhost:3306/ds1?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dataSource2.setUsername("root");
        dataSource2.setPassword("root");
        dataSourceMap.put("ds1", dataSource2);

        // 配置Order表規則
        TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("user_info", "ds${0..1}.user_info");

        // 分表策略,使用 Standard 自定義實現,這裡沒有分表,表名固定為user_info
        StandardShardingStrategyConfiguration tableInlineStrategy =
                new StandardShardingStrategyConfiguration("user_id", new TblPreShardAlgo(),new TblRangeShardAlgo());
        orderTableRuleConfig.setTableShardingStrategyConfig(tableInlineStrategy);

        // 分庫策略,使用 Standard 自定義實現
        StandardShardingStrategyConfiguration dataBaseInlineStrategy =new StandardShardingStrategyConfiguration("user_id", new DBShardAlgo());
        orderTableRuleConfig.setDatabaseShardingStrategyConfig(dataBaseInlineStrategy);

        // 新增表配置
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);

        // 獲取資料來源物件
        DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
        return dataSource;
    }

    // 事務管理器
    @Bean
    public DataSourceTransactionManager transactitonManager(DataSource shardingDataSource) {
        return new DataSourceTransactionManager(shardingDataSource);
    }

}

 

public class DBShardAlgo implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        String db_name="ds";
        Long num = preciseShardingValue.getValue()%2;
        db_name = db_name + num;
        for (String each : collection) {
            if (each.equals(db_name)) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

}
public class TblPreShardAlgo implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingColumn) {
        // 不分表
        for (String tbname : availableTargetNames) {
            return tbname ;
        }
        throw new IllegalArgumentException();
    }
}
public class TblRangeShardAlgo implements RangeShardingAlgorithm<Long> {
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> rangeShardingValue) {
        System.out.println("範圍-*-*-*-*-*-*-*-*-*-*-*---------------"+availableTargetNames);
        System.out.println("範圍-*-*-*-*-*-*-*-*-*-*-*---------------"+rangeShardingValue);
        Collection<String> collect = new LinkedHashSet<>();
        Range<Long> valueRange = rangeShardingValue.getValueRange();
        for (Long i = valueRange.lowerEndpoint(); i <= valueRange.upperEndpoint(); i++) {
            for (String each : availableTargetNames) {
                if (each.endsWith(i % availableTargetNames.size() + "")) {
                    collect.add(each);
                }
            }
        }
        //
        return collect;
    }

}

其它業務層和資料庫層程式碼和以前寫法一樣,後面程式碼會發布,就不一一搞了

1.1.3 、複合分片策略

       比如:根據日期和 ID 兩個欄位分片,每個月 3 張表,先根據日期,再根據 ID 取模。對應 ComplexShardingStrategy 類。可以支援等值查詢和範圍查詢。複合分片策略支援多分片鍵,提供了 ComplexKeysShardingAlgorithm,分片演算法需要自己實現。由於多分片鍵之間的關係複雜,因此Sharding-JDBC並未做過多的封裝,而是直接將分片鍵值組合以及分片操作符交於演算法介面,完全由應用開發者實現,提供最大的靈活度

         Sharding -jdbc 在使用分片策略的時候,與分片演算法是成對出現的,每種策略都對應一到兩種分片演算法(不分片策略NoneShardingStrategy除外)

 分庫分表最核心的兩點SQL 路由  、 SQL 改寫 :

SQL 路由:解析原生SQL,確定需要使用哪些資料庫,哪些資料表Route (路由)引擎:為什麼要用Route 引擎呢?在實際查詢當中,資料可能不只是存在一臺MYSQL伺服器上,

SELECT *  FROM t_order WHERE order _id IN(1,3,6)

資料分佈:

ds0.t_order0 (1,3,5,7)

ds1.t_order0(2,4,6)

這個SELECT 查詢就需要走2個database,如果這個SQL原封不動的執行,肯定會報錯(表不存在),Sharding-jdbc 必須要對這個sql進行改寫,將庫名和表名 2個路由加上 

SELECT *  FROM ds0.t_order0  WHERE order _id IN(1,3)

SELECT *  FROM ds0.t_order1  WHERE order _id IN(6)

SQL 改寫:將SQL 按照一定規則,重寫FROM 的資料庫和表名(Route 返回路由決定需要去哪些庫表中執行SQL)

Sharding-JDBC分片策略詳解(二)

 

 

application.properties 配置

配置主要分為三個部分

  1. 配置資料來源
  2. 分庫配置  
  3. 分表配置
# 複合分片
sharding.jdbc.datasource.names=ds0,ds1
 
sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds0.url=jdbc:mysql://127.0.0.1:5306/ds0?useUnicode=yes&characterEncoding=utf8
sharding.jdbc.datasource.ds0.username=root
sharding.jdbc.datasource.ds0.password=root
 
sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds1.url=jdbc:mysql://127.0.0.1:5306/ds1?useUnicode=yes&characterEncoding=utf8
sharding.jdbc.datasource.ds1.username=root
sharding.jdbc.datasource.ds1.password=root
 
# 分庫配置 (行表示式分片策略 + 行表示式分片演算法)
sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=user_id
sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
sharding.jdbc.config.sharding.binding-tables=t_order,t_order_item
 
 
# t_order分表配置 (複合分片策略)
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order$->{0..1}_$->{0..1}
sharding.jdbc.config.sharding.tables.t_order.table-strategy.complex.sharding-columns=user_id,order_id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.complex.algorithm-class-name=ai.yunxi.sharding.config.ComplexShardingAlgorithm
 
# t_order_item分表配置 (複合分片策略)
sharding.jdbc.config.sharding.tables.t_order_item.actual-data-nodes=ds$->{0..1}.t_order_item$->{0..1}_$->{0..1}
# 標準 和 inline 都是單分片鍵 ,複合分片策略可以配置則多分片鍵
sharding.jdbc.config.sharding.tables.t_order_item.table-strategy.complex.sharding-columns=user_id,order_id
# 自定義演算法,讓使用者根據業務自定義實現(開發性介面更靈活方便)
sharding.jdbc.config.sharding.tables.t_order_item.table-strategy.complex.algorithm-class-name=ai.yunxi.sharding.config.ComplexShardingAlgorithm
 
# 定義廣播表
sharding.jdbc.config.sharding.broadcast-tables=t_province
 
sharding.jdbc.config.props.sql.show=true

自定義ComplexShardingAlgorithm

import io.shardingsphere.api.algorithm.sharding.ListShardingValue;
import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
 
public class ComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm {
 
    /**
     *
     * @param collection 在載入配置檔案時,會解析表分片規則。將結果儲存到 collection中,doSharding()引數使用
     * @param shardingValues SQL中對應的
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> collection, Collection<ShardingValue> shardingValues) {
        System.out.println("collection:" + collection + ",shardingValues:" + shardingValues);
 
        Collection<Integer> orderIdValues = getShardingValue(shardingValues, "order_id");
        Collection<Integer> userIdValues = getShardingValue(shardingValues, "user_id");
 
        List<String> shardingSuffix = new ArrayList<>();
 
        // user_id,order_id分片鍵進行分表
        for (Integer userId : userIdValues) {
            for (Integer orderId : orderIdValues) {
                String suffix = userId % 2 + "_" + orderId % 2;
                for (String s : collection) {
                    if (s.endsWith(suffix)) {
                        shardingSuffix.add(s);
                    }
                }
            }
        }
 
        return shardingSuffix;
    }
 
    /**
     * 例如: SELECT * FROM T_ORDER user_id = 100000 AND order_id = 1000009
     * 迴圈 獲取SQL 中 分片鍵列對應的value值
     * @param shardingValues sql 中分片鍵的value值   -> 1000009
     * @param key 分片鍵列名                        -> user_id
     * @return shardingValues 集合                 -> [1000009]
     */
    private Collection<Integer> getShardingValue(Collection<ShardingValue> shardingValues, final String key) {
        Collection<Integer> valueSet = new ArrayList<>();
        Iterator<ShardingValue> iterator = shardingValues.iterator();
        while (iterator.hasNext()) {
            ShardingValue next = iterator.next();
            if (next instanceof ListShardingValue) {
                ListShardingValue value = (ListShardingValue) next;
                // user_id,order_id分片鍵進行分表
                if (value.getColumnName().equals(key)) {
                    return value.getValues();
                }
            }
        }
        return valueSet;
    }
}
 

1.1.4、 Hint 分片策略

https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/usage/sharding/hint/

 

Hint分片策略(HintShardingStrategy)相比於上面幾種分片策略稍有不同,這種分片策略無需配置分片健,分片健值也不再從 SQL中解析,而是由外部指定分片資訊,讓 SQL在指定的分庫、分表中執行。ShardingSphere 通過 Hint API實現指定操作,實際上就是把分片規則tablerule 、databaserule由集中配置變成了個性化配置。

舉個例子,如果我們希望訂單表t_order用 user_id 做分片健進行分庫分表,但是 t_order 表中卻沒有 user_id 這個欄位,這時可以通過 Hint API 在外部手動指定分片健或分片庫。

下邊我們這邊給一條無分片條件的SQL,看如何指定分片健讓它路由到指定庫表。

SELECT * FROM t_order;

 

 使用 Hint分片策略同樣需要自定義,實現 HintShardingAlgorithm 介面並重寫 doSharding()方法。

/**
 * @author xinzhifu
 * @description hit分表演算法
 * @date 2020/11/2 12:06
 */
public class MyTableHintShardingAlgorithm implements HintShardingAlgorithm<String> {

    @Override
    public Collection<String> doSharding(Collection<String> tableNames, HintShardingValue<String> hintShardingValue) {

        Collection<String> result = new ArrayList<>();
        for (String tableName : tableNames) {
            for (String shardingValue : hintShardingValue.getValues()) {
                if (tableName.endsWith(String.valueOf(Long.valueOf(shardingValue) % tableNames.size()))) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
}

自定義完演算法只實現了一部分,還需要在呼叫 SQL 前通過 HintManager 指定分庫、分表資訊。由於每次新增的規則都放在 ThreadLocal 內,所以要先執行 clear() 清除掉上一次的規則,否則會報錯;addDatabaseShardingValue 設定分庫分片健鍵值,addTableShardingValue設定分表分片健鍵值。setMasterRouteOnly 讀寫分離強制讀主庫,避免造成主從複製導致的延遲。

// 清除掉上一次的規則,否則會報錯
HintManager.clear();
// HintManager API 工具類例項
HintManager hintManager = HintManager.getInstance();
// 直接指定對應具體的資料庫
hintManager.addDatabaseShardingValue("ds",0);
// 設定表的分片健
hintManager.addTableShardingValue("t_order" , 0);
hintManager.addTableShardingValue("t_order" , 1);
hintManager.addTableShardingValue("t_order" , 2);

// 在讀寫分離資料庫中,Hint 可以強制讀主庫
hintManager.setMasterRouteOnly();

debug 除錯看到,我們對 t_order 表設定分表分片健鍵值,可以在自定義的演算法 HintShardingValue 引數中成功拿到。

 Sharding-JDBC分片策略詳解(二)

 

 properties 檔案中配置無需再指定分片健,只需自定義的 Hint分片演算法類路徑即可。

# Hint分片演算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.hint.algorithm-class-name=com.ghy.shardingjdbccostom.config.MyTableHintShardingAlgorithm

1.1.5、 不分片策略

 對應 NoneShardingStrategy。不分片的策略(只在一個節點儲存)。

1.2、分片演算法

     建立了分片策略之後,需要進一步實現分片演算法,作為引數傳遞給分片策略。Sharding-JDBC 目前提供 4 種分片演算法。

1.2.1 、精確分片演算法

對應 PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的=與 IN 進行分片的場景。需要配合 StandardShardingStrategy 使用。

1.2.2、 範圍分片演算法

對應 RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的 BETWEEN AND 進行分片的場景。需要配合 StandardShardingStrategy 使用。如果不配置範圍分片演算法,範圍查詢預設會路由到所有節點。

1.2.3 、複合分片演算法

對應 ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較複雜,需要應用開發者自行處理其中的複雜度。需要配合 ComplexShardingStrategy 使用。

 1.2.4 、Hint 分片演算法

對應 HintShardingAlgorithm ,用於處理使用 Hint 行分片的場景。 需要配合 HintShardingStrategy 使用。

二、Sharding-JDBC 總結

https://github.com/apache/shardingsphere 

https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/

2.1、資料來源選擇的解決方案層次

DAO:AbstractRoutingDataSource

ORM:MyBatis 外掛

JDBC:Sharding-JDBC

Proxy:Mycat、Sharding-Proxy

Server:特定資料庫或者版本

2.2、基本特性

定位為輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務。 它使用客戶端直連資料庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全相容 JDBC 和各種 ORM 框架。也就是說,在 maven 的工程裡面,我們使用它的方式是引入依賴,然後進行配置就可以了,不用像 Mycat 一樣獨立執行一個服務,客戶端不需要修改任何一行程式碼,原來是 SSM 連線資料庫,還是 SSM,因為它是支援 MyBatis 的。 mycat 一樣,因為資料來源有多個,所以要配置資料來源,而且分片規則是定義在客戶端的。

 2.3、架構

我們在專案內引入 Sharding-JDBC 的依賴,我們的業務程式碼在運算元據庫的時候,就會通過 Sharding-JDBC 的程式碼連線到資料庫。也就是分庫分表的一些核心動作,比如 SQL 解析,路由,執行,結果處理,都是由它來完成的。它工作在客戶端。

 Sharding-JDBC分片策略詳解(二)

 

 

當然,在 Sharding-Sphere 裡面同樣提供了代理 Proxy 的版本,跟 Mycat 的作用是一樣的。Sharding-Sidecar 是一個 Kubernetes 的雲原生資料庫代理,正在開發中

 

 Sharding-JDBC分片策略詳解(二)

 

 2.4、功能

分庫分表後的幾大問題,Sharding-JDBC 全部解決了:跨庫關聯查詢(ER 表)、排序翻頁計算、分散式事務、全域性主鍵

2.4.1、 全域性 ID

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

 無中心化分散式主鍵(包括 UUID 雪花 SNOWFLAKE)使用 key-generator-column-name 配置,生成了一個 18 位的 ID。

Properties 配置:

spring.shardingsphere.sharding.tables.user_info.key-generator.column=user_id 
spring.shardingsphere.sharding.tables.user_info.key-generator.type=SNOWFLAKE

 keyGeneratorColumnName:指定需要生成 ID 的列

 KeyGenerotorClass:指定生成器類,預設是 DefaultKeyGenerator.java,裡面使用了雪花演算法。

 注意:ID 要用 BIGINT。Mapper.xml insert 語句裡面不能出現主鍵。否則會報錯:Sharding value must implements Comparable

三、分散式事務

我們用到分散式事務的場景有兩種,一種是跨應用(比如微服務場景),一種是單應用多個資料庫(分庫分表的場景),對於程式碼層的使用來說的一樣的。

3.1、事務概述

https://shardingsphere.apache.org/document/current/cn/features/transaction/

  XA 模型的不足:需要鎖定資源 

 SEATA:支援 AT、XA、TCC、SAGA 

 SEATA 是一種全域性事務的框架。 

3.2、兩階段事務-XA

玩起來也特別容易首先新增依賴

<dependency>

<groupId>org.apache.shardingsphere</groupId>

<artifactId>sharding-transaction-xa-core</artifactId>

<version>4.1.1</version>

</dependency>

然後在業務層上加入下面註解就可以了

@ShardingTransactionType(TransactionType.XA)

@Transactional(rollbackFor = Exception.class)

3.3 、柔性事務 Seata

 https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/usage/transaction/

 

https://seata.io/zh-cn/docs/overview/what-is-seata.html

 

https://github.com/seata/seata

 

https://github.com/seata/seata-workshop(官網推薦的搭建例子教程)

 

https://github.com/seata/seata-samples(官網推薦的搭建例子教程)

 

四、 Sharding-JDBC 工作流程

https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/

ShardingSphere 的 3 個產品的資料分片主要流程是完全一致的。 核心由 SQL 解析 => 執行器優化 => SQL 路由 => SQL 改寫 => SQL 執行 => 結果歸併的流程組成。

Sharding-JDBC分片策略詳解(二)

 

 

 

4.1、SQL 解析

分為詞法解析和語法解析。 先通過詞法解析器將 SQL 拆分為一個個不可再分的單詞。再使用語法解析器對 SQL 進行理解,並最終提煉出解析上下文。 解析上下文包括表、選擇項、排序項、分組項、聚合函式、分頁資訊、查詢條件以及可能需要修改的佔位符的標記。目前常見的 SQL 解析器主要有 fdb,jsqlparser和 Druid。Sharding-JDBC1.4.x 之前的版本使用 Druid 作為 SQL 解析器。從 1.5.x 版本開始,Sharding-JDBC 採用完全自研的 SQL 解析引擎。

4.2、執行器優化

合併和優化分片條件,如 OR 等。

4.3、SQL 路由

根據解析上下文匹配使用者配置的分片策略,並生成路由路徑。目前支援分片路由和廣播路由。

Sharding-JDBC分片策略詳解(二)

 

 

       SQL 路由是根據分片規則配置以及解析上下文中的分片條件,將 SQL 定位至真正的資料來源。它又分為直接路由、簡單路由和笛卡爾積路由。

       直接路由,使用 Hint 方式。

       Binding 表是指使用同樣的分片鍵和分片規則的一組表,也就是說任何情況下, Binding 表的分片結果應與主表一致。例如:order 表和 order_item 表,都根據 order_id 分片,結果應是 order_1 與 order_item_1 成對出現。這樣的關聯查詢和單表查詢複雜度和效能相當。如果分片條件不是等於,而是 BETWEEN 或 IN,則路由結果不一定落入單庫(表),因此一條邏輯 SQL 最終可能拆分為多條 SQL 語句。

       笛卡爾積查詢最為複雜,因為無法根據 Binding 關係定位分片規則的一致性,所以非 Binding 表的關聯查詢需要拆解為笛卡爾積組合執行。查詢效能較低,而且資料庫連線數較高,需謹慎使用。

 4.4、SQL 改寫

例如:將邏輯表名稱改成真實表名稱,優化分頁查詢等。

4.5 、SQL 執行

 因為可能連結到多個真實資料來源, Sharding -JDBC 將採用多執行緒併發執行 SQL。

4.6、結果歸併

例如資料的組裝、分頁、排序等等。

 

 Sharding-JDBC中所有的程式碼:https://github.com/ljx958720/springSphere.git