三、Sharding-JDBC 執行原理

Sharding-JDBC 執行原理

本文通過 黑馬 sharding-jdbc 視頻教程 摘錄

1、基本概念

1.1 邏輯表

  水平拆分的數據包的總稱。例如:訂單數據表根據主鍵尾數拆分爲 10 張表,分別是 t_order_0、t_order_1 到 t_order_9,他們的邏輯表名 爲 t_order。

# 指定t_order表的數據分佈情況配置數據節點
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes = m1.t_order_$->{1..2}
    /**
     * 根據id查詢訂單
     * @param orderIds id集合
     * @return
     */
    @Select("<script>" +
            "select * from t_order o " +
            "where o.order_id in " +
            "<foreach collection = 'orderIds' open = '(' separator = ',' close = ')' item = 'id'> " +
            "#{id}" +
            "</foreach>" +
            "</script>")
    List<Map> selectOrderByIds(@Param("orderIds") List<Long> orderIds);

  如以上的 t_order 爲邏輯表。

1.2 真實表

  在分片的數據庫中真實存在的物理表。即上個示例中的 t_order_0 到 t_order_9。

1.3 數據節點

  數據分片的最小物理單元。由數據源名稱和數據表組成,例如:ds_0.t_order_0。

1.4 綁定表

  指分片規則一致的主表和子表。例如:t_order 表中 t_order_item 表,均按照 order_id 分片,綁定表之間的分區鍵完全相同,則此兩張表互爲綁定表關係。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢料率將大大提升。舉例說明,如果 SQL 爲:

SELECT i.* FROM t_order o JOIN t_order_item ON o.order_id = i.order_id WHERE o.order_id in(10, 11)

  在不配置綁定表關係時,假設分片鍵 order_id 將數值 10 路由至第 0 片,將數值 11 路由至第 1 片,那麼路由後的 SQL 應該爲 4 條,它們呈現的笛卡爾積:

SELECT i.* FROM t_order_0 JOIN t_order_item_0 ON o.order_id = i.order_id WHERE o.order_id in (10, 11)

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 ON o.order_id WHERE o.order_id in (10, 11)

SELECT i.* FROM t_order_1 JOIN t_order_item_0 ON o.order_id = i.order_id WHERE o.order_id in (10, 11)

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 ON o.order_id WHERE o.order_id in (10, 11)

  在配置綁定表關係後,路由的 SQL 應該爲 2 條,比如,綁定後,查詢 t_order_0 時,則必然相關數據一定在 t_order_item_0 中:

SELECT i.* FROM t_order_0 JOIN t_order_item_0 ON o.order_id = i.order_id WHERE o.order_id in (10, 11)

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 ON o.order_id WHERE o.order_id in (10, 11)

1.5 廣播表

  指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。使用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表。

1.6 分片鍵

  用於分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段。例:將訂單表中的訂單主鍵的尾數取模分片,則訂單主鍵爲分片字段。SQL 中如果無分片字段,將執行全路由,性能較差。除了對單分片字段的支持,Sharding-jdbc 也支持根據多個字段進行分片。

1.7 分片算法

  通過分片算法將數據分片,支持通過 =、BETWEEN 和 IN 分片。分片算法需要應用方開發者自行實現,可實現的靈活度非常高。包括:精確分片算法、範圍分片算法,符合分片算法等。例如:where order_id = ? 將採用精確分片算法,where order_id in (?, ? ,?) 將採用精確分片算法,where order_id BETWEEN ? and ? 將採用範圍分片算法,符合分片算法用於分片有多個符合情況。

1.8 分片策略

  包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的分片鍵 + 分片算法,也就是分片策略。內置的分片策略大致可以分爲尾數取模、哈希、範圍、標籤、時間等。由用戶配置的分片策略則更加靈活,常用的使用行表達式配置分片策略,它採用 Groovy 表達式表示,如 t_user_${u_id % 8} 表示 t_user 表根據 u_id 模 8,而分爲 8 張表,表名稱爲 t_user_0 到 t_user_7。

1.9 自增主鍵生成策略

  通過在客戶端生成自增主鍵替代以數據庫原生自增主鍵的方式,做到分佈式主鍵無重複。

2、SQL 解析

  當 Sharding-JDBC 接受到一條 SQL 語句時,會陸續執行 SQL 解析 => 查詢優化 => SQL 路由 => SQL 改寫 => SQL 執行 => 結果歸併,最終返回執行結果。

在這裏插入圖片描述
  SQL 解析過程分爲詞法解析語法解析。詞法解析器用於將 SQL 拆解爲不可再分的原子符號,成爲 Token。並根據不同數據庫方言所提供的字典,將其歸類爲關鍵字,表達式,字面量和操作等。再使用語法解析器將 SQL 轉換爲抽象語法樹。

  例如,以下 SQL:

SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18

  解析之後的抽象語法樹見下圖:
在這裏插入圖片描述
  爲了便於理解,抽象語法樹中的關鍵字的 Token 用綠色表示,變量的 Token 用紅色表示,灰色表示可以進一步拆分。

  最後,通過對抽象語法樹的遍歷去提取分片所需的上下文,並標記有可能需要 SQL 改寫(後邊介紹)的位置。供分片使用的解析上下文包含查詢選擇項(Select Items)、表信息(Table)、分片條件(Sharding Condition)、自增主鍵信息(Auto increment Primary Key)、排序信息(Order by)、分組信息(Group By)以及分頁信息(Group By)以及分頁信息(Limit、Rownum、Top)。

3、 SQL 路由

  SQL 路由就是把針對邏輯表的數據操作印射到數據節點操作的過程。

  根據解析上下文匹配數據庫和表的分片策略,並生成路由路徑。對於攜帶分片鍵的 SQL,根據分片鍵操作符不同可以劃分爲單片路由(分片鍵的操作符是等號)、多片路由(分片鍵的操作符是 IN)和範圍路由(分片鍵的操作符是 BETWEEN),不攜帶分片鍵的 SQL 則採用廣播路由。根據分片鍵進行路路由的場景可分爲直接路由、標準路由、笛卡爾路由等。

3.1 標準路由

  標準路由是 Sharding-JDBC 最爲推薦使用的分片方式,它的使用範圍是不包含關聯查詢或僅包含綁定表之間的關聯查詢的 SQL。當分片運算符是等於號時,路由結果將落入單庫(表),當分片運算符是 BETWEEN 或 IN 時,則路由結果不一定落入唯一的庫(表),因此一條邏輯 SQL 最終可能被拆分爲多條用於執行的真實 SQL。舉例說明,如果按照 order_id 的奇數和偶數進行數據分片,一個單表查詢的 SQL 如下:

SELECT * FROM t_order WHERE order_id IN (1, 2);

  那麼路由的結果應爲:

SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);

  綁定表的關聯查詢與單表查詢複雜度和性能相當。舉例說明,如果一個包含綁定表的關聯查詢的 SQL 如下:

SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

  那麼路由的結果應爲:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

  可以看到, SQL 拆分的數目與單表是一致的。

3.2 笛卡爾路由

  笛卡爾路由是最複雜的情況,它無法根據綁定表的關係定位分片規則,因此非綁定表之間的關聯查詢需要拆解爲笛卡爾積組合執行。如果上個示例中的 SQL 並未配置綁定表關係,那麼路由的結果應爲:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

笛卡爾路由查詢性能較低,需謹慎使用。

3.3 全庫表路由

  對於不攜帶分片鍵的 SQL,則採取廣播路由的方式。根據 SQL 類型又可以劃分爲全庫表路由、全庫路由、全實例路由、單播路由和阻斷路由這 5 鍾類型。其中全庫表路由用於處理對數據庫中其邏輯表相關的所有真實表的操作,主要包括不帶分片鍵的 DQL(數據查詢)和 DML(數據操縱),以及 DDL(數據定義)等。例如:

SELECT * FROM t_order WHERE good_prority IN (1, 10);

  則會遍歷所有數據庫中的所有表,逐一匹配邏輯表和真實表名,能匹配得上則執行。路由後成爲:

SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);

4、 SQL 改寫

  工程師面向邏輯表書寫的 SQL,並不能夠直接在真實的數據庫彙總執行,SQL 改寫用於將邏輯 SQL 改寫爲在真實數據庫彙總可以正確執行的 SQL。

  如一個簡單的例子,若邏輯 SQL 爲:

SELECT order_id FROM t_order WHERE order_id = 1;

  假設該 SQL 配置分片鍵 order_id,並且 order_id = 1 的情況,將路由至分片表 1。那麼改寫之後的 SQL 應該爲:

SELECT order_id FROM t_order_1 WHERE order_id = 1;

  再比如,Sharding-JDBC 需要在結果歸併時獲取相應數據,但該數據並未能通過查詢的 SQL 返回。這種情況主要是針對 GROUP BY 和 ORDER BY。結果歸併時,需要根據 GROUP BY 和 ORDER BY 的字段項進行分組和排序,但如果原始 SQL 的選擇項中若並未包含分組項或排序項,則需要對原始 SQL 進行改寫。先看一下原始 SQL 中帶有結果歸併所需信息的場景:

SELECT order_id, user_id FROM t_order ORDER BY user_id;

  由於使用 user_id 進行排序,在結果歸併中需要能夠獲取到 user_id 的數據,而上面的 SQL 是能夠獲取到 user_id 數據的,因此無需補列。

  如果選擇項中不包含結果歸併時所需的列,則需要進行補列,如以下 SQL:

SELECT order_id FROM t_order ORDER BY user_id ;

  由於原始 SQL 中並不包含需要在結果歸併中需要獲取的 user_id,因此需要對 SQL 進行補列改寫。補列之後的 SQL 是:

SELECT order_id, user_id AS ORDER BY_DERIVED_0 FROM t_order ORDER BY user_id;

5、 SQL 執行

  Sharding-JDBC 採用一套自動化的執行引擎,負責將路由和改寫完成之後的真實 SQL 安全且高效發送奧底層數據源執行。它不是簡單地將 SQL 通過 JDBC 直接發送到數據源執行;也並非直接將執行請求放入到線程池去併發執行。它更關注平衡數據源連接創建以及內存佔用鎖產生的消耗,以及最大限度地合理利用併發等問題。執行引擎的目標是自動化的平衡資源控制與執行效率,他能在以下兩種模式自適應切換:

5.1內存限制模式

  使用此模式的前提是,Sharding-JDBC 對一次操作所耗費的數據庫連接數量不做限制。如果執行的 SQL 需要對某數據庫實例中的 200 張表做操作,則對每張表創建一個新的數據庫連接,並通過多線程的方式併發處理,以達成執行效率最大化。

5.2 連接限制模式

  使用此模式的前提是,Sharding-JDBC 嚴格控制對一次操作所耗費的數據庫連接數量。如果實際執行的 SQL 需要對某數據庫實例中的 200 張表做操作,那麼只會創建唯一的數據庫連接,並對其 200 張表串行處理。如果一次操作中的分片散落在不同的數據庫,仍然採用多線程處理對不同庫的操作,但每個庫的每次操作仍然只創建一個唯一的數據庫連接。

  內存限制模式適用於 OLAP 操作,可以通過放寬對數據庫連接的限制提升系統的吞吐量;連接限制模式適用於 OLTP 操作,OLTP 通常帶有分片鍵,會路由到單一的分片,因此嚴格控制數據庫連接,以保證在線系統數據庫資源能夠被更多的應用所使用,是明智的選擇。

6、 結果歸併

  將從各個數據節點獲取的多數據結果集,組合成爲一個結果集並正確的返回至請求客戶端,稱爲結果歸併。

  Sharding-JDBC 支持的結果歸併從功能上可分爲遍歷排序分組分頁聚合 5 種類型,它們是組合而非互斥的關係。

  歸併引擎的整體結構劃分如下圖:
在這裏插入圖片描述
  結果歸併從藉故偶劃分可分爲流式歸併內存歸併裝飾者歸併。流式歸併和內存歸併時互斥的,裝飾者歸並可以在流式歸併和內存歸併之上做進一步的處理。

6.1 內存歸併

  內存歸併很容易理解,他是將所有分片結果集的數據都遍歷並存儲在內存中,再通過統一的分組、排序以及聚合等計算之後,再將其封裝成爲逐條訪問的數據結果集返回。這種方式及其消耗內存,因此對服務器的內存要求很高。

6.2 流式歸併

  流式歸併是指每一次從數據庫結果集中獲取到的數據,都能夠通過遊標逐條火球的方式範湖正確的單條數據,它與數據庫原生的返回結果集的方式最爲契合。我們用的最多的就是這種方式。

  下邊舉例說明排序歸併的過程,如下圖是一個通過分數進行排序的示例圖,它採用流式歸併方式。圖中展示了 3 張表返回的數據結果,每個數據結果集已經根據分數排序完畢,但是 3 個數據結果集之間是無序的。將 3 個數據結果集的當前遊標指向的數據值進行排序,並放入優先隊列,t_score_0 的第一個數據值最大,t_score_2 的第一個數據值次之,t_score_1 的第一個數據值最小,因此優先級隊列根據 t_score_0,t_score_2 和 t_score_1 的方式排序隊列。

在這裏插入圖片描述
  下圖則展現了進行 next 調用的時候,排序歸併是如何進行的。 通過圖中我們可以看到,當進行第一次 next 調用時,排在隊列首位的 t_score_0 將會被彈出隊列,並且將當前遊標指向的數據值(也就是 100)返回至查詢客戶端,並且將遊標下移一位之後,重新放入優先級隊列。 而優先級隊列也會根據 t_score_0 的當前數據結果集指向遊標的數據值(這裏是 90)進行排序,根據當前數值,t_score_0 排列在隊列的最後一位。 之前隊列中排名第二的 t_score_2 的數據結果集則自動排在了隊列首位。

  在進行第二次 next 時,只需要將目前排列在隊列首位的 t_score_2 彈出隊列,並且將其數據結果集遊標指向的值返回至客戶端,並下移遊標,繼續加入隊列排隊,以此類推。 當一個結果集中已經沒有數據了,則無需再次加入隊列。

在這裏插入圖片描述

  可以看到,對於每個數據結果集中的數據有序,而多數據結果集整體無序的情況下,ShardingSphere 無需將所有的數據都加載至內存即可排序。 它使用的是流式歸併的方式,每次 next 僅獲取唯一正確的一條數據,極大的節省了內存的消耗。

6.3 裝飾者歸併

  裝飾者歸併是對所有的結果集歸併進行統一的功能增強,比如歸併時需要聚合 SUM 前,在進行聚合計算前,都會通過內存歸併或流式歸併查詢出結果集。因此,聚合歸併是在之前介紹的歸併類型之上追加的歸併能力,即裝飾者模式。

7、總結

  通過以上內容介紹,相信大家已經瞭解到 Sharding-JDBC 基礎概念、核心功能以及執行原理。

  基本概念:邏輯表,真實表,數據節點,綁定表,廣播表,分片鍵,分片算法,分片策略,主鍵生成策略

  核心功能:數據分片,讀寫分離

  SQL 解析 => 查詢優化 => SQL 路由 => SQL 改寫 => SQL 執行 => 結果歸併

  接下來通過一個個 demo,來演示 Sharding-JDBC 實際使用方法。

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