sharding-jdbc分庫分表規則(1)-單表查詢

前言

當數據量到達必定數量級的時候,通常都會考慮分庫分表。sharding-jdbc是一個開源的客戶端分庫分表基礎類庫,以一個jar包的形式提供,基於原生的JDBC驅動進行加強,基本可以無縫整合舊代碼,很是的便捷。本小節以一個最簡單的單表查詢淺析概要流程。html

建庫建表

ds_jdbc_0 t_order_0 , t_order_1
ds_jdbc_1 t_order_0 , t_order_1



訂單表邏輯語名: 
CREATE TABLE IF NOT EXISTS t_order (order_id INT NOT NULL, user_id INT NOT NULL, status VARCHAR(50), PRIMARY KEY (order_id))mysql

配置

爲簡單起見,使用基本的jdbc進行操做,最精簡的代碼以下:算法

public final class SingleSelect {

    public static void main(final String[] args) throws SQLException {
        DataSource dataSource = getOrderShardingDataSource();
        printSingleSelect(dataSource);
    }

    private static ShardingDataSource getOrderShardingDataSource() {
        DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap());
        TableRule orderTableRule = TableRule.builder("t_order").actualTables(Arrays.asList("t_order_0", "t_order_1")).dataSourceRule(dataSourceRule).build();
        ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule).tableRules(Arrays.asList(orderTableRule))
                .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())).build();
        return new ShardingDataSource(shardingRule);
    }

    private static void printSingleSelect(final DataSource dataSource) throws SQLException {
        String sql = "SELECT * FROM t_order where user_id=? and order_id=?";
        try (
            Connection conn = dataSource.getConnection();
            PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
            preparedStatement.setInt(1, 10);
            preparedStatement.setInt(2, 1001);
            try (ResultSet rs = preparedStatement.executeQuery()) {
                while (rs.next()) {
                    System.out.println(rs.getInt(1));
                    System.out.println(rs.getInt(2));
                    System.out.println(rs.getInt(3));
                }
            }
        }
    }
    private static DataSource createDataSource(final String dataSourceName) {
        BasicDataSource result = new BasicDataSource();
        result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
        result.setUrl(String.format("jdbc:mysql://127.0.0.1:3306/%s", dataSourceName));
        result.setUsername("root");
        result.setPassword("123456");
        return result;
    }
    private static Map<String, DataSource> createDataSourceMap() {
        Map<String, DataSource> result = new HashMap<>(2);
        result.put("ds_jdbc_0", createDataSource("ds_jdbc_0"));
        result.put("ds_jdbc_1", createDataSource("ds_jdbc_1"));
        return result;
    }
}

分庫分表最主要有幾個配置: 
1. 有多少個數據源 
2. 每張表的邏輯表名和全部物理表名 
3. 用什麼列進行分庫以及分庫算法 
4. 用什麼列進行分表以及分表算法sql

本示例定義了兩個數據源: ds_jdbc_0 和 ds_jdbc_1,定義了邏輯表:t_order,以及物理表:t_order_0 和 t_order_0。採用 user_id列進行分庫,order_id列進行分表。一切準備就緒,咱們的目標是執行以下的語句:併發

SELECT * FROM t_order where user_id=10 and order_id=1001
  • 1

咱們想實現以下的目標: 
分庫: 
user_id % 2 = 0 的數據存儲到 ds_jdbc_0 ,爲1的數據存儲到 ds_jdbc_1 
分表: 
order_id % 2 = 0 的數據存儲到 t_order_0 ,爲1的數據存儲到 t_order_1ide

這屬於業務的範疇,咱們必須清楚告知sharding-jdbc咱們的意圖,因此要提供分庫分表策略類, 
看看分庫策略類:ui

public final class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Integer> {

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

    @Override
    public Collection<String> doInSharding(final Collection<String> dataSourceNames, final ShardingValue<Integer> shardingValue) {
        return null;
    }

    @Override
    public Collection<String> doBetweenSharding(final Collection<String> dataSourceNames, final ShardingValue<Integer> shardingValue) {
        return null;
    }
}

因爲咱們使用了 = 號條件進行查詢,因此只實現了 doEqualSharding 這個方法。代碼很是簡單,參數dataSourceNames的值爲[ds_jdbc_0 , ds_jdbc_1],而shardingValue在執行的時候能夠獲取到 user_id=10這個值,在doEqualSharding 中,咱們本身根據user_id的值返回路由的庫的名稱。spa

接下來看看分表策略類:線程

public final class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {

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

    @Override
    public Collection<String> doInSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        return null;
    }

    @Override
    public Collection<String> doBetweenSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        return null;
    }
}

同樣實現了 doEqualSharding這個方法,由於咱們的條件中有 order_id=1001,在執行回調時,tableNames的值爲[t_order_0 ,t_order_1 ],咱們能夠決定如何路由到真實的表名。code

流程淺析

先來看個基本的流程圖:

這裏寫圖片描述

咱們再來看看咱們的目標sql語句:

SELECT * FROM t_order where user_id=10 and order_id=1001
  • 1
  1. 經過sql解析發現有一張邏輯表名稱:t_order

    發現兩個條件: t_order.userId = 10 和 t_order.order_id = 1001

  2. 經過 t_order 找到對應的表配置規則 TableRule,這裏定義了兩個物理表: t_order_0和t_order_1

  3. 根據 TableRule找出目標數據源集合

    經過TableRule找到DatabaseShardingStrategy,獲得分庫列:user_id 
    經過t_order 和 user_id聯合爲key從條件中查找,找到了t_order.userId = 10, 
    再結合參數值(10),獲得分片值 
    ShardingValue(logicTableName=t_order, columnName=user_id, value=10), 
    調用自定義分庫策略類(傳輸[ds_jdbc_0 , ds_jdbc_1],ShardingValue),獲得最終的數據源名稱集合[ds_jdbc_0]

  4. 根據TableRule和數據源 [ds_jdbc_0] 找到物理表

    經過TableShardingStrategy找到表的分片列: order_id 
    經過t_order 和 order_id聯合爲key從條件中查找,找到了order_id=1001,再結合參數值(1001),獲得分片值 
    ShardingValue(logicTableName=t_order, columnName=order_id, value=1001), 
    調用自定義分表策略類(傳輸[t_order_0,t_order_1],ShardingValue),獲得[ds_jdbc_0]下的最終物理表集合[t_order_1]

  5. 根據數據源和物理表,獲得 DataNode的集合

    根據獲得的[ds_jdbc_0] 和 [t_order_1],構建 DataNode集合,每個DataNode表示 xx庫.xx表,此示例下獲得一個DataNode實體: [ds_jdbc_0].[t_order_1]

  6. 根據 DataNode生成TableUnits集合

    TableUnit由 邏輯表,物理庫,物理表 三個字段組成, 
    此示例爲: t_order 、ds_jdbc_0 、t_order_1

  7. SQL重寫

    構建重寫引擎SQLRewriteEngine,根據TableUnits生成對應最終的sql語句執行單元(替換成最終表名),獲得執行單元集合(ExecutionUnits),一個執行單元表示在哪一個庫,執行什麼sql語句

  8. ExecutionUnits轉換爲PreparedStatement,最後又轉爲PreparedStatementUnit

  9. 線程池併發執行PreparedStatementUnit,最後再合併結果返回

多庫單表又如何

經過上面的分析,咱們已經知道了單庫單表的基本查詢邏輯,如今把sql簡單調整爲:

SELECT * FROM t_order where order_id=1001
  • 1

此次,咱們發現搜索條件並無分庫鍵,這時候,引擎並不會調用分庫策略類,直接認定目標庫爲[ds_jdbc_0,ds_jdbc_1],而分表的邏輯是不變的,既然目標庫有兩個,後面生成的DataNode,TableUnits,PreparedStatementUnit 將是之前數量的兩倍,因此這回,引擎最終將會發起多個sql語句的併發執行,併合並最終的結果再返回。

總結

以上基於一個最簡單的查詢拆解了基本的流程,固然,sql解析的細節仍是很複雜的,但不是本文關注的重點,本文主要關注一個簡單的sql語句,在sharding-jdbc下是如何達到分庫分表的目的的,後續再分析更多的sql語句的執行。

參考

http://shardingjdbc.io/index_zh.html

相關文章
相關標籤/搜索