經過近兩年時間的優化和打磨,Apache ShardingSphere 5.0.0 GA 版終於在本月正式釋出,相比於 4.1.1 GA 版,5.0.0 GA 版在核心層面進行了大量的優化。首先,基於可插拔架構對核心進行了全面改造,核心中的各個功能可以任意組合併疊加使用。其次,為了提升 SQL 分散式查詢能力,5.0.0 GA 版打造了全新的Federation 執行引擎,來滿足使用者複雜的業務場景。此外,5.0.0 GA 版在核心功能 API 層面也進行了大量優化,旨在降低使用者使用這些功能的成本。本文將為大家詳細解讀 5.0.0 GA 版中的這些重大核心優化,並將對比兩個 GA 版本中存在的差異,以典型的資料分片、讀寫分離和加解密整合使用的場景為例,幫助使用者更好地理解這些優化並完成版本升級。
端正強
SphereEx 高階中介軟體開發工程師,Apache ShardingSphere Committer。
2018 年開始接觸 Apache ShardingSphere 中介軟體,曾主導公司內部海量資料的分庫分表,有著豐富的實踐經驗;熱愛開源,樂於分享,目前專注於 Apache ShardingSphere 核心模組開發。
可拔插架構核心
Apache ShardingSphere 5.0.0 GA 版提出了全新的 Database Plus 理念,目標是構建異構資料庫上層標準和生態,為使用者提供精準化和差異化的能力。Database Plus 具有連線、增量、可插拔的特點,具體來說,Apache ShardingSphere 能夠連線不同的異構資料庫,基於異構資料庫的基礎服務,提供資料分片、資料加解密及分散式事務等增量功能。另外,通過可插拔平臺,Apache ShardingSphere 提供的增量功能能夠無限擴充套件,使用者也可以根據需求靈活進行擴充套件。Database Plus 理念的出現,使得 ShardingSphere 真正意義上從一個分庫分表中介軟體蛻變成為一套強大的分散式資料庫生態系統。通過踐行 Database Plus 理念,基於可插拔平臺提供的擴充套件點,Apache ShardingSphere 核心也進行了全面地可插拔化改造。下圖展示了全新的可插拔架構核心:
Apache ShardingSphere 核心流程中的後設資料載入、SQL 解析、SQL 路由 、SQL 改寫、SQL 執行和結果歸併,都提供了豐富的擴充套件點,基於這些擴充套件點,Apache ShardingSphere 預設實現了資料分片、讀寫分離、加解密、影子庫壓測及高可用等功能。
按照擴充套件點是基於技術還是基於功能實現,我們可以將擴充套件點劃分為功能擴充套件點和技術擴充套件點。Apache ShardingSphere 核心流程中,SQL 解析引擎及 SQL 執行引擎的擴充套件點屬於技術擴充套件點,而後設資料載入、SQL 路由引擎、SQL 改寫引擎及結果歸併引擎的擴充套件點屬於功能擴充套件點。
SQL 解析引擎擴充套件點,主要包括 SQL 語法樹解析及 SQL 語法樹遍歷兩個擴充套件點。Apache ShardingSphere 的 SQL 解析引擎,基於這兩個擴充套件點,預設支援了 MySQL、PostgreSQL、Oracle、SQLServer、openGauss 和 SQL92 等資料庫方言的解析和遍歷。使用者也可以基於這兩個擴充套件點,實現 Apache ShardingSphere SQL 解析引擎暫不支援的資料庫方言,以及開發諸如 SQL 審計這樣的新功能。
SQL 執行引擎擴充套件點按照不同的執行方式來提供擴充套件,目前 Apache ShardingSphere SQL 執行引擎已經提供了單執行緒執行引擎和多執行緒執行引擎。單執行緒執行引擎主要用於處理包含事務的語句執行,多執行緒執行引擎則適用於不包含事務的場景,用於提升 SQL 執行的效能。未來,Apache ShardingSphere 將基於執行引擎擴充套件點,提供諸如 MPP 執行引擎在內的更多執行引擎,滿足分散式場景下 SQL 執行的要求。
基於功能擴充套件點,Apache ShardingSphere 提供了資料分片、讀寫分離、加解密、影子庫壓測及高可用等功能,這些功能根據各自需求,實現了全部或者部分功能擴充套件點,並且在功能內部,又通過細化功能級擴充套件點提供了諸如分片策略、分散式 ID 生成及負載均衡演算法等內部擴充套件點。下面是 Apache ShardingSphere 核心功能實現的擴充套件點:
-
資料分片:實現了後設資料載入、SQL 路由、SQL 改寫和結果歸併的全部功能擴充套件點,在資料分片功能內部,又提供了分片演算法、分散式 ID 等擴充套件點;
-
讀寫分離:實現了 SQL 路由的功能擴充套件點,功能內部提供了負載均衡演算法擴充套件點;
-
加解密:實現了後設資料載入、SQL 改寫和結果歸併的擴充套件點,內部提供了加解密演算法擴充套件點;
-
影子庫壓測:實現了 SQL 路由的擴充套件點,在影子庫壓測功能內部,提供了影子演算法擴充套件點;
-
高可用:實現了 SQL 路由的擴充套件點。
基於這些擴充套件點,Apache ShardingSphere 功能的可擴充套件空間非常大,像多租戶和 SQL 審計等功能,都可以通過擴充套件點無縫地整合到 Apache ShardingSphere 生態中。此外,使用者也可以根據自己的業務需求,基於擴充套件點完成定製化功能開發,快速地搭建出一套分散式資料庫系統。關於可插拔架構擴充套件點的詳細說明,可以參考官網開發者手冊:https://shardingsphere.apache.org/document/current/cn/dev-manual/
綜合對比來看,5.0.0 GA 版可插拔架構核心和 4.1.1 GA 版核心主要的差異如下:
首先,從專案定位上來看,5.0.0 GA 版藉助可插拔架構實現了從分庫分表中介軟體到分散式資料庫生態系統的轉變,各個功能都可以通過可插拔架構融入到分散式資料庫生態系統中。其次,從專案功能上來看,4.1.1 GA 版只提供一些基礎功能,而 5.0.0 GA 版則更加側重於提供基礎設施,以及一些功能的最佳實踐,使用者完全可以捨棄這些功能,基於核心基礎設施開發定製化功能。從功能耦合的角度來看,5.0.0 GA 版的核心功能,做到了相互隔離,互無感知,這樣可以最大程度地保證核心的穩定性。最後,從功能組合使用的角度來看,5.0.0 GA 版實現了功能的層級一致,資料分片、讀寫分離、影子庫壓測、加解密和高可用等功能,可以按照使用者的需求任意組合。而在 4.1.1 GA 版中,使用者在組合使用這些功能時,必須以資料分片為中心,再疊加使用其他功能。
通過這些對比可以看出, 5.0.0 GA 版可插拔核心進行了全方位地增強,使用者可以像搭積木一樣對功能進行疊加組合,從而滿足更多業務需求。但是,可插拔架構的調整也導致了核心功能的使用方式出現了很大的變化,在文章的後續內容中,我們會通過例項來詳細介紹在 5.0.0 GA 版中如何組合使用這些功能。
Federation 執行引擎
Federation 執行引擎是 5.0.0 GA 版核心的又一大亮點功能,目標是支援那些在 4.1.1 GA 版中無法執行的分散式查詢語句,例如:跨資料庫例項的關聯查詢及子查詢。Federation 執行引擎的出現,使得業務研發人員不必再關心 SQL 的使用範圍,能夠專注於業務功能開發,減少了業務層面的功能限制。
上圖展示了 Federation 執行引擎的處理流程,總體上來看,仍然是遵循著 SQL 解析、SQL 路由、SQL 改寫、SQL 執行這幾個步驟,唯一的區別是 Federation 執行引擎額外引入了 SQL 優化,對分散式查詢語句進行 RBO(Rule Based Optimizer) 和 CBO(Cost Based Optimizer) 優化,從而得到代價最小的執行計劃。在 SQL 路由階段,路由引擎會根據 SQL 語句是否跨多個資料庫例項,來決定 SQL 是否通過 Federation 執行引擎來執行。
Federation 執行引擎目前處於快速開發中,仍然需要大量的優化,還是一個實驗性的功能,因此預設是關閉的,如果想要體驗 Federation 執行引擎,可以通過配置 sql-federation-enabled: true 來開啟該功能。
Federation 執行引擎主要用來支援跨多個資料庫例項的關聯查詢和子查詢,以及部分核心不支援的聚合查詢。下面我們通過具體的場景,來了解下 Federation 執行引擎支援的語句。
-
跨庫關聯查詢:當關聯查詢中的多個表分佈在不同的資料庫例項上時,由 Federation 執行引擎提供支援。
例如,在下面的資料分片配置中,t_order 和 t_order_item 表是多資料節點的分片表,並且未配置繫結表規則,t_user 和 t_user_role 則是分佈在不同的資料庫例項上的單表。
rules: - !SHARDING tables: t_order: actualDataNodes: ds_${0..1}.t_order_${0..1} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: t_order_inline t_order_item: actualDataNodes: ds_${0..1}.t_order_item_${0..1} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: t_order_item_inline
由於跨多個資料庫例項,下面這些常用的 SQL,會使用 Federation 執行引擎進行關聯查詢。
SELECT * FROM t_order o INNER JOIN t_order_item i ON o.order_id = i.order_id WHERE o.order_id = 1; SELECT * FROM t_order o INNER JOIN t_user u ON o.user_id = u.user_id WHERE o.user_id = 1; SELECT * FROM t_order o LEFT JOIN t_user_role r ON o.user_id = r.user_id WHERE o.user_id = 1; SELECT * FROM t_order_item i LEFT JOIN t_user u ON i.user_id = u.user_id WHERE i.user_id = 1; SELECT * FROM t_order_item i RIGHT JOIN t_user_role r ON i.user_id = r.user_id WHERE i.user_id = 1; SELECT * FROM t_user u RIGHT JOIN t_user_role r ON u.user_id = r.user_id WHERE u.user_id = 1;
-
子查詢:Apache ShardingSphere 的 Simple Push Down 引擎能夠支援分片條件一致的子查詢,以及路由到單個分片的子查詢。對於子查詢和外層查詢未同時指定分片鍵,或分片鍵的值不一致的場景,需要由 Federation 執行引擎來提供支援。
下面展示了一些由 Federation 執行引擎支援的子查詢場景:
SELECT * FROM t_order o INNER JOIN t_order_item i ON o.order_id = i.order_id WHERE o.order_id = 1; SELECT * FROM t_order o INNER JOIN t_user u ON o.user_id = u.user_id WHERE o.user_id = 1; SELECT * FROM t_order o LEFT JOIN t_user_role r ON o.user_id = r.user_id WHERE o.user_id = 1; SELECT * FROM t_order_item i LEFT JOIN t_user u ON i.user_id = u.user_id WHERE i.user_id = 1; SELECT * FROM t_order_item i RIGHT JOIN t_user_role r ON i.user_id = r.user_id WHERE i.user_id = 1; SELECT * FROM t_user u RIGHT JOIN t_user_role r ON u.user_id = r.user_id WHERE u.user_id = 1;
- 聚合查詢:對於 Apache ShardingSphere Simple Push Down 引擎暫不支援的一些聚合查詢,我們也同樣通過 Federation 執行引擎提供了支援。
SELECT user_id, SUM(order_id) FROM t_order GROUP BY user_id HAVING SUM(order_id) > 10; SELECT (SELECT MAX(user_id) FROM t_order) a, order_id FROM t_order; SELECT COUNT(DISTINCT user_id), SUM(order_id) FROM t_order;
Federation 執行引擎的出現,使得 Apache ShardingSphere 分散式查詢能力得到明顯增強,未來 Apache ShardingSphere 將持續優化,有效降低 Federation 執行引擎的記憶體佔用,不斷提升分散式查詢的能力。關於 Federation 執行引擎支援語句的詳細清單,可參考官方文件中的實驗性支援的 SQL:https://shardingsphere.apache.org/document/5.0.0/cn/features/sharding/use-norms/sql/
核心功能 API 調整
為了降低使用者使用核心功能的成本,5.0.0 GA 版在 API 層面也進行了大量的優化。首先,針對社群反饋較多的資料分片 API 過於複雜、難以理解的問題,經過社群充分討論之後,在 5.0.0 GA 版中提供了全新的資料分片 API。同時,隨著 Apache ShardingSphere 專案定位的變化——由傳統資料庫中介軟體蛻變為分散式資料庫生態系統,實現透明化的資料分片功能也變得越發重要。因此,5.0.0 GA 版提供了自動化的分片策略,使用者無需關心分庫分表的細節,通過指定分片數即可實現自動分片。此外,由於可插拔架構的提出,以及影子庫壓測等功能的進一步增強,核心功能 API 都進行了相應的優化調整。下面我們將會從不同功能的角度,為大家詳細介紹 5.0.0 GA 版 API 層面的調整。
資料分片 API 調整
在 4.x 版中,社群經常反饋資料分片的 API 過於複雜,難以理解。下面是 4.1.1 GA 版中的資料分片配置,分片策略包含了standard、complex、inline、hint和none 5 種策略,不同的分片策略之間引數也大不相同,導致普通使用者很難理解和使用。
shardingRule: tables: t_order: databaseStrategy: standard: shardingColumn: order_id preciseAlgorithmClassName: xxx rangeAlgorithmClassName: xxx complex: shardingColumns: year, month algorithmClassName: xxx hint: algorithmClassName: xxx inline: shardingColumn: order_id algorithmExpression: ds_${order_id % 2} none: tableStrategy: ...
5.0.0 GA 版對分片 API 中的分片策略進行了簡化,首先去除了原有的inline策略,只保留了standard、complex、hint和none這四個分片策略,同時將分片演算法從分片策略中抽取出來,放到shardingAlgorithms屬性下進行單獨配置,分片策略中通過指定shardingAlgorithmName屬性進引用即可。
rules: - !SHARDING tables: t_order: databaseStrategy: standard: shardingColumn: order_id shardingAlgorithmName: database_inline complex: shardingColumns: year, month shardingAlgorithmName: database_complex hint: shardingAlgorithmName: database_hint none: tableStrategy: ... shardingAlgorithms: database_inline: type: INLINE props: algorithm-expression: ds_${order_id % 2} database_complex: type: CLASS_BASED props: strategy: COMPLEX algorithmClassName: xxx database_hint: type: CLASS_BASED props: strategy: HINT algorithmClassName: xxx
上面是根據 4.1.1 GA 版分片配置修改後的配置,可以看出新的分片 API 更加簡潔清晰。同時為了減少使用者的配置量,Apache ShardingSphere 提供了眾多內建分片演算法供使用者選擇,使用者也可以通過CLASS_BASED分片演算法進行自定義。更多關於內建分片演算法的內容,可以參考官方文件內建演算法-分片演算法: https://shardingsphere.apache.org/document/5.0.0/cn/user-manual/shardingsphere-jdbc/configuration/built-in-algorithm/sharding/
除了優化資料分片 API 之外,為了能夠實現透明化資料分片,5.0.0 GA 版還提供了自動化的分片策略。下面展示了自動化分片策略配置和手動宣告分片策略配置的差異:
rules: - !SHARDING autoTables: # 自動分片策略 t_order: actualDataSources: ds_0, ds_1 shardingStrategy: standard: shardingColumn: order_id shardingAlgorithmName: auto_mod keyGenerateStrategy: column: order_id keyGeneratorName: snowflake shardingAlgorithms: auto_mod: type: MOD props: sharding-count: 4 tables: # 手動宣告分片策略 t_order: actualDataNodes: ds_${0..1}.t_order_${0..1} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: table_inline dataBaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: database_inline
自動化分片策略,需要配置在 autoTables 屬性下,使用者只需要指定資料儲存的資料來源,同時通過自動分片演算法指定分片數即可,不再需要通過 actualDataNodes 來手動宣告資料分佈,也無需專門設定分庫策略和分表策略,Apache ShardingSphere 將自動實現資料分片管理。
此外,5.0.0 GA 版刪除了資料分片 API 中的 defaultDataSourceName 配置。在 5.0.0 GA 版中,Apache ShardingSphere 定位為分散式資料庫生態系統,使用者可以像使用傳統資料庫一樣,直接使用 Apache ShardingSphere 提供的服務,因此使用者無需感知底層的資料庫儲存。Apache ShardingSphere 通過內建的 SingleTableRule 來管理資料分片之外的單表,幫助使用者實現單表的自動載入和路由。
5.0.0 GA 版為了進一步簡化使用者配置,同時配合資料分片 API 中的 defaultDatabaseStrategy 和 defaultTableStrategy 分片策略,增加了 defaultShardingColumn 配置,作為預設的分片鍵。當多個表分片鍵相同時,使用者可以不配置 shardingColumn,使用預設的 defaultShardingColumn 配置。下面的分片配置中,t_order 表的分片策略都會使用預設的 defaultShardingColumn 配置。
rules: - !SHARDING tables: t_order: actualDataNodes: ds_${0..1}.t_order_${0..1} tableStrategy: standard: shardingAlgorithmName: table_inline defaultShardingColumn: order_id defaultDatabaseStrategy: standard: shardingAlgorithmName: database_inline defaultTableStrategy: none:
讀寫分離 API 調整
讀寫分離 API 的基本功能,在 5.0.0 GA 版變化不大,只是由MasterSlave調整為ReadWriteSplitting,其他用法基本相同。下面是 4.1.1 GA 版和 5.0.0 GA 版讀寫分離 API 的對比。
# 4.1.1 GA 讀寫分離 API masterSlaveRule: name: ms_ds masterDataSourceName: master_ds slaveDataSourceNames: - slave_ds_0 - slave_ds_1 # 5.0.0 GA 讀寫分離 API rules: - !READWRITE_SPLITTING dataSources: pr_ds: writeDataSourceName: write_ds readDataSourceNames: - read_ds_0 - read_ds_1
此外,在 5.0.0 GA 版中,基於可插拔架構開發了高可用功能,讀寫分離可以配合高可用功能,提供能夠自動切換主從的高可用版讀寫分離,歡迎大家關注高可用功能後續的官方文件及技術分享。
加解密 API 調整
5.0.0 GA 版對於加解密 API 進行了小幅度優化,增加了table級別的queryWithCipherColumn屬性,方便使用者能夠對加解密欄位的明文、密文切換進行表級別的控制,其他配置和 4.1.1 GA 版基本保持一致。
rules: - !ENCRYPT encryptors: aes_encryptor: type: AES props: aes-key-value: 123456abc md5_encryptor: type: MD5 tables: t_encrypt: columns: user_id: plainColumn: user_plain cipherColumn: user_cipher encryptorName: aes_encryptor order_id: cipherColumn: order_cipher encryptorName: md5_encryptor queryWithCipherColumn: true queryWithCipherColumn: false
影子庫壓測 API 調整
影子庫壓測 API,在 5.0.0 GA 版中進行了全面調整,首先刪除了影子庫中的邏輯列,並增加了功能強大的影子庫匹配演算法,用來幫助使用者實現更加靈活的路由控制。下面是 4.1.1 GA 版影子庫壓測的 API,總體上功能較為簡單,根據邏輯列對應的值判斷是否開啟影子庫壓測。
shadowRule: column: shadow shadowMappings: ds: shadow_ds
5.0.0 GA 版中影子庫壓測 API 則更加強大,使用者可以通過enable屬性,控制是否開啟影子庫壓測,同時可以按照表的維度,細粒度控制需要進行影子庫壓測的生產表,並支援多種不同的匹配演算法,例如:列值匹配演算法、列正規表示式匹配演算法以及 SQL 註釋匹配演算法。
rules: - !SHADOW enable: true dataSources: shadowDataSource: sourceDataSourceName: ds shadowDataSourceName: shadow_ds tables: t_order: dataSourceNames: - shadowDataSource shadowAlgorithmNames: - user-id-insert-match-algorithm - simple-hint-algorithm shadowAlgorithms: user-id-insert-match-algorithm: type: COLUMN_REGEX_MATCH props: operation: insert column: user_id regex: "[1]" simple-hint-algorithm: type: SIMPLE_NOTE props: shadow: true foo: bar
在後續的技術分享文章中,我們會對影子庫壓測功能進行詳細介紹,此處就不展開說明,更多影子庫匹配演算法可以參考官方文件影子演算法:https://shardingsphere.apache.org/document/5.0.0/cn/user-manual/shardingsphere-jdbc/configuration/built-in-algorithm/shadow/
5.0.0 GA 升級指南
前面分別從可插拔核心架構、Federation 執行引擎以及核心功能 API 調整三個方面,詳細地介紹了 5.0.0 GA 版核心的重大優化。面對兩個版本存在的眾多差異,大家最關心的莫過於如何從 4.1.1 GA 升級到 5.0.0 GA 版本?下面我們將基於資料分片、讀寫分離和加解密整合使用這樣一個典型的場景,詳細介紹下升級 5.0.0 GA 版本需要注意哪些問題。
在 4.1.1 GA 中,組合使用多個功能時,必須以資料分片為基礎,然後疊加讀寫分離和加解密,因此 4.1.1 GA 版中的配置通常如下:
shardingRule: tables: t_order: actualDataNodes: ms_ds_${0..1}.t_order_${0..1} tableStrategy: inline: shardingColumn: order_id algorithmExpression: t_order_${order_id % 2} t_order_item: actualDataNodes: ms_ds_${0..1}.t_order_item_${0..1} tableStrategy: inline: shardingColumn: order_id algorithmExpression: t_order_item_${order_id % 2} bindingTables: - t_order,t_order_item broadcastTables: - t_config defaultDataSourceName: ds_0 defaultDatabaseStrategy: inline: shardingColumn: user_id algorithmExpression: ms_ds_${user_id % 2} defaultTableStrategy: none: masterSlaveRules: ms_ds_0: masterDataSourceName: ds_0 slaveDataSourceNames: - ds_0_slave_0 - ds_0_slave_1 loadBalanceAlgorithmType: ROUND_ROBIN ms_ds_1: masterDataSourceName: ds_1 slaveDataSourceNames: - ds_1_slave_0 - ds_1_slave_1 loadBalanceAlgorithmType: ROUND_ROBIN encryptRule: encryptors: aes_encryptor: type: aes props: aes.key.value: 123456abc tables: t_order: columns: content: plainColumn: content_plain cipherColumn: content_cipher encryptor: aes_encryptor t_user: columns: telephone: plainColumn: telephone_plain cipherColumn: telephone_cipher encryptor: aes_encryptor
從上面的配置檔案中可以看出,t_order 和 t_order_item配置了分片規則,並且t_order 表的 content欄位同時設定了加解密規則,使用 AES 演算法進行加解密。t_user則是未分片的普通表,telephone欄位也配置了加解密規則。另外需要注意的是,讀寫分離規則和加解密規則都是以屬性的形式,配置在分片規則中,這也是 4.1.1 GA 中功能依賴的具體體現,其他功能都必須以資料分片為基礎。
配置完成之後,我們啟動 4.1.1 GA 版 Proxy 接入端,對 t_order、t_order_item 及 t_user表進行初始化。初始化語句執行的結果如下:
CREATE TABLE t_order(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)); # Logic SQL: CREATE TABLE t_order(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_0(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_1(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_0(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_1(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)) CREATE TABLE t_order_item(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)); # Logic SQL: CREATE TABLE t_order_item(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_item_0(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_item_1(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_item_0(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_item_1(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone VARCHAR(100)); # Logic SQL: CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone VARCHAR(100))
t_order表分片功能路由改寫正常,但加解密功能對應的改寫沒有能夠支援,因為 4.1.1 GA 版本不支援加解密場景下 DDL 語句的改寫,因此,需要使用者在底層資料庫上提前建立好對應的加解密表,DDL 語句支援加解密改寫在 5.0.0 GA 版已經完美支援,減少了使用者不必要的操作。
t_order_item 表由於不涉及加解密,路由改寫的結果正常。t_user 表同樣存在加解密 DDL 語句改寫的問題,並且 t_user 表被路由到了 ds_0 資料來源,這是因為我們在分片規則中配置了 defaultDataSourceName: ds_0,所以對於非分片表,都會使用這個規則進行路由。
對於 t_order 表和 t_user 表,我們通過如下 SQL 在路由結果對應的底層資料庫上,手動建立加解密表。
# ds_0 建立 t_order_0、t_order_1 和 t_user CREATE TABLE t_order_0(order_id INT(11) PRIMARY KEY, user_id INT(11), content_plain VARCHAR(100), content_cipher VARCHAR(100)) CREATE TABLE t_order_1(order_id INT(11) PRIMARY KEY, user_id INT(11), content_plain VARCHAR(100), content_cipher VARCHAR(100)) CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone_plain VARCHAR(100), telephone_cipher VARCHAR(100)) # ds_1 建立 t_order_0 和 t_order_1 CREATE TABLE t_order_0(order_id INT(11) PRIMARY KEY, user_id INT(11), content_plain VARCHAR(100), content_cipher VARCHAR(100)) CREATE TABLE t_order_1(order_id INT(11) PRIMARY KEY, user_id INT(11), content_plain VARCHAR(100), content_cipher VARCHAR(100))
我們重啟 Proxy 並向 t_order、t_order_item和t_user 表新增資料。t_order和t_order_item表在插入資料過程中,會根據分片鍵及配置的分片策略,路由到對應的資料節點。t_user表則根據defaultDataSourceName配置路由到ds_0資料來源。
INSERT INTO t_order(order_id, user_id, content) VALUES(1, 1, 'TEST11'), (2, 2, 'TEST22'), (3, 3, 'TEST33'); # Logic SQL: INSERT INTO t_order(order_id, user_id, content) VALUES(1, 1, 'TEST11'), (2, 2, 'TEST22'), (3, 3, 'TEST33') # Actual SQL: ds_0 ::: INSERT INTO t_order_0(order_id, user_id, content_cipher, content_plain) VALUES(2, 2, 'mzIhTs2MD3dI4fqCc5nF/Q==', 'TEST22') # Actual SQL: ds_1 ::: INSERT INTO t_order_1(order_id, user_id, content_cipher, content_plain) VALUES(1, 1, '3qpLpG5z6AWjRX2sRKjW2g==', 'TEST11'), (3, 3, 'oVkQieUbS3l/85axrf5img==', 'TEST33') INSERT INTO t_order_item(item_id, order_id, user_id, content) VALUES(1, 1, 1, 'TEST11'), (2, 2, 2, 'TEST22'), (3, 3, 3, 'TEST33'); # Logic SQL: INSERT INTO t_order_item(item_id, order_id, user_id, content) VALUES(1, 1, 1, 'TEST11'), (2, 2, 2, 'TEST22'), (3, 3, 3, 'TEST33') # Actual SQL: ds_0 ::: INSERT INTO t_order_item_0(item_id, order_id, user_id, content) VALUES(2, 2, 2, 'TEST22') # Actual SQL: ds_1 ::: INSERT INTO t_order_item_1(item_id, order_id, user_id, content) VALUES(1, 1, 1, 'TEST11'), (3, 3, 3, 'TEST33') INSERT INTO t_user(user_id, telephone) VALUES(1, '11111111111'), (2, '22222222222'), (3, '33333333333'); # Logic SQL: INSERT INTO t_user(user_id, telephone) VALUES(1, '11111111111'), (2, '22222222222'), (3, '33333333333') # Actual SQL: ds_0 ::: INSERT INTO t_user(user_id, telephone_cipher, telephone_plain) VALUES(1, 'jFZBCI7G9ggRktThmMlClQ==', '11111111111'), (2, 'lWrg5gaes8eptaQkUM2wtA==', '22222222222'), (3, 'jeCwC7gXus4/1OflXeGW/w==', '33333333333')
然後再執行幾個簡單的查詢語句,看下讀寫分離是否生效。根據日誌可以看出,t_order和t_order_item表,進行了加解密改寫,也正確地路由到了從庫。而t_user表仍然路由到ds_0資料來源上執行,規則中配置的讀寫分離規則沒有起到作用。這是由於在 4.1.1 GA 版中,讀寫分離和加解密都是基於分片功能進行整合,這種方案天然限制了分片之外功能的配合使用。
SELECT * FROM t_order WHERE user_id = 1 AND order_id = 1; # Logic SQL: SELECT * FROM t_order WHERE user_id = 1 AND order_id = 1 # Actual SQL: ds_1_slave_0 ::: SELECT order_id, user_id, content_plain, content_cipher FROM t_order_1 WHERE user_id = 1 AND order_id = 1 SELECT * FROM t_order_item WHERE user_id = 1 AND order_id = 1; # Logic SQL: SELECT * FROM t_order_item WHERE user_id = 1 AND order_id = 1 # Actual SQL: ds_1_slave_1 ::: SELECT * FROM t_order_item_1 WHERE user_id = 1 AND order_id = 1 SELECT * FROM t_user WHERE user_id = 1; # Logic SQL: SELECT * FROM t_user WHERE user_id = 1 # Actual SQL: ds_0 ::: SELECT user_id, telephone_plain, telephone_cipher FROM t_user WHERE user_id = 1
5.0.0 GA 版基於可插拔架構,對核心進行了全面地升級,核心中的各個功能都可以任意組合使用。同時,5.0.0 GA 版刪除了需要使用者額外配置的defaultDataSourceName,預設通過SingleTableRule實現單表的後設資料載入及路由。下面我們來看看相同的功能,在 5.0.0 GA 版中是如何配置和使用的,具體配置如下:
rules: - !SHARDING tables: t_order: actualDataNodes: ms_ds_${0..1}.t_order_${0..1} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: t_order_inline t_order_item: actualDataNodes: ms_ds_${0..1}.t_order_item_${0..1} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: t_order_item_inline bindingTables: - t_order,t_order_item broadcastTables: - t_config defaultDatabaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: database_inline defaultTableStrategy: none: shardingAlgorithms: database_inline: type: INLINE props: algorithm-expression: ms_ds_${user_id % 2} t_order_inline: type: INLINE props: algorithm-expression: t_order_${order_id % 2} t_order_item_inline: type: INLINE props: algorithm-expression: t_order_item_${order_id % 2} - !READWRITE_SPLITTING dataSources: ms_ds_0: writeDataSourceName: ds_0 readDataSourceNames: - ds_0_slave_0 - ds_0_slave_1 loadBalancerName: ROUND_ROBIN ms_ds_1: writeDataSourceName: ds_1 readDataSourceNames: - ds_1_slave_0 - ds_1_slave_1 loadBalancerName: ROUND_ROBIN - !ENCRYPT encryptors: aes_encryptor: type: AES props: aes-key-value: 123456abc tables: t_order: columns: content: plainColumn: content_plain cipherColumn: content_cipher encryptor: aes_encryptor t_user: columns: telephone: plainColumn: telephone_plain cipherColumn: telephone_cipher encryptor: aes_encryptor
首先,從配置上來看,5.0.0 GA 版和 4.1.1 GA 版最大的區別在於不同功能之間的關係,它們是一個平級關係,不存在 4.1.1 GA 中的功能依賴,每個功能都可以通過可插拔的方式靈活載入和解除安裝。其次,這些功能在整合使用時,使用類似於管道的傳遞方式,例如:讀寫分離規則基於兩組主從關係,聚合出兩個邏輯資料來源,分別是ms_ds_0和ms_ds_1。資料分片規則基於讀寫分離聚合出的邏輯資料來源,配置資料分片規則,從而又聚合出邏輯表 t_order。加解密功能則關注於列和值的改寫,面向資料分片功能聚合出的邏輯表,配置加解密規則。讀寫分離、資料分片和加解密功能層層傳遞,通過裝飾模式,不斷對功能進行增加。
為了對比 4.1.1 GA 版功能,我們執行同樣的初始化語句、插入語句和查詢語句對 5.0.0 GA 版進行測試。
CREATE TABLE t_order(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)); # Logic SQL: CREATE TABLE t_order(order_id INT(11) PRIMARY KEY, user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_0(order_id INT(11) PRIMARY KEY, user_id INT(11), content_cipher VARCHAR(100), content_plain VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_1(order_id INT(11) PRIMARY KEY, user_id INT(11), content_cipher VARCHAR(100), content_plain VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_0(order_id INT(11) PRIMARY KEY, user_id INT(11), content_cipher VARCHAR(100), content_plain VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_1(order_id INT(11) PRIMARY KEY, user_id INT(11), content_cipher VARCHAR(100), content_plain VARCHAR(100)) CREATE TABLE t_order_item(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)); # Logic SQL: CREATE TABLE t_order_item(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_item_0(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_order_item_1(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_item_0(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) # Actual SQL: ds_0 ::: CREATE TABLE t_order_item_1(item_id INT(11) PRIMARY KEY, order_id INT(11), user_id INT(11), content VARCHAR(100)) CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone VARCHAR(100)); # Logic SQL: CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone VARCHAR(100)) # Actual SQL: ds_1 ::: CREATE TABLE t_user(user_id INT(11) PRIMARY KEY, telephone_cipher VARCHAR(100), telephone_plain VARCHAR(100))
在 5.0.0 GA 版中,增加了對加解密 DDL 語句改寫的支援,因此在建立t_order過程中,不論是資料分片、讀寫分離還是加解密,路由和改寫都能夠正常執行。t_user表從日誌來看,被路由到 ds_1資料來源執行,在 5.0.0 GA 版中,t_user屬於單表,無需使用者配置資料來源,在執行建表語句時,會隨機選擇一個資料來源進行路由。對於單表,我們需要保證它在邏輯庫中唯一,從而保證路由結果的準確性。
INSERT INTO t_order(order_id, user_id, content) VALUES(1, 1, 'TEST11'), (2, 2, 'TEST22'), (3, 3, 'TEST33'); # Logic SQL: INSERT INTO t_order(order_id, user_id, content) VALUES(1, 1, 'TEST11'), (2, 2, 'TEST22'), (3, 3, 'TEST33') # Actual SQL: ds_1 ::: INSERT INTO t_order_1(order_id, user_id, content_cipher, content_plain) VALUES(1, 1, '3qpLpG5z6AWjRX2sRKjW2g==', 'TEST11'), (3, 3, 'oVkQieUbS3l/85axrf5img==', 'TEST33') # Actual SQL: ds_0 ::: INSERT INTO t_order_0(order_id, user_id, content_cipher, content_plain) VALUES(2, 2, 'mzIhTs2MD3dI4fqCc5nF/Q==', 'TEST22') INSERT INTO t_order_item(item_id, order_id, user_id, content) VALUES(1, 1, 1, 'TEST11'), (2, 2, 2, 'TEST22'), (3, 3, 3, 'TEST33'); # Logic SQL: INSERT INTO t_order_item(item_id, order_id, user_id, content) VALUES(1, 1, 1, 'TEST11'), (2, 2, 2, 'TEST22'), (3, 3, 3, 'TEST33') # Actual SQL: ds_1 ::: INSERT INTO t_order_item_1(item_id, order_id, user_id, content) VALUES(1, 1, 1, 'TEST11'), (3, 3, 3, 'TEST33') # Actual SQL: ds_0 ::: INSERT INTO t_order_item_0(item_id, order_id, user_id, content) VALUES(2, 2, 2, 'TEST22') INSERT INTO t_user(user_id, telephone) VALUES(1, '11111111111'), (2, '22222222222'), (3, '33333333333'); # Logic SQL: INSERT INTO t_user(user_id, telephone) VALUES(1, '11111111111'), (2, '22222222222'), (3, '33333333333') # Actual SQL: ds_1 ::: INSERT INTO t_user(user_id, telephone_cipher, telephone_plain) VALUES(1, 'jFZBCI7G9ggRktThmMlClQ==', '11111111111'), (2, 'lWrg5gaes8eptaQkUM2wtA==', '22222222222'), (3, 'jeCwC7gXus4/1OflXeGW/w==', '33333333333')
在對 t_user表執行資料插入時,會根據後設資料中儲存的資訊來進行自動路由,由於前一個步驟中t_user路由到了ds_1資料來源,因此其他語句會根據t_user: ds_1這樣的後設資料進行路由處理。
SELECT * FROM t_order WHERE user_id = 1 AND order_id = 1; # Logic SQL: SELECT * FROM t_order WHERE user_id = 1 AND order_id = 1 # Actual SQL: ds_1_slave_0 ::: SELECT `t_order_1`.`order_id`, `t_order_1`.`user_id`, `t_order_1`.`content_cipher` AS `content` FROM t_order_1 WHERE user_id = 1 AND order_id = 1 SELECT * FROM t_order_item WHERE user_id = 1 AND order_id = 1; # Logic SQL: SELECT * FROM t_order_item WHERE user_id = 1 AND order_id = 1 # Actual SQL: ds_1_slave_1 ::: SELECT * FROM t_order_item_1 WHERE user_id = 1 AND order_id = 1 SELECT * FROM t_user WHERE user_id = 1; # Logic SQL: SELECT * FROM t_user WHERE user_id = 1 # Actual SQL: ds_1_slave_0 ::: SELECT `t_user`.`user_id`, `t_user`.`telephone_cipher` AS `telephone` FROM t_user WHERE user_id = 1
在執行查詢語句時,我們可以發現,t_user表被路由到了ds_1_slave_0資料來源,實現了單表的讀寫分離。在 5.0.0 GA 版中,Apache ShardingSphere 核心通過後設資料載入,內部維護了單表的資料分佈資訊,並充分考慮了不同功能組合使用的場景,使得單表也能夠完美支援。
5.0.0 GA 版中還有很多新功能,升級指南中的案例只是挑選了兩個 GA 版本中都能夠支援的一些功能進行對比,期望能夠幫助大家理解新功能,並順利地實現功能升級。如果大家對可插拔架構、Federation 執行引擎或者其他的新功能感興趣,歡迎參考官方文件進行測試使用。
結語
歷經兩年時間的打磨,Apache ShardingSphere 以全新的姿態展示在大家面前,可插拔架構核心為所有的開發者提供了無限的可能性,未來,我們將基於可插拔架構核心,不斷開拓新的功能,豐富 Apache ShardingSphere 生態系統。Federation 執行引擎則開啟了分散式查詢的大門,後續我們將專注於記憶體及效能的優化,為大家提供更可靠、更高效的分散式查詢能力。最後,也歡迎大家能夠積極地參與進來,共同推動 Apache ShardingSphere 的發展。
參考文件
歡迎新增掃碼社群經理微信,回覆“交流群”進群和眾多 ShardingSphere 愛好者一同交流探討!