1. Sharding-Sphere

Sharding-JDBC 最早是噹噹網內部使用的一款分庫分表框架,到2017年的時候才開始對外開源,這幾年在大量社區貢獻者的不斷迭代下,功能也逐漸完善,現已更名為 ShardingSphere ,2020年4⽉16⽇正式成為 Apache 軟件基⾦會的頂級項⽬。

隨着版本的不斷更迭 ShardingSphere 的核心功能也變得多元化起來。如下圖,ShardingSphere生態包含三款開源分佈式數據庫中間件解決方案,Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar。

ldapsearch 分頁查詢_#java

Apache ShardingSphere 5.x 版本開始致力於提供可插拔架構,項目的功能組件能夠靈活的以可插拔的方式進行擴展。 目前,數據分片、讀寫分離、數據加密、影子庫壓測等功能,以及對 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 與協議的支持,均通過插件的方式織入項目。 開發者能夠像使用積木一樣定製屬於自己的獨特系統。Apache ShardingSphere 目前已提供數十個 SPI 作為系統的擴展點,而且仍在不斷增加中。

如圖是Sharding-Sphere的整體架構。

ldapsearch 分頁查詢_#database_02

1.1 Sharding-JDBC

Sharding-JDBC是比較常用的一個組件,它定位的是一個增強版的JDBC驅動,簡單來説就是在應用端來完成數據庫分庫分表相關的路由和分片操作,也是我們本次重點去分析的組件。

我們在項目內引入Sharding-JDBC的依賴,我們的業務代碼在操作數據庫的時候,就會通過Sharding-JDBC的代碼連接到數據庫。也就是分庫分表的一些核心動作,比如SQL解析,路由,執行,結果處理,都是由它來完成的,它工作在客户端。

ldapsearch 分頁查詢_#分庫分表_03

1.2 Sharding-Proxy

Sharding-Proxy有點類似於Mycat,它是提供了數據庫層面的代理,什麼意思呢?簡單來説,以前我們的應用是直連數據庫,引入了Sharding-Proxy之後,我們的應用是直連Sharding-Proxy,然後Sharding-Proxy通過處理之後再轉發到mysql中。

這種方式的好處在於,用户不需要感知到分庫分表的存在,相當於正常訪問mysql。目前Sharding-Proxy支持Mysql和PostgreSQL兩種數據庫協議,如下圖所示。

ldapsearch 分頁查詢_#分庫分表_04

1.3 Sharding-Sidecar(TODO)

看到Sidecar,大家應該就能想到服務網格架構,它主要定位於 Kubernetes 的雲原生數據庫代理,以Sidecar 的形式代理所有對數據庫的訪問。目前Sharding-Sidecar還處於開發階段未發佈。

2. Sharding-JDBC

Sharding-JDBC是對原有JDBC驅動的增強,在分庫分表的場景中,為應用提供瞭如下圖所示的功能。

ldapsearch 分頁查詢_ldapsearch 分頁查詢_05

2.1 實戰

話不多説,現在我們知道了他有什麼功能,是不是迫不及待想實現分庫分表了?那就讓我們直接進入實戰吧。由於篇幅有限,我將實戰分為JAVA API版本以及常用的Springboot版本。大家可以前往查看,相關概念問題還在本文闡述。當然,建議瞭解了相關概念後再去實戰。

  • Api 實戰
  • Spring boot 實戰

2.2 相關概念

前面我們通過兩種方式演示了Sharding-JDBC的分庫分表功能的用法,其實,從這個層面來説,Sharding-JDBC相當於增強了JDBC驅動的功能,使得開發者只需要通過配置就可以輕鬆完成分庫分表功能的實現。

在Sharding-JDBC中,有一些表的概念,需要給大家普及一下,邏輯表、真實表、分片鍵、數據節點、動態表、廣播表、綁定表。

2.2.1 邏輯表

邏輯表可以理解為數據庫中的視圖,是一張虛擬表。可以映射到一張物理表,也可以由多張物理表組成,這些物理表可以來自於不同的數據源。對於mysql, Hbase和ES,要組成一張邏輯表,只需要他們有相同含義的key即可。這個key在mysql中是主鍵,Hbase中是生成rowkey用的值,是ES中的key。

在前面的分庫分表規則配置中,就有用到t_order這個邏輯表的定義,當我們針對t_order表操作時,會根據分片規則映射到實際的物理表進行相關事務操作,如下圖所示,邏輯表會在SQL解析和路由時被替換成真實的表名。

spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds-$-> {0.}.t_order_$->{0.}

ldapsearch 分頁查詢_ldapsearch 分頁查詢_06

2.2.2 廣播表

廣播表也叫全局表,也就是它會存在於多個庫中冗餘,避免跨庫查詢問題。

比如省份、字典等一些基礎數據,為了避免分庫分表後關聯表查詢這些基礎數據存在跨庫問題,所以可以把這些數據同步給每一個數據庫節點,這個就叫廣播表,如下圖所示。

ldapsearch 分頁查詢_#database_07

在Sharding-JDBC中,配置方式如下

# 廣播表, 其主節點是ds0 
spring.shardingsphere.sharding.broadcast-tables=t_config 
spring.shardingsphere.sharding.tables.t_config.actual-data-nodes=ds$-> {0}.t_config

2.2.3 綁定表

我們有些表的數據是存在邏輯的主外鍵關係的,比如訂單表order_info,存的是彙總的商品數,商品金額;訂單明細表order_detail,是每個商品的價格,個數等等。或者叫做從屬關係,父表和子表的關係。他們之間會經常有關聯查詢的操作,如果父表的數據和子表的數據分別存儲在不同的數據庫,跨庫關聯查詢也比較麻煩。所以我們能不能把父表和數據和從屬於父表的數據落到一個節點上呢?

比如order_id=1001的數據在node1,它所有的明細數據也放到node1;order_id=1002的數據在node2,它所有的明細數據都放到node2,這樣在關聯查詢的時候依然是在一個數據庫,如下圖所示:

ldapsearch 分頁查詢_#分庫分表_08

# 綁定表規則,多組綁定規則使用數組形式配置 
spring.shardingsphere.rules.sharding.binding-tables=t_order,t_order_item

如果存在多個綁定表規則,可以用數組的方式聲明

spring.shardingsphere.rules.sharding.binding-tables[0]= # 綁定表規則列表 
spring.shardingsphere.rules.sharding.binding-tables[1]= # 綁定表規則列表 
spring.shardingsphere.rules.sharding.binding-tables[x]= # 綁定表規則列表

2.3 Sharding-JDBC中的分片

Sharding-JDBC內置了很多常用的分片策略,這些算法主要針對兩個維度

  • 數據源分片
  • 數據表分片

Sharding-JDBC的分片策略包含了分片鍵和分片算法;

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

Sharding-JDBC提供內置了多種分片算法,包含四種類型分別是

  • 自動分片算法
  • 標準分片算法
  • 複合分片算法
  • Hinit分片算法

2.3.1 自動分片算法

自動分片算法,就是根據我們配置的算法表達式完成數據的自動分發功能,在Sharding-JDBC中提供了五種自動分片算法.

  • 取模分片算法
  • 哈希取模分片算法
  • 基於分片容量的範圍分片算法
  • 基於分片邊界的範圍分片算法
  • 自動時間段分片算法

2.3.2 標準分片算法

標準分片策略( StandardShardingStrategy ),它只支持對單個分片健(字段)為依據的分庫分表,Sharding-JDBC提供了兩種算法實現。

2.3.3 複合分片算法

使用場景:SQL 語句中有 > , >= , <= , < , = , IN 和 BETWEEN AND 等操作符,不同的是複合分片策略支持對多個分片健操作。

2.3.4 自定義分片算法

除了默認提供了分片算法之外,我們可以根據實際需求自定義分片算法,Sharding-JDBC同樣提供了幾種類型的擴展實現:

  • 標準分片算法
  • 複合分片算法
  • Hinit分片策略
  • 不分片策略

3. 關於Java中的SPI機制

SPI 的全名為 Service Provider Interface,它的核心思想是中間件中定義標準,然後使用者可以在這個標準上實現自定義擴展,舉個比較常見的例子,就是JDBC驅動。 Java官方只提供了JDBC驅動的接口:

java.sql.Driver

然後各大數據庫廠商,如Mysql、Oracle都會基於這個接口定義不同數據庫的連接實現,然後使用java語言的開發者不需要關心不同數據庫的具體配置,只需要集成相關的依賴包以及配置相關驅動,Java程序就能自動匹配到相關的實現完成數據庫連接。

這種思想在很多地方都有使用,比如Spring中的SpringFactoriesLoader、Dubbo中的SPI思想、Sentinel中的SPI思想等等。很多中間件中使用的SPI都不是Java原生的SPI,而是基於這種思想優化過後的。

下面來看一下SPI如何使用:

SPI的使用規則

SPI的使用必須遵循以下約定。

  • META/service的目錄 放在classpath目錄下
  • 擴展類全限定名組成的文件名
  • 文件內容填寫該擴展類的實現類
  • ServiceLoad.load(Driver.class)
  • 會從classpath目錄下 META-INF/service目錄下找到java.sql.Driver的文件名 [list]
  • 解析這些文件
  • 得到所有文件中填寫的實現類

4. Sharding-JDBC的工作流程

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

ldapsearch 分頁查詢_ldapsearch 分頁查詢_09

4.1 SQL解析引擎

先通過詞法解析器將 SQL 拆分為一個個不可再分的單詞。再使用語法解析器對 SQL 進行理解,並最終提煉出解析上下文。 解析上下文包括表、選擇項、排序項、分組項、聚合函數、分頁信息、查詢條件以及可能需要修改的佔位符的標記。

ldapsearch 分頁查詢_ldapsearch 分頁查詢_10

為了便於理解,抽象語法樹中的關鍵字的 Token 用綠色表示,變量的 Token 用紅色表示,灰色表示需要進一步拆分。

最後,通過 visitor 對抽象語法樹遍歷構造域模型,通過域模型( SQLStatement )去提煉分片所需的上下文,並標記有可能需要改寫的位置。 供分片使用的解析上下文包含查詢選擇項(Select Items)、表信息(Table)、分片條件(Sharding Condition)、自增主鍵信息(Auto increment Primary Key)、排序信息(Order By)、分組信息(Group By)以及分頁信息(Limit、Rownum、Top)。 SQL 的一次解析過程是不可逆的,一個個 Token 按 SQL 原本的順序依次進行解析,性能很高。 考慮到各種數據庫 SQL 方言的異同,在解析模塊提供了各類數據庫的 SQL 方言字典。

4.2 路由引擎

路由引擎主要作用是,根據解析的上下文匹配數據庫和表的分片策略,並生成路由路徑。

這裏的分片策略就是我們前面講的內置分片策略或者用户自定義分片策略。內置的分片策略大致可分為尾數取模、哈希、範圍、標籤、時間等,用户方配置的分片策略則更加靈活,可以根據使用方需求定製複合分片策略。

對於攜帶分片鍵的 SQL,根據分片鍵的不同可以劃分為單片路由(分片鍵的操作符是等號)、多片路由(分片鍵的操作符是 IN)和範圍路由(分片鍵的操作符是 BETWEEN)。 不攜帶分片鍵的 SQL 則採用廣播路由。

所謂廣播路由,就是sql中沒有攜帶分片鍵,此時需要掃描全部庫和全部表進行數據聚合。

4.3 SQL改寫引擎

我們在配置數據庫的分片策略時,都是基於邏輯庫和邏輯表來操作,包括我們編寫的sql語句,都是面向邏輯庫來操作。

這個基於邏輯庫的查詢,並不能真正在數據庫中執行,比如:

SELECT order_id FROM t_order WHERE order_id=1;

此時t_order表示一個邏輯表,真正的表可能是db0中的t_order_0。所以需要通過SQL改寫引擎把邏輯表改成真實數據庫中可以執行的正確SQL。

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

SELECT order_id FROM t_order_1 WHERE order_id=1;

除了表名改寫之外,改寫引擎還提供了補列、分頁修正、批量拆分等改寫方式。

4.4 SQL執行引擎

ShardingSphere採用一套自動化的執行引擎,負責將路由和改寫完成之後的真實SQL安全且高效發送到底層數據源執行。

4.5 結果歸併引擎

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

ShardingSphere 支持的結果歸併從功能上分為遍歷、排序、分組、分頁和聚合 5 種類型。