學習Sharding-JDBC,這些基本概念你不搞懂?

前言

在瞭解Sharding-JDBC的執行原理前,需要了解以下概念:

邏輯表

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

真實表

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

數據節點

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

綁定表

指分片規則一致的主表和子表。例如:torder 表和 torderitem 表,均按照 orderid 分片,綁定表之間的分區鍵完全相同,則此兩張表互爲綁定表關係。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。

舉例說明,如果SQL爲:

SELECT i.* FROM t_order o JOIN t_order_item i 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 o JOIN t_order_item_0 i 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 i 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_0 i 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 i ON o.order_id=i.order_id WHERE o.order_id in(10, 11);

配置綁定表關係後,路由的SQL應該爲2條:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i 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 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

廣播表

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

分片鍵

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

分片算法

通過分片算法將數據分片,支持通過 =****、>=*、*<=、>、<、****BETWEEN 和 IN 分片。分片算法需要應用方開發者自行實現**,可實現的靈活度非常高。

目前提供4種分片算法。由於分片算法和業務實現緊密相關,因此並未提供內置****分片算法*,而是通過*分片****策略將各種場景提煉出來**,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。

  • 精確分片算法

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

  • 範圍分片算法

對應 RangeShardingAlgorithm,用於處理使用單一鍵作爲分片鍵的 BETWEEN AND、>、<、>=、<=進行分片的場景。需要配合 StandardShardingStrategy 使用。

  • 複合分片算法

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

  • Hint分片算法

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

分片策略

包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵****+分片算法,也就是分片策略。目前提供 5 種分片策略。

  • 標準分片策略

對應 StandardShardingStrategy。提供對 SQL語句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。StandardShardingStrategy 只支持單分片鍵,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 兩個分片算法。

PreciseShardingAlgorithm 是必選的,用於處理 = 和 IN 的分片

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

  • 複合分片策略

對應 ComplexShardingStrategy。複合分片策略。提供對 SQL 語句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。ComplexShardingStrategy 支持多分片鍵,由於多分片鍵之間的關係複雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。

  • 行表達式分片策略

對應 InlineShardingStrategy。使用Groovy的表達式,提供對 SQL 語句中的 = 和 IN 的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的 Java 代碼開發,如: tuser$->{uid % 8} 表示tuser表根據uid模8,而分成8張表,表名稱爲 tuser0 到 tuser_7。

  • Hint分片策略

對應 HintShardingStrategy。通過 Hint 指定分片值而非從 SQL 中提取分片值的方式進行分片的策略。

  • 不分片策略

對應 NoneShardingStrategy。不分片的策略。

自增 主鍵生成策略

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

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)以及分頁信息(Limit、Rownum、Top)。

SQL路由

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

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

根據分片鍵進行路由的場景可分爲直接路由、標準路由、笛卡爾路由等。

標準路由

標準路由是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拆分的數目與單表是一致的。

笛卡爾路由

笛卡爾路由是最複雜的情況,它無法根據綁定表的關係定位分片規則,因此非綁定表之間的關聯查詢需要拆解爲笛卡爾積組合執行

如果上個示例中的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);

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

全庫表路由

對於不攜帶分片鍵的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);

SQL改寫

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

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

SELECT order_id FROM t_order WHERE order_id=1;

假設該SQL配置分片鍵orderid,並且orderid=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;

由於使用userid進行排序,在結果歸併中需要能夠獲取到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;

SQL執行

Sharding-JDBC採用一套自動化的執行引擎,負責將路由和改寫完成之後的真實SQL安全且高效發送到底層數據源執行。它不是簡單地將SQL通過JDBC直接發送至數據源執行;也並非直接將執行請求放入線程池去併發執行

結果歸併

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

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

歸併引擎的整體結構劃分如下圖:

結果歸併從結構劃分可分爲流式歸併、內存歸併和裝飾者歸併。流式歸併和內存歸併是互斥的,裝飾者歸併可以在流式歸併和內存歸併之上做進一步的處理

總結

今天老顧介紹了Sharding-JDBC基礎概念、核心功能以及執行原理

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

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

後面的文章,老顧將介紹Sharding-jdbc的實戰使用,謝謝!!!

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