成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文快速入門分庫分表中間件Sharding-JDBC (必修課)

數據庫 其他數據庫
下邊我們介紹一下 Sharding-JDBC框架和快速的搭建一個分庫分表案例,為講解后續功能點準備好環境。

作為Sharding-JDBC 分庫分表實戰系列的開篇文章,我們在前文中回顧了一下分庫分表的基礎知識,對分庫分表的拆分方式有了一定的了解,下邊我們介紹一下 Sharding-JDBC框架和快速的搭建一個分庫分表案例,為講解后續功能點準備好環境。

一、Sharding-JDBC 簡介

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

隨著版本的不斷更迭 ShardingSphere 的核心功能也變得多元化起來。從最開始 Sharding-JDBC 1.0 版本只有數據分片,到 Sharding-JDBC 2.0 版本開始支持數據庫治理(注冊中心、配置中心等等),再到 Sharding-JDBC 3.0版本又加分布式事務 (支持 Atomikos、Narayana、Bitronix、Seata),如今已經迭代到了 Sharding-JDBC 4.0 版本。

在這里插入圖片描述

現在的 ShardingSphere 不單單是指某個框架而是一個生態圈,這個生態圈 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 這三款開源的分布式數據庫中間件解決方案所構成。

ShardingSphere 的前身就是 Sharding-JDBC,所以它是整個框架中最為經典、成熟的組件,我們先從 Sharding-JDBC 框架入手學習分庫分表。

二、核心概念

在開始 Sharding-JDBC分庫分表具體實戰之前,我們有必要先了解分庫分表的一些核心概念。

分片

一般我們在提到分庫分表的時候,大多是以水平切分模式(水平分庫、分表)為基礎來說的,數據分片將原本一張數據量較大的表 t_order 拆分生成數個表結構完全一致的小數據量表 t_order_0、t_order_1、···、t_order_n,每張表只存儲原大表中的一部分數據,當執行一條SQL時會通過 分庫策略、分片策略 將數據分散到不同的數據庫、表內。

在這里插入圖片描述

數據節點

數據節點是分庫分表中一個不可再分的最小數據單元(表),它由數據源名稱和數據表組成,例如上圖中 order_db_1.t_order_0、order_db_2.t_order_1 就表示一個數據節點。

邏輯表

邏輯表是指一組具有相同邏輯和數據結構表的總稱。比如我們將訂單表t_order 拆分成 t_order_0 ···  t_order_9 等 10張表。此時我們會發現分庫分表以后數據庫中已不在有 t_order 這張表,取而代之的是 t_order_n,但我們在代碼中寫 SQL 依然按 t_order 來寫。此時 t_order 就是這些拆分表的邏輯表。

真實表

真實表也就是上邊提到的 t_order_n 數據庫中真實存在的物理表。

分片鍵

用于分片的數據庫字段。我們將 t_order 表分片以后,當執行一條SQL時,通過對字段 order_id 取模的方式來決定,這條數據該在哪個數據庫中的哪個表中執行,此時 order_id 字段就是 t_order 表的分片健。

在這里插入圖片描述

這樣以來同一個訂單的相關數據就會存在同一個數據庫表中,大幅提升數據檢索的性能,不僅如此 sharding-jdbc 還支持根據多個字段作為分片健進行分片。

分片算法

上邊我們提到可以用分片健取模的規則分片,但這只是比較簡單的一種,在實際開發中我們還希望用 >=、<=、>、<、BETWEEN 和 IN 等條件作為分片規則,自定義分片邏輯,這時就需要用到分片策略與分片算法。

從執行 SQL 的角度來看,分庫分表可以看作是一種路由機制,把 SQL 語句路由到我們期望的數據庫或數據表中并獲取數據,分片算法可以理解成一種路由規則。

咱們先捋一下它們之間的關系,分片策略只是抽象出的概念,它是由分片算法和分片健組合而成,分片算法做具體的數據分片邏輯。

分庫、分表的分片策略配置是相對獨立的,可以各自使用不同的策略與算法,每種策略中可以是多個分片算法的組合,每個分片算法可以對多個分片健做邏輯判斷。

分片算法和分片策略的關系

注意:sharding-jdbc 并沒有直接提供分片算法的實現,需要開發者根據業務自行實現。

sharding-jdbc 提供了4種分片算法:

1、精確分片算法

精確分片算法(PreciseShardingAlgorithm)用于單個字段作為分片鍵,SQL中有 = 與 IN 等條件的分片,需要在標準分片策略(StandardShardingStrategy )下使用。

2、范圍分片算法

范圍分片算法(RangeShardingAlgorithm)用于單個字段作為分片鍵,SQL中有 BETWEEN AND、>、<、>=、<=  等條件的分片,需要在標準分片策略(StandardShardingStrategy )下使用。

3、復合分片算法

復合分片算法(ComplexKeysShardingAlgorithm)用于多個字段作為分片鍵的分片操作,同時獲取到多個分片健的值,根據多個字段處理業務邏輯。需要在復合分片策略(ComplexShardingStrategy )下使用。

4、Hint分片算法

Hint分片算法(HintShardingAlgorithm)稍有不同,上邊的算法中我們都是解析SQL 語句提取分片鍵,并設置分片策略進行分片。但有些時候我們并沒有使用任何的分片鍵和分片策略,可還想將 SQL 路由到目標數據庫和表,就需要通過手動干預指定SQL的目標數據庫和表信息,這也叫強制路由。

分片策略

上邊講分片算法的時候已經說過,分片策略是一種抽象的概念,實際分片操作的是由分片算法和分片健來完成的。

1、標準分片策略

標準分片策略適用于單分片鍵,此策略支持 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 兩個分片算法。

其中 PreciseShardingAlgorithm 是必選的,用于處理 = 和 IN 的分片。RangeShardingAlgorithm 是可選的,用于處理BETWEEN AND, >, <,>=,<= 條件分片,如果不配置RangeShardingAlgorithm,SQL中的條件等將按照全庫路由處理。

2、復合分片策略

復合分片策略,同樣支持對 SQL語句中的 =,>, <, >=, <=,IN和 BETWEEN AND 的分片操作。不同的是它支持多分片鍵,具體分配片細節完全由應用開發者實現。

3、行表達式分片策略

行表達式分片策略,支持對 SQL語句中的 = 和 IN 的分片操作,但只支持單分片鍵。這種策略通常用于簡單的分片,不需要自定義分片算法,可以直接在配置文件中接著寫規則。

t_order_$->{t_order_id % 4} 代表 t_order 對其字段 t_order_id取模,拆分成4張表,而表名分別是t_order_0 到 t_order_3。

4、Hint分片策略

Hint分片策略,對應上邊的Hint分片算法,通過指定分片健而非從 SQL中提取分片健的方式進行分片的策略。

分布式主鍵

數據分⽚后,不同數據節點⽣成全局唯⼀主鍵是⾮常棘⼿的問題,同⼀個邏輯表(t_order)內的不同真實表(t_order_n)之間的⾃增鍵由于⽆法互相感知而產⽣重復主鍵。

盡管可通過設置⾃增主鍵 初始值 和 步⻓ 的⽅式避免ID碰撞,但這樣會使維護成本加大,乏完整性和可擴展性。如果后去需要增加分片表的數量,要逐一修改分片表的步長,運維成本非常高,所以不建議這種方式。

實現分布式主鍵⽣成器的方式很多,可以參考我之前寫的《9種分布式ID生成方式》。

為了讓上手更加簡單,ApacheShardingSphere 內置了UUID、SNOWFLAKE 兩種分布式主鍵⽣成器,默認使⽤雪花算法(snowflake)⽣成64bit的⻓整型數據。不僅如此它還抽離出分布式主鍵⽣成器的接口,⽅便我們實現⾃定義的⾃增主鍵⽣成算法。

廣播表

廣播表:存在于所有的分片數據源中的表,表結構和表中的數據在每個數據庫中均完全一致。一般是為字典表或者配置表 t_config,某個表一旦被配置為廣播表,只要修改某個數據庫的廣播表,所有數據源中廣播表的數據都會跟著同步。

綁定表

綁定表:那些分片規則一致的主表和子表。比如:t_order 訂單表和 t_order_item 訂單服務項目表,都是按 order_id 字段分片,因此兩張表互為綁定表關系。

那綁定表存在的意義是啥呢?

通常在我們的業務中都會使用 t_order 和 t_order_item 等表進行多表聯合查詢,但由于分庫分表以后這些表被拆分成N多個子表。如果不配置綁定表關系,會出現笛卡爾積關聯查詢,將產生如下四條SQL。 

  1. SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id   
  2. SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id   
  3. SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id   
  4. SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id  

笛卡爾積查詢

而配置綁定表關系后再進行關聯查詢時,只要對應表分片規則一致產生的數據就會落到同一個庫中,那么只需 t_order_0 和 t_order_item_0 表關聯即可。 

  1. SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id   
  2. SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id  

綁定表關系

注意:在關聯查詢時 t_order 它作為整個聯合查詢的主表。所有相關的路由計算都只使用主表的策略,t_order_item 表的分片相關的計算也會使用 t_order 的條件,所以要保證綁定表之間的分片鍵要完全相同。

三、和JDBC的貓膩

從名字上不難看出,Sharding-JDBC 和 JDBC有很大關系,我們知道 JDBC 是一種 Java 語言訪問關系型數據庫的規范,其設計初衷就是要提供一套用于各種數據庫的統一標準,不同廠家共同遵守這套標準,并提供各自的實現方案供應用程序調用。

在這里插入圖片描述

但其實對于開發人員而言,我們只關心如何調用 JDBC API 來訪問數據庫,只要正確使用 DataSource、Connection、Statement 、ResultSet 等 API 接口,直接操作數據庫即可。所以如果想在 JDBC 層面實現數據分片就必須對現有的 API 進行功能拓展,而 Sharding-JDBC 正是基于這種思想,重寫了 JDBC 規范并完全兼容了 JDBC 規范。

JDBC流程

對原有的 DataSource、Connection 等接口擴展成 ShardingDataSource、ShardingConnection,而對外暴露的分片操作接口與 JDBC 規范中所提供的接口完全一致,只要你熟悉 JDBC 就可以輕松應用 Sharding-JDBC 來實現分庫分表。

因此它適用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate,Mybatis,Spring JDBC Template 或直接使用的 JDBC。完美兼容任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP,Druid, HikariCP 等,幾乎對主流關系型數據庫都支持。

那 Sharding-JDBC 又是如何拓展這些接口的呢?想知道答案我們就的從源碼入手了,下邊我們以 JDBC API  中的 DataSource 為例看看它是如何被重寫擴展的。

數據源 DataSource 接口的核心作用就是獲取數據庫連接對象 Connection,我們看其內部提供了兩個獲取數據庫連接的方法 ,并且繼承了 CommonDataSource 和 Wrapper 兩個接口。 

  1. public interface DataSource  extends CommonDataSource, Wrapper {  
  2.   /**  
  3.    * <p>Attempts to establish a connection with the data source that  
  4.    * this {@code DataSource} object represents.  
  5.    * @return  a connection to the data source  
  6.    */  
  7.   Connection getConnection() throws SQLException;  
  8.   /**  
  9.    * <p>Attempts to establish a connection with the data source that  
  10.    * this {@code DataSource} object represents.  
  11.    * @param username the database user on whose behalf the connection is  
  12.    *  being made 
  13.    * @param password the user's password  
  14.    */  
  15.   Connection getConnection(String username, String password)  
  16.     throws SQLException;  

其中 CommonDataSource 是定義數據源的根接口這很好理解,而 Wrapper 接口則是拓展 JDBC 分片功能的關鍵。

由于數據庫廠商的不同,他們可能會各自提供一些超越標準 JDBC API 的擴展功能,但這些功能非 JDBC 標準并不能直接使用,而 Wrapper 接口的作用就是把一個由第三方供應商提供的、非 JDBC 標準的接口包裝成標準接口,也就是適配器模式。

既然講到了適配器模式就多啰嗦幾句,也方便后邊的理解。

適配器模式個種比較常用的設計模式,它的作用是將某個類的接口轉換成客戶端期望的另一個接口,使原本因接口不匹配(或者不兼容)而無法在一起工作的兩個類能夠在一起工作。比如用耳機聽音樂,我有個圓頭的耳機,可手機插孔卻是扁口的,如果我想要使用耳機聽音樂就必須借助一個轉接頭才可以,這個轉接頭就起到了適配作用。舉個栗子:假如我們 Target 接口中有 hello() 和 word() 兩個方法。 

  1. public interface Target {  
  2.     void hello();  
  3.     void world();  

可由于接口版本迭代Target 接口的 word() 方法可能會被廢棄掉或不被支持,Adaptee  類的 greet()方法將代替hello() 方法。 

  1. public class Adaptee {  
  2.     public void greet(){  
  3.     }  
  4.     public void world(){  
  5.     }  

但此時舊版本仍然有大量 word() 方法被使用中,解決此事最好的辦法就是創建一個適配器Adapter,這樣就適配了 Target 類,解決了接口升級帶來的兼容性問題。 

  1. public class Adapter extends Adaptee implements Target {  
  2.     @Override  
  3.     public void world() {      
  4.      }  
  5.     @Override  
  6.     public void hello() {  
  7.         super.greet();  
  8.     } 
  9.      @Override  
  10.     public void greet() {        
  11.      }  

而 Sharding-JDBC 提供的正是非 JDBC 標準的接口,所以它也提供了類似的實現方案,也使用到了 Wrapper 接口做數據分片功能的適配。除了 DataSource 之外,Connection、Statement、ResultSet 等核心對象也都繼承了這個接口。

下面我們通過 ShardingDataSource  類源碼簡單看下實現過程,下圖是繼承關系流程圖。

ShardingDataSource實現流程

ShardingDataSource  類它在原 DataSource 基礎上做了功能拓展,初始化時注冊了分片SQL路由包裝器、SQL重寫上下文和結果集處理引擎,還對數據源類型做了校驗,因為它要同時支持多個不同類型的數據源。到這好像也沒看出如何適配,那接著向上看 ShardingDataSource 的繼承類  AbstractDataSourceAdapter 。 

  1. @Getter  
  2. public class ShardingDataSource extends AbstractDataSourceAdapter {      
  3.     private final ShardingRuntimeContext runtimeContext;  
  4.     /**  
  5.      * 注冊路由、SQl重寫上下文、結果集處理引擎  
  6.      */  
  7.     static {  
  8.         NewInstanceServiceLoader.register(RouteDecorator.class);  
  9.         NewInstanceServiceLoader.register(SQLRewriteContextDecorator.class);  
  10.         NewInstanceServiceLoader.register(ResultProcessEngine.class);  
  11.     }  
  12.     /**  
  13.      * 初始化時校驗數據源類型 并根據數據源 map、分片規則、數據庫類型得到一個分片上下文,用來獲取數據庫連接  
  14.      */  
  15.     public ShardingDataSource(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Properties props) throws SQLException {  
  16.         super(dataSourceMap);  
  17.         checkDataSourceType(dataSourceMap);  
  18.         runtimeContext = new ShardingRuntimeContext(dataSourceMap, shardingRule, props, getDatabaseType());  
  19.     }  
  20.     private void checkDataSourceType(final Map<String, DataSource> dataSourceMap) {  
  21.         for (DataSource each : dataSourceMap.values()) {  
  22.             Preconditions.checkArgument(!(each instanceof MasterSlaveDataSource), "Initialized data sources can not be master-slave data sources.");  
  23.         }  
  24.     }  
  25.     /**  
  26.      * 數據庫連接  
  27.      */  
  28.     @Override  
  29.     public final ShardingConnection getConnection() {  
  30.         return new ShardingConnection(getDataSourceMap(), runtimeContext, TransactionTypeHolder.get());  
  31.     }  

AbstractDataSourceAdapter 抽象類內部主要獲取不同類型的數據源對應的數據庫連接對象,實現 AutoCloseable 接口是為在使用完資源后可以自動將這些資源關閉(調用 close方法),那再看看繼承類 AbstractUnsupportedOperationDataSource 。 

  1. @Getter  
  2. public abstract class AbstractDataSourceAdapter extends AbstractUnsupportedOperationDataSource implements AutoCloseable {    
  3.      private final Map<String, DataSource> dataSourceMap;      
  4.     private final DatabaseType databaseType;      
  5.     public AbstractDataSourceAdapter(final Map<String, DataSource> dataSourceMap) throws SQLException {  
  6.         this.dataSourceMap = dataSourceMap;  
  7.         databaseType = createDatabaseType();  
  8.     }  
  9.      public AbstractDataSourceAdapter(final DataSource dataSource) throws SQLException {  
  10.         dataSourceMap = new HashMap<>(1, 1);  
  11.         dataSourceMap.put("unique", dataSource);  
  12.         databaseType = createDatabaseType();  
  13.     }   
  14.      private DatabaseType createDatabaseType() throws SQLException {  
  15.         DatabaseType result = null 
  16.         for (DataSource each : dataSourceMap.values()) {  
  17.             DatabaseType databaseType = createDatabaseType(each);  
  18.             Preconditions.checkState(null == result || result == databaseType, String.format("Database type inconsistent with '%s' and '%s'", result, databaseType)); 
  19.              result = databaseType         }  
  20.         return result;  
  21.     }   
  22.      /**  
  23.      * 不同數據源類型獲取數據庫連接  
  24.      */  
  25.     private DatabaseType createDatabaseType(final DataSource dataSource) throws SQLException {  
  26.         if (dataSource instanceof AbstractDataSourceAdapter) {  
  27.             return ((AbstractDataSourceAdapter) dataSource).databaseType;  
  28.         }  
  29.         try (Connection connection = dataSource.getConnection()) {  
  30.             return DatabaseTypes.getDatabaseTypeByURL(connection.getMetaData().getURL());  
  31.         }  
  32.     }    
  33.      @Override  
  34.     public final Connection getConnection(final String username, final String password) throws SQLException {  
  35.         return getConnection();  
  36.     }    
  37.  
  38.     @Override  
  39.     public final void close() throws Exception {  
  40.         close(dataSourceMap.keySet());  
  41.     }  

AbstractUnsupportedOperationDataSource 實現DataSource 接口并繼承了 WrapperAdapter 類,它內部并沒有什么具體方法只起到橋接的作用,但看著是不是和我們前邊講適配器模式的例子方式有點相似。 

  1. public abstract class AbstractUnsupportedOperationDataSource extends WrapperAdapter implements DataSource {    
  2.     @Override  
  3.     public final int getLoginTimeout() throws SQLException {  
  4.         throw new SQLFeatureNotSupportedException("unsupported getLoginTimeout()");  
  5.     }   
  6.      @Override  
  7.     public final void setLoginTimeout(final int seconds) throws SQLException {  
  8.         throw new SQLFeatureNotSupportedException("unsupported setLoginTimeout(int seconds)");  
  9.     }  

WrapperAdapter 是一個包裝器的適配類,實現了 JDBC 中的 Wrapper 接口,其中有兩個核心方法 recordMethodInvocation 用于添加需要執行的方法和參數,而 replayMethodsInvocation 則將添加的這些方法和參數通過反射執行。仔細看不難發現兩個方法中都用到了 JdbcMethodInvocation類。 

  1. public abstract class WrapperAdapter implements Wrapper {      
  2.     private final Collection<JdbcMethodInvocation> jdbcMethodInvocations = new ArrayList<>();   
  3.     /**  
  4.      * 添加要執行的方法  
  5.      */  
  6.     @SneakyThrows 
  7.      public final void recordMethodInvocation(final Class<?> targetClass, final String methodName, final Class<?>[] argumentTypes, final Object[] arguments) {  
  8.         jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments));  
  9.     }  
  10.      /**  
  11.      * 通過反射執行 上邊添加的方法  
  12.      */  
  13.     public final void replayMethodsInvocation(final Object target) {  
  14.         for (JdbcMethodInvocation each : jdbcMethodInvocations) {  
  15.             each.invoke(target);  
  16.         }  
  17.     }  

JdbcMethodInvocation 類主要應用反射通過傳入的 method 方法和 arguments 參數執行對應的方法,這樣就可以通過 JDBC API 調用非 JDBC 方法了。 

  1. @RequiredArgsConstructor  
  2. public class JdbcMethodInvocation {    
  3.      @Getter  
  4.     private final Method method;      
  5.     @Getter 
  6.      private final Object[] arguments;     
  7.      /**  
  8.      * Invoke JDBC method.  
  9.      *   
  10.      * @param target target object  
  11.      */  
  12.     @SneakyThrows  
  13.     public void invoke(final Object target) {  
  14.         method.invoke(target, arguments);  
  15.     }  

那 Sharding-JDBC 拓展 JDBC API 接口后,在新增的分片功能里又做了哪些事情呢?

一張表經過分庫分表后被拆分成多個子表,并分散到不同的數據庫中,在不修改原業務 SQL 的前提下,Sharding-JDBC 就必須對 SQL進行一些改造才能正常執行。

大致的執行流程:SQL 解析 -> 執⾏器優化 -> SQL 路由 -> SQL 改寫 -> SQL 執⾏ -> 結果歸并 六步組成,一起瞅瞅每個步驟做了點什么。

SQL 解析

SQL解析過程分為詞法解析和語法解析兩步,比如下邊這條查詢用戶訂單的SQL,先用詞法解析將SQL拆解成不可再分的原子單元。在根據不同數據庫方言所提供的字典,將這些單元歸類為關鍵字,表達式,變量或者操作符等類型。

  1. SELECT order_no,price FROM t_order_ where user_id = 10086 and order_status > 0 

接著語法解析會將拆分后的SQL轉換為抽象語法樹,通過對抽象語法樹遍歷,提煉出分片所需的上下文,上下文包含查詢字段信息(Field)、表信息(Table)、查詢條件(Condition)、排序信息(Order By)、分組信息(Group By)以及分頁信息(Limit)等,并標記出 SQL中有可能需要改寫的位置。

抽象語法樹

執⾏器優化

執⾏器優化對SQL分片條件進行優化,處理像關鍵字 OR這種影響性能的壞味道。

SQL 路由

SQL 路由通過解析分片上下文,匹配到用戶配置的分片策略,并生成路由路徑。簡單點理解就是可以根據我們配置的分片策略計算出 SQL該在哪個庫的哪個表中執行,而SQL路由又根據有無分片健區分出 分片路由 和 廣播路由。

官方路由圖譜

有分⽚鍵的路由叫分片路由,細分為直接路由、標準路由和笛卡爾積路由這3種類型。

標準路由

標準路由是最推薦也是最為常⽤的分⽚⽅式,它的適⽤范圍是不包含關聯查詢或僅包含綁定表之間關聯查詢的SQL。

當 SQL分片健的運算符為 = 時,路由結果將落⼊單庫(表),當分⽚運算符是BETWEEN 或IN 等范圍時,路由結果則不⼀定落⼊唯⼀的庫(表),因此⼀條邏輯SQL最終可能被拆分為多條⽤于執⾏的真實SQL。 

  1. SELECT * FROM t_order  where t_order_id in (1,2) 

SQL路由處理后 :

  1. SELECT * FROM t_order_0  where t_order_id in (1,2)  
  2. SELECT * FROM t_order_1  where t_order_id in (1,2) 

直接路由

直接路由是通過使用 HintAPI 直接將 SQL路由到指定⾄庫表的一種分⽚方式,而且直接路由可以⽤于分⽚鍵不在SQL中的場景,還可以執⾏包括⼦查詢、⾃定義函數等復雜情況的任意SQL。

比如根據 t_order_id 字段為條件查詢訂單,此時希望在不修改SQL的前提下,加上 user_id作為分片條件就可以使用直接路由。

笛卡爾積路由

笛卡爾路由是由⾮綁定表之間的關聯查詢產生的,查詢性能較低盡量避免走此路由模式。

無分⽚鍵的路由又叫做廣播路由,可以劃分為全庫表路由、全庫路由、 全實例路由、單播路由和阻斷路由這 5種類型。

全庫表路由

全庫表路由針對的是數據庫 DQL和 DML,以及 DDL等操作,當我們執行一條邏輯表 t_order SQL時,在所有分片庫中對應的真實表 t_order_0 ···  t_order_n 內逐一執行。

全庫路由

全庫路由主要是對數據庫層面的操作,比如數據庫 SET 類型的數據庫管理命令,以及 TCL 這樣的事務控制語句。

對邏輯庫設置 autocommit 屬性后,所有對應的真實庫中都執行該命令。 

  1. SET autocommit=0

全實例路由

全實例路由是針對數據庫實例的 DCL 操作(設置或更改數據庫用戶或角色權限),比如:創建一個用戶 order ,這個命令將在所有的真實庫實例中執行,以此確保 order 用戶可以正常訪問每一個數據庫實例。 

  1. CREATE USER order@127.0.0.1 identified BY '程序員內點事'; 

單播路由

單播路由用來獲取某一真實表信息,比如獲得表的描述信息: 

  1. DESCRIBE t_order;  

t_order 的真實表是 t_order_0 ···· t_order_n,他們的描述結構相完全同,我們只需在任意的真實表執行一次就可以。

阻斷路由

來屏蔽SQL對數據庫的操作,例如: 

  1. USE order_db; 

這個命令不會在真實數據庫中執⾏,因為 ShardingSphere 采⽤的是邏輯 Schema(數據庫的組織和結構) ⽅式,所以無需將切換數據庫的命令發送⾄真實數據庫中。

SQL 改寫

將基于邏輯表開發的SQL改寫成可以在真實數據庫中可以正確執行的語句。比如查詢 t_order 訂單表,我們實際開發中 SQL是按邏輯表 t_order 寫的。 

  1. SELECT * FROM t_order 

但分庫分表以后真實數據庫中 t_order 表就不存在了,而是被拆分成多個子表 t_order_n 分散在不同的數據庫內,還按原SQL執行顯然是行不通的,這時需要將分表配置中的邏輯表名稱改寫為路由之后所獲取的真實表名稱。 

  1. SELECT * FROM t_order_n 

SQL執⾏

將路由和改寫后的真實 SQL 安全且高效發送到底層數據源執行。但這個過程并不是簡單的將 SQL 通過JDBC 直接發送至數據源執行,而是平衡數據源連接創建以及內存占用所產生的消耗,它會自動化的平衡資源控制與執行效率。

結果歸并

將從各個數據節點獲取的多數據結果集,合并成一個大的結果集并正確的返回至請求客戶端,稱為結果歸并。而我們SQL中的排序、分組、分頁和聚合等語法,均是在歸并后的結果集上進行操作的。

四、快速實踐

下面我們結合 Springboot + mybatisplus 快速搭建一個分庫分表案例。

1、準備工作

先做準備工作,創建兩個數據庫 ds-0、ds-1,兩個庫中分別建表 t_order_0、t_order_1、t_order_2 、t_order_item_0、t_order_item_1、t_order_item_2,t_config,方便后邊驗證廣播表、綁定表的場景。

表結構如下:

t_order_0 訂單表 

  1. CREATE TABLE `t_order_0` (  
  2.   `order_id` bigint(200) NOT NULL,  
  3.   `order_no` varchar(100) DEFAULT NULL,  
  4.   `create_name` varchar(50) DEFAULT NULL,  
  5.   `price` decimal(10,2) DEFAULT NULL,  
  6.   PRIMARY KEY (`order_id`)  
  7. ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

t_order_0 與 t_order_item_0  互為關聯表 

  1. CREATE TABLE `t_order_item_0` (  
  2.   `item_id` bigint(100) NOT NULL,  
  3.   `order_no` varchar(200) NOT NULL,  
  4.   `item_name` varchar(50) DEFAULT NULL,  
  5.   `price` decimal(10,2) DEFAULT NULL,  
  6.   PRIMARY KEY (`item_id`)  
  7. ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

廣播表 t_config 

  1. `id` bigint(30) NOT NULL,  
  2. `remark` varchar(50) CHARACTER SET utf8 DEFAULT NULL,  
  3. `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  4. `last_modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
  5. PRIMARY KEY (`id`)  
  6. ENGINE=InnoDB DEFAULT CHARSET=latin1

ShardingSphere 提供了4種分片配置方式:

  •  Java 代碼配置
  •  Yaml 、properties 配置
  •  Spring 命名空間配置
  •  Spring Boot配置

為讓代碼看上去更簡潔和直觀,后邊統一使用 properties 配置的方式,引入 shardingsphere 對應的 sharding-jdbc-spring-boot-starter 和 sharding-core-common 包,版本統一用的 4.0.0-RC1。

2、分片配置 

  1. <dependency>  
  2.  <groupId>org.apache.shardingsphere</groupId>  
  3.  <artifactId>sharding-jdbc-spring-boot-starter</artifactId>  
  4.  <version>4.0.0-RC1</version>  
  5. </dependency>  
  6. <dependency>  
  7.  <groupId>org.apache.shardingsphere</groupId>  
  8.  <artifactId>sharding-core-common</artifactId>  
  9.  <version>4.0.0-RC1</version>  
  10. </dependency> 

準備工作做完( mybatis 搭建就不贅述了),接下來我們逐一解讀分片配置信息。

我們首先定義兩個數據源 ds-0、ds-1,并分別加上數據源的基礎信息。 

  1. # 定義兩個全局數據源  
  2. spring.shardingsphere.datasource.names=ds-0,ds-1  
  3. # 配置數據源 ds-0  
  4. spring.shardingsphere.datasource.ds-0.type=com.alibaba.druid.pool.DruidDataSource  
  5. spring.shardingsphere.datasource.ds-0.driverClassName=com.mysql.jdbc.Driver  
  6. spring.shardingsphere.datasource.ds-0.url=jdbc:mysql://127.0.0.1:3306/ds-0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT 
  7. spring.shardingsphere.datasource.ds-0.username=root  
  8. spring.shardingsphere.datasource.ds-0.password=root  
  9. # 配置數據源 ds-1  
  10. spring.shardingsphere.datasource.ds-1.type=com.alibaba.druid.pool.DruidDataSource  
  11. spring.shardingsphere.datasource.ds-1.driverClassName=com.mysql.jdbc.Driver  
  12. spring.shardingsphere.datasource.ds-1.url=jdbc:mysql://127.0.0.1:3306/ds-1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT 
  13. spring.shardingsphere.datasource.ds-1.username=root  
  14. spring.shardingsphere.datasource.ds-1.password=root 

配置完數據源接下來為表添加分庫和分表策略,使用 sharding-jdbc 做分庫分表需要我們為每一個表單獨設置分片規則。 

  1. # 配置分片表 t_order  
  2. # 指定真實數據節點  
  3. spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..2} 

actual-data-nodes 屬性指定分片的真實數據節點,$是一個占位符,{0..1}表示實際拆分的數據庫表數量。

ds-$->{0..1}.t_order_$->{0..2}  表達式相當于 6個數據節點

  •  ds-0.t_order_0
  •  ds-0.t_order_1
  •  ds-0.t_order_2
  •  ds-1.t_order_0
  •  ds-1.t_order_1
  •  ds-1.t_order_2 
  1. ### 分庫策略  
  2. # 分庫分片健  
  3. spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=order_id  
  4. # 分庫分片算法  
  5. spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds-$->{order_id % 2} 

為表設置分庫策略,上邊講了 sharding-jdbc 它提供了四種分片策略,為快速搭建我們先以最簡單的行內表達式分片策略來實現,在下一篇會介紹四種分片策略的詳細用法和使用場景。

database-strategy.inline.sharding-column  屬性中 database-strategy  為分庫策略,inline  為具體的分片策略,sharding-column  代表分片健。

database-strategy.inline.algorithm-expression 是當前策略下具體的分片算法,ds-$->{order_id % 2} 表達式意思是 對 order_id字段進行取模分庫,2 代表分片庫的個數,不同的策略對應不同的算法,這里也可以是我們自定義的分片算法類。 

  1. # 分表策略  
  2. # 分表分片健  
  3. spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id  
  4. # 分表算法  
  5. spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 3}  
  6. # 自增主鍵字段  
  7. spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id  
  8. # 自增主鍵ID 生成方案  
  9. spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE 

分表策略 和 分庫策略 的配置比較相似,不同的是分表可以通過 key-generator.column 和 key-generator.type 設置自增主鍵以及指定自增主鍵的生成方案,目前內置了SNOWFLAKE 和 UUID 兩種方式,還能自定義的主鍵生成算法類,后續會詳細的講解。 

  1. # 綁定表關系  
  2. spring.shardingsphere.sharding.binding-tablest_order,t_order_item 

必須按相同分片健進行分片的表才能互為成綁定表,在聯合查詢時就能避免出現笛卡爾積查詢。 

  1. # 配置廣播表  
  2. spring.shardingsphere.sharding.broadcast-tables=t_config 

廣播表,開啟 SQL解析日志,能清晰的看到 SQL分片解析的過程 

  1. # 是否開啟 SQL解析日志  
  2. spring.shardingsphere.props.sql.show=true 

3、驗證分片

分片配置完以后我們無需在修改業務代碼了,直接執行業務邏輯的增、刪、改、查即可,接下來驗證一下分片的效果。

我們同時向 t_order、t_order_item 表插入 5條訂單記錄,并不給定主鍵 order_id ,item_id 字段值。 

  1. public String insertOrder() {  
  2.    for (int i = 0; i < 4; i++) {  
  3.        TOrder order = new TOrder();  
  4.        order.setOrderNo("A000" + i);  
  5.        order.setCreateName("訂單 " + i);  
  6.        order.setPrice(new BigDecimal("" + i)); 
  7.         orderRepository.insert(order);  
  8.        TOrderItem orderItem = new TOrderItem();  
  9.        orderItem.setOrderId(order.getOrderId());  
  10.        orderItem.setOrderNo("A000" + i);  
  11.        orderItem.setItemName("服務項目" + i);  
  12.        orderItem.setPrice(new BigDecimal("" + i));  
  13.        orderItemRepository.insert(orderItem);  
  14.    }  
  15.    return "success";  

看到訂單記錄被成功分散到了不同的庫表中, order_id 字段也自動生成了主鍵ID,基礎的分片功能就完成了。

基礎分片

那向廣播表 t_config 中插入一條數據會是什么效果呢? 

  1. public String config() {  
  2.     TConfig tConfig = new TConfig();  
  3.     tConfig.setRemark("我是廣播表");  
  4.     tConfig.setCreateTime(new Date());  
  5.     tConfig.setLastModifyTime(new Date());  
  6.     configRepository.insert(tConfig);  
  7.     return "success";  

發現所有庫中 t_config 表都執行了這條SQL,廣播表和 MQ廣播訂閱的模式很相似,所有訂閱的客戶端都會收到同一條消息。

廣播表

簡單SQL操作驗證沒問通,接下來在試試復雜一點的聯合查詢,前邊我們已經把 t_order 、t_order_item 表設為綁定表,直接聯表查詢執行一下。

關聯查詢

通過控制臺日志發現,邏輯表SQL 經過解析以后,只對  t_order_0 和 t_order_item_0 表進行了關聯產生一條SQL。

綁定表SQL

那如果不互為綁定表又會是什么情況呢?去掉 spring.shardingsphere.sharding.binding-tables試一下。

發現控制臺解析出了 3條真實表SQL,而去掉 order_id 作為查詢條件再次執行后,結果解析出了 9條SQL,進行了笛卡爾積查詢。所以相比之下綁定表的優點就不言而喻了。

笛卡爾積查詢

五、總結

以上對分庫分表中間件 sharding-jdbc 的基礎概念做了簡單梳理,快速的搭建了一個分庫分表案例,但這只是實踐分庫分表的第一步,下一篇我們會詳細的介紹四種分片策略的具體用法和使用場景(必知必會),后邊將陸續講解自定義分布式主鍵、分布式數據庫事務、分布式服務治理,數據脫敏等。 

 

責任編輯:龐桂玉 來源: 數據庫開發
相關推薦

2020-09-27 08:00:49

分庫分表

2025-04-03 09:39:14

2021-10-27 09:55:55

Sharding-Jd分庫分表Java

2021-05-08 18:50:57

分庫分表中間件

2023-07-24 09:00:00

數據庫MyCat

2019-12-13 10:32:56

開源消息中間件

2009-09-29 10:35:42

Linux系統系統提速Linux

2010-11-25 10:55:34

2024-08-13 17:09:00

架構分庫分表開發

2014-02-17 09:22:37

2022-03-11 10:53:32

UML建模語言

2022-05-16 08:50:23

數據脫加密器

2023-03-10 18:20:07

客戶端開源中間件

2009-02-10 15:08:41

2024-12-04 13:02:34

數據庫分庫分表

2022-02-23 08:55:06

數據遷移分庫分表數據庫

2022-06-22 07:32:53

Sharding分庫數據源

2015-07-29 10:25:05

數據開發產品必修課

2025-04-03 08:35:00

分頁查詢開發代碼

2018-04-28 10:05:17

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产欧美一区二区久久性色99 | 怡红院怡春院一级毛片 | 日本黄色激情视频 | 精品国产一区二区在线 | 久久婷婷麻豆国产91天堂 | 中文字幕免费 | 国产日韩欧美在线 | 国产在视频一区二区三区吞精 | 欧美日韩在线免费 | 欧美在线视频观看 | 一级黄在线观看 | 久色| 91精品久久久久久久久久入口 | h在线播放 | 99精品热视频 | av在线免费观看网站 | 久久国产传媒 | 亚洲iv一区二区三区 | 亚洲一视频 | 欧美成人一区二区三区 | 国产美女精品视频免费观看 | 久久久www成人免费精品 | 日本一区二区三区在线观看 | 一区二区国产精品 | 一级全黄少妇性色生活免费看 | 老头搡老女人毛片视频在线看 | se婷婷| 国产成人精品一区二区三区在线 | 九九久久精品视频 | 欧美一级高清片 | 午夜影院| 一区二区三区国产好的精 | 美国a级毛片免费视频 | 精品一区av | 国产精品毛片无码 | 一区二区视频在线观看 | 久久精品国产一区二区三区 | av不卡一区| 国产精品毛片无码 | 久久网一区二区三区 | 日韩快播电影 |