1. 程式人生 > 實用技巧 >如何系統剖析 ShardingSphere 的程式碼結構?

如何系統剖析 ShardingSphere 的程式碼結構?

如何系統剖析 ShardingSphere 的程式碼結構?

在閱讀開源框架時,我們碰到的一大問題在於,常常會不由自主地陷入程式碼的細節而無法把握框架程式碼的整體結構。市面上主流的、被大家所熟知而廣泛應用的程式碼框架肯定考慮得非常周全,其程式碼結構不可避免存在一定的複雜性。對 ShardingSphere 而言,情況也是一樣,我們發現 ShardingSphere 原始碼的一級程式碼結構目錄就有 15 個,而這些目錄內部包含的具體 Maven 工程則多達 50 餘個:

如何快速把握 ShardingSphere 的程式碼結構呢?這是我們剖析原始碼時需要回答的第一個問題,為此我們需要梳理剖析 ShardingSphere 框架程式碼結構的系統方法。梳理出應對這一問題的六大系統方法(如下圖):

下來,我們將結合 ShardingSphere 框架對這些方法進行展開。

基於可擴充套件性設計閱讀原始碼
ShardingSphere 在設計上採用了微核心架構模式來確保系統具有高度的可擴充套件性,並使用了 JDK 提供的 SPI 機制來具體實現微核心架構。在 ShardingSphere 原始碼的根目錄下,存在一個獨立工程 shardingsphere-spi。顯然,從命名上看,這個工程中應該包含了 ShardingSphere 實現 SPI 的相關程式碼。該工程中存在一個 TypeBasedSPI 介面,它的類層結構比較豐富,後面將要講到的很多核心介面都繼承了該介面,包括實現配置中心的 ConfigCenter、註冊中心的 RegistryCenter 等,如下所示:

這些介面的實現都遵循了 JDK 提供的 SPI 機制。在我們閱讀 ShardingSphere 的各個程式碼工程時,一旦發現在程式碼工程中的 META-INF/services 目錄裡建立了一個以服務介面命名的檔案,就說明這個程式碼工程中包含了用於實現擴充套件性的 SPI 定義。

在 ShardingSphere 中,大量使用了微核心架構和 SPI 機制實現系統的擴充套件性。只要掌握了微核心架構的基本原理以及 SPI 的實現方式就會發現,原來在 ShardingSphere 中,很多程式碼結構上的組織方式就是為了滿足這些擴充套件性的需求。ShardingSphere 中實現微核心架構的方式就是直接對 JDK 的 ServiceLoader 類進行一層簡單的封裝,並新增屬性設定等自定義的功能,其本身並沒有太多複雜的內容。

當然,可擴充套件性的表現形式不僅僅只有微核心架構一種。在 ShardingSphere 中也大量使用了回撥(Callback)機制以及多種支援擴充套件性的設計模式。掌握這些機制和模式也有助於更好地閱讀 ShardingSphere 原始碼。

基於分包設計原則閱讀原始碼
分包(Package)設計原則可以用來設計和規劃開源框架的程式碼結構。對於一個包結構而言,最核心的設計要點就是高內聚和低耦合。我們剛開始閱讀某個框架的原始碼時,為了避免過多地扎進細節而只關注某一個具體元件,同樣可以使用這些原則來管理我們的學習預期。

以 ShardingSphere 為例,我們在分析它的路由引擎時發現了兩個程式碼工程,一個是 sharding-core-route,一個是 sharding-core-entry。從程式碼結構上講,儘管這兩個程式碼工程都不是直接面向業務開發人員,但 sharding-core-route 屬於路由引擎的底層元件,包含了路由引擎的核心類 ShardingRouter。

而 sharding-core-entry 則位於更高的層次,提供了 PreparedQueryShardingEngine 和 SimpleQueryShardingEngine 類,分包結構如下所示:

圖中我們可以看到兩個清晰的程式碼結構層次關係,這是 ShardingSphere 中普遍採用的分包原則中,具有代表性的一種,即根據類的所屬層級來組織包結構。

基於基礎開發規範閱讀原始碼
對於 ShardingSphere 而言,在梳理它的程式碼結構時有一個非常好的切入點,那就是基於 JDBC 規範。我們知道 ShardingSphere 在設計上一開始就完全相容 JDBC 規範,它對外暴露的一套分片操作介面與 JDBC 規範中所提供的介面完全一致。只要掌握了 JDBC 中關於 DataSource、Connection、Statement 等核心介面的使用方式,就可以非常容易地把握 ShardingSphere 中暴露給開發人員的程式碼入口,進而把握整個框架的程式碼結構。

我們來看這方面的示例,如果你是剛接觸到 ShardingSphere 原始碼,要想找到 SQL 執行入口是一件有一定難度的事情。在 ShardingSphere 中,存在一個 ShardingDataSourceFactory 工廠類,專門用來建立 ShardingDataSource。ShardingDataSource 就是一個 JDBC 規範中的 DataSource 實現類:

public final class ShardingDataSourceFactory {
 public static DataSource createDataSource(
 final Map<String, DataSource> dataSourceMap, final ShardingRuleConfiguration shardingRuleConfig, final Properties props) throws SQLException {
 return new ShardingDataSource(dataSourceMap, new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()), props);
 }
}

通過這個工廠類,我們很容易就找到了建立支援分片機制的 DataSource 入口,從而引出其背後的 ShardingConnection、ShardingStatement 等類。

事實上,在 ShardingSphere 中存在一批 DataSourceFactory 工廠類以及對應的 DataSource 類:

在閱讀 ShardingSphere 原始碼時,JDBC 規範所提供的核心介面及其實現類,為我們高效梳理程式碼入口和組織方式提供了一種途徑。

基於核心執行流程閱讀原始碼

事實上,還有一個比較容易理解和把握的方法可以幫我們梳理程式碼結構,這就是程式碼的執行流程。任何系統行為都可以認為是流程的組合。通過分析,看似複雜的程式碼結構一般都能梳理出一條貫穿全域性的主流程。只要我們抓住這條主流程,就能把握框架的整體程式碼結構。

那麼,對於 ShardingSphere 框架而言,什麼才是它的主流程呢?這個問題其實不難回答。事實上,JDBC 規範為我們實現資料儲存和訪問提供了基本的開發流程。我們可以從 DataSource 入手,逐步引入 Connection、Statement 等物件,並完成 SQL 執行的主流程。這是從框架提供的核心功能角度梳理的一種主流程。

對於框架內部的程式碼組織結構而言,實際上也存在著核心流程的概念。最典型的就是 ShardingSphere 的分片引擎結構,整個分片引擎執行流程可以非常清晰的分成五個組成部分,分別是解析引擎、路由引擎、改寫引擎、執行引擎和歸併引擎

ShardingSphere 對每個引擎都進行了明確地命名,在程式碼工程的組織結構上也做了對應的約定,例如 sharding-core-route 工程用於實現路由引擎;sharding-core-execute 工程用於實現執行引擎;sharding-core-merge 工程用於實現歸併引擎等。這是從框架內部實現機制角度梳理的一種主流程。

在軟體建模領域,可以通過一些工具和手段對程式碼執行流程進行視覺化,例如 UML 中的活動圖和時序圖。在後續的課時中,我們會基於這些工具幫你梳理 ShardingSphere 中很多有待挖掘的程式碼執行流程。

基於框架演進過程閱讀原始碼

ShardingSphere 經歷了從 1.X 到 4.X 版本的發展,功能越來越豐富,目前的程式碼結構已經比較複雜。但我相信 ShardingSphere 的開發人員也不是一開始就把 ShardingSphere 設計成現在這種程式碼結構。換個角度,如果我們自己來設計這樣一個框架,通常會採用一定的策略,從簡單到複雜、從核心功能到輔助機制,逐步實現和完善框架,這也是軟體開發的一個基本規律。針對這個角度,當我們想要解讀 ShardingSphere 的程式碼結構而又覺得無從下手時,可以考慮一個核心問題:如何從易到難對框架進行逐步拆解

我們首先介紹的是分庫分表功能,然後擴充套件到讀寫分離,然後再到資料脫敏。從這些功能的演進我們可以推演其背後的程式碼結構的演進。這裡以資料脫敏功能的實現過程為例來解釋這一觀點。

在 ShardingSphere 中,資料脫敏功能的實現實際上並不是獨立的,而是依賴於 SQL 改寫引擎。我們可以快速來到 BaseShardingEngine 類的 rewriteAndConvert 方法中:

 private Collection<RouteUnit> rewriteAndConvert(final String sql, final List<Object> parameters, final SQLRouteResult sqlRouteResult) {
 //構建SQLRewriteContext
  SQLRewriteContext sqlRewriteContext = new SQLRewriteContext(metaData.getRelationMetas(), sqlRouteResult.getSqlStatementContext(), sql, parameters);

  //構建ShardingSQLRewriteContextDecorator對SQLRewriteContext進行裝飾
  new ShardingSQLRewriteContextDecorator(shardingRule, sqlRouteResult).decorate(sqlRewriteContext);

  //判斷是否根據資料脫敏列進行查詢
  boolean isQueryWithCipherColumn = shardingProperties.<Boolean>getValue(ShardingPropertiesConstant.QUERY_WITH_CIPHER_COLUMN);

 //構建EncryptSQLRewriteContextDecorator對SQLRewriteContext進行裝飾
  new EncryptSQLRewriteContextDecorator(shardingRule.getEncryptRule(), isQueryWithCipherColumn).decorate(sqlRewriteContext);

  //生成SQLTokens
  sqlRewriteContext.generateSQLTokens();
 …

 return result;

 }

注意,這裡基於裝飾器模式實現了兩個 SQLRewriteContextDecorator,一個是 ShardingSQLRewriteContextDecorator,另一個是 EncryptSQLRewriteContextDecorator,而後者是在前者的基礎上完成裝飾工作。也就是說,我們首先可以單獨使用 ShardingSQLRewriteContextDecorator 來完成對 SQL 的改寫操作。

隨著架構的演進,我們也可以在原有 EncryptSQLRewriteContextDecorator 的基礎上新增新的面向資料脫敏的功能,這就體現了一種架構演進的過程。通過閱讀這兩個裝飾器類,以及 SQL 改寫上下文物件 SQLRewriteContext,我們就能更好地把握程式碼的設計思想和實現原理:

基於通用外部元件閱讀原始碼

在 ShardingSphere 中集成了一批優秀的開源框架,包括用於實現配置中心和註冊中心的Zookeeper、Apollo、Nacos,用於實現鏈路跟蹤的 SkyWalking,用於實現分散式事務的 Atomikos 和 Seata 等。

我們先以分散式事務為例,ShardingSphere 提供了一個 sharding-transaction-core 程式碼工程,用於完成對分散式事務的抽象。然後又針對基於兩階段提交的場景,提供了 sharding-transaction-2pc 程式碼工程,以及針對柔性事務提供了 sharding-transaction-base 程式碼工程。而在 sharding-transaction-2pc 程式碼工程內部,又包含了如下所示的 5 個子程式碼工程。

在翻閱這些程式碼工程時,會發現每個工程中的類都很少,原因就在於,這些類都只是完成與第三方框架的整合而已。所以,只要我們對這些第三方框架有一定了解,閱讀這部分程式碼就會顯得非常簡單。

再舉一個例子,我們知道 ZooKeeper 可以同時用來實現配置中心和註冊中心。作為一款主流的分散式協調框架,基本的工作原理就是採用了它所提供的臨時節點以及監聽機制。基於 ZooKeeper 的這一原理,我們可以把當前 ShardingSphere 所使用的各個 DataSource 註冊到 ZooKeeper 中,並根據 DataSource 的執行時狀態來動態對資料庫例項進行治理,以及實現訪問熔斷機制。事實上,ShardingSphere 能做到這一點,依賴的就是 ZooKeeper 所提供的基礎功能。只要我們掌握了這些功能,理解這塊程式碼就不會很困難,而 ShardingSphere 本身並沒有使用 ZooKeeper 中任何複雜的功能。

如何梳理ShardingSphere中的核心技術體系?

ShardingSphere 中包含了很多技術體系,在本課程中,我們將從基礎架構、分片引擎、分散式事務以及治理與整合等 4 個方面對這些技術體系進行闡述。

基礎架構

這裡定義基礎架構的標準是,屬於基礎架構類的技術可以脫離 ShardingSphere 框架本身獨立執行。也就是說,這些技術可以單獨抽離出來,供其他框架直接使用。我們認為 ShardingSphere 所實現的微核心架構和分散式主鍵可以歸到基礎架構。

分片引擎

分片引擎是 ShardingSphere 最核心的技術體系,包含了解析引擎、路由引擎、改寫引擎、執行引擎、歸併引擎和讀寫分離等 6 大主題,我們對每個主題都會詳細展開。分片引擎在整個 ShardingSphere 原始碼解析內容中佔有最大篇幅。

對於解析引擎而言,我們重點梳理 SQL 解析流程所包含的各個階段;對於路由引擎,我們將在介紹路由基本原理的基礎上,給出資料訪問的分片路由和廣播路由,以及如何在路由過程中整合多種分片策略和分片演算法的實現過程;改寫引擎相對比較簡單,我們將圍繞如何基於裝飾器模式完成 SQL 改寫實現機制這一主題展開討論;而對於執行引擎,首先需要梳理和抽象分片環境下 SQL 執行的整體流程,然後把握 ShardingSphere 中的 Executor 執行模型;在歸併引擎中,我們將分析資料歸併的型別,並闡述各種歸併策略的實現過程;最後,我們將關注普通主從架構和分片主從架構下讀寫分離的實現機制。

分散式事務

針對分散式事務,我們需要理解 ShardingSphere 中對分散式事務的抽象過程,然後系統分析在 ShardingSphere 中如何基於各種第三方框架整合強一致性事務和柔性事務支援的實現原理。

治理與整合

在治理和整合部分,從原始碼角度討論的話題包括資料脫敏、配置中心、註冊中心、鏈路跟蹤以及系統整合。

對於資料脫敏,我們會在改寫引擎的基礎上給出如何實現低侵入性的資料脫敏方案;配置中心用來完成配置資訊的動態化管理,而註冊中心則實現了資料庫訪問熔斷機制,這兩種技術可以採用通用的框架進行實現,只是面向了不同的業務場景,我們會分析通用的實現原理以及面向業務場景的差異性;ShardingSphere 中實現了一系列的 Hook 機制,我們將基於這些 Hook 機制以及 OpenTracing 協議來剖析實現資料訪問鏈路跟蹤的工作機制;當然,作為一款主流的開源框架,ShardingSphere 也完成與 Spring 以及 SpringBoot 的無縫整合,對系統整合方式的分析可以更好地幫助我們使用這個框架。

從原始碼解析到日常開發

通過系統講解框架原始碼來幫助你深入理解 ShardingSphere 實現原理是本課程的一大目標,但也不是唯一目標。作為擴充套件,我們希望通過對 ShardingSphere 這款優秀開源框架的學習,掌握系統架構設計和實現過程中的方法和技巧,並指導日常的開發工作。例如,在下一課時介紹微核心架構時,我們還將重點描述基於 JDK 所提供的 SPI 機制來實現系統的擴充套件性,而這種實現機制完全可以應用到日常開發過程中。

這是一個從原始碼分析到日常開發的過程,而且是一個不斷演進的過程。所謂理論指導實踐,我們需要從紛繁複雜的技術知識體系和各種層出不窮的工具框架中抓住其背後的原理,然後做到用自己的語言和方法對這些原理進行闡述,也就是能夠構建屬於你自己的技術知識體系。