使用ShardingJDBC進(jìn)行數(shù)據(jù)分片以及讀寫分離
簡述
- ShardingJDBC: 它是一個輕量級的Java框架,提供了數(shù)據(jù)分片、讀寫分離、分布式主鍵生成等數(shù)據(jù)訪問功能。ShardingJDBC 直接嵌入在應(yīng)用程序中,不需要通過中間件代理的方式實現(xiàn)數(shù)據(jù)庫訪問。
- 多數(shù)據(jù)源: 在 ShardingJDBC 中,多數(shù)據(jù)源指的是將數(shù)據(jù)儲存到多個數(shù)據(jù)庫中。數(shù)據(jù)根據(jù)某種分片策略(如按照ID范圍、哈希值等)分布在不同的數(shù)據(jù)庫中。
- 讀寫分離: 讀寫分離是通過配置主庫(寫操作)和從庫(讀操作)來實現(xiàn)的。應(yīng)用程序?qū)懭氩僮髦饕槍χ鲙?,讀取操作可以分散到多個從庫中,從而提高數(shù)據(jù)庫的讀取性能和系統(tǒng)的可擴(kuò)展性。
原理
ShardingJDBC 的核心組件和功能,包括一些相關(guān)代碼片段以更好地理解其工作原理。
1. SQL 解析
SQL 解析是 ShardingJDBC 處理流程的起點。ShardingJDBC 使用 ANTLR(另一個語言識別工具)作為 SQL 解析工具。
- SQL解析類:
SQLParseEngine
是解析的入口點。它接收原始 SQL 語句并基于數(shù)據(jù)庫方言(MySQL、PostgreSQL 等)進(jìn)行解析。 - 解析過程:解析過程包括詞法分析和語法分析,用于構(gòu)建抽象語法樹(AST)。AST 提供了 SQL 結(jié)構(gòu)的詳細(xì)視圖,包括表名、列名、條件等。
SQLParseEngine 源碼分析
public final class SQLParseEngine { private final DatabaseType databaseType; private final String sql; private final LexerEngine lexerEngine; private final SQLStatementParser statementParser; public SQLParseEngine(DatabaseType databaseType, String sql, Properties properties, List<SQLToken> sqlTokens) { this.databaseType = databaseType; this.sql = sql; Lexer lexer = LexerFactory.newInstance(databaseType, sql); this.lexerEngine = new LexerEngine(lexer); this.statementParser = SQLStatementParserFactory.newInstance(databaseType, lexerEngine); } public SQLStatement parse() { lexerEngine.nextToken(); return statementParser.parse(); } }
在上面的代碼中,SQLParseEngine
使用了數(shù)據(jù)庫類型(DatabaseType
)和 SQL 語句來初始化。它首先創(chuàng)建一個 Lexer(詞法解析器),然后使用這個 Lexer 創(chuàng)建一個 LexerEngine
,并且基于數(shù)據(jù)庫類型創(chuàng)建相應(yīng)的 SQL 語句解析器。parse
方法最終返回一個 SQL 語句對象(SQLStatement
),這個對象代表了解析后的 SQL 語句。
2. 路由計算
路由是根據(jù)分片策略和解析出的 SQL 信息,確定 SQL 應(yīng)該執(zhí)行在哪些具體的數(shù)據(jù)庫和表上。
- 路由類:
ShardingRouter
負(fù)責(zé)執(zhí)行路由邏輯。 - 路由策略:通過實現(xiàn)
ShardingStrategy
接口的類(如StandardShardingStrategy
、ComplexShardingStrategy
),根據(jù)分片鍵和分片算法確定目標(biāo)數(shù)據(jù)源。
ShardingRouter 源碼分析
public final class ShardingRouter { private final ShardingRule shardingRule; public ShardingRouter(ShardingRule shardingRule) { this.shardingRule = shardingRule; } public RoutingResult route(final SQLStatement sqlStatement) { // 省略具體路由邏輯 } }
在這里,ShardingRouter
通過 ShardingRule
(包含分片策略和規(guī)則)來進(jìn)行初始化。route
方法接受一個 SQL 語句對象,并根據(jù)分片規(guī)則返回路由結(jié)果。
3. SQL 改寫
根據(jù)路由結(jié)果,ShardingJDBC 會改寫原始 SQL,使其適用于目標(biāo)的物理數(shù)據(jù)庫和表。
- 改寫類:
SQLRewriteEngine
負(fù)責(zé) SQL 改寫。 - 改寫過程:根據(jù)路由結(jié)果和 AST,
SQLRewriteEngine
會修改表名、增加額外的條件等,生成最終要執(zhí)行的 SQL。
SQLRewriteEngine 源碼分析
public final class SQLRewriteEngine { private final SQLStatement sqlStatement; private final List<SQLToken> sqlTokens; public SQLRewriteEngine(SQLStatement sqlStatement) { this.sqlStatement = sqlStatement; this.sqlTokens = new LinkedList<>(); } public String rewrite() { // 省略具體改寫邏輯 } }
這里的 SQLRewriteEngine
接收一個 SQL 語句對象,并根據(jù)路由結(jié)果和 SQL 語句中的令牌(SQLToken
)列表來改寫 SQL。
4. 執(zhí)行計劃生成與執(zhí)行
生成 SQL 的執(zhí)行計劃并在相應(yīng)的數(shù)據(jù)庫實例上執(zhí)行。
- 執(zhí)行類:
ShardingExecuteEngine
負(fù)責(zé)管理 SQL 的執(zhí)行。 - 執(zhí)行過程:它可能涉及到并行查詢、合并結(jié)果集等操作。對于寫操作,通常直接路由到主庫;對于讀操作,則可能涉及到多個從庫。
public class ShardingExecuteEngine implements AutoCloseable { private final ExecutorService executorService; public ShardingExecuteEngine(int executorSize) { this.executorService = Executors.newFixedThreadPool(executorSize); } // 省略執(zhí)行方法 }
ShardingExecuteEngine
使用一個線程池來執(zhí)行 SQL。這個類負(fù)責(zé)管理 SQL 的執(zhí)行過程,包括可能的并行查詢和結(jié)果集合并。
5. 結(jié)果集合并
對于查詢操作,ShardingJDBC 需要合并來自不同物理表或數(shù)據(jù)庫的結(jié)果集。
- 合并類:
MergeEngine
負(fù)責(zé)結(jié)果集的合并。 - 合并過程:根據(jù)不同的查詢類型(聚合查詢、排序查詢等),
MergeEngine
使用不同的合并策略來確保返回給用戶的是一個統(tǒng)一的結(jié)果集。
public final class MergeEngine { public MergedResult merge(List<QueryResult> queryResults, SQLStatement sqlStatement) { // 省略合并邏輯 } }
MergeEngine
負(fù)責(zé)將來自不同物理表或數(shù)據(jù)庫的查詢結(jié)果合并成一個統(tǒng)一的結(jié)果集。它根據(jù) SQL 語句的類型(如聚合查詢、排序查詢)來應(yīng)用不同的合并策略。
6. 分布式事務(wù)處理
處理分布式環(huán)境下的事務(wù)一致性。
- 事務(wù)管理器:
ShardingTransactionManager
接口定義了事務(wù)管理的行為。 - 具體實現(xiàn):如
XAShardingTransactionManager
用于處理 XA 類型的分布式事務(wù)。
ShardingTransactionManager 接口和 XAShardingTransactionManager 實現(xiàn)
public interface ShardingTransactionManager { void begin(); void commit(); void rollback(); // 省略其他方法 } public class XAShardingTransactionManager implements ShardingTransactionManager { // 實現(xiàn)分布式事務(wù)管理邏輯 }
ShardingTransactionManager
接口定義了事務(wù)管理的基本行為,如開始(begin)、提交(commit)和回滾(rollback)操作。XAShardingTransactionManager
是這個接口的一個實現(xiàn),用于處理 XA 類型的分布式事務(wù)。
我們將假設(shè)有兩個業(yè)務(wù)表:order
和 user
,并且這兩個表需要根據(jù)不同的策略進(jìn)行分片。同時,我們將設(shè)置四個數(shù)據(jù)源(兩個主庫和兩個從庫)來實現(xiàn)讀寫分離。
實際案例
場景設(shè)定
- 數(shù)據(jù)源:
ds0
、ds0_slave
、ds1
、ds1_slave
。其中ds0
和ds1
是主庫,ds0_slave
和ds1_slave
是從庫。 - 業(yè)務(wù)表:
order
和user
。 - 分片策略:
order
表按照訂單ID分片。user
表按照用戶ID分片。
- 讀寫分離:所有寫操作都發(fā)生在主庫,讀操作可以分配到從庫。
配置和代碼實現(xiàn)
添加依賴:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>YOUR_VERSION</version> </dependency>
配置文件 application.yml
:
spring: shardingsphere: datasource: names: ds0,ds0_slave,ds1,ds1_slave ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: YOUR_DRIVER_CLASS jdbc-url: JDBC_URL_FOR_DS0 username: YOUR_USERNAME password: YOUR_PASSWORD ds0_slave: type: com.zaxxer.hikari.HikariDataSource driver-class-name: YOUR_DRIVER_CLASS jdbc-url: JDBC_URL_FOR_DS0_SLAVE username: YOUR_USERNAME password: YOUR_PASSWORD ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: YOUR_DRIVER_CLASS jdbc-url: JDBC_URL_FOR_DS1 username: YOUR_USERNAME password: YOUR_PASSWORD ds1_slave: type: com.zaxxer.hikari.HikariDataSource driver-class-name: YOUR_DRIVER_CLASS jdbc-url: JDBC_URL_FOR_DS1_SLAVE username: YOUR_USERNAME password: YOUR_PASSWORD sharding: tables: order: actual-data-nodes: ds${0..1}.order_${0..1} table-strategy: inline: sharding-column: id algorithm-expression: order_${id % 2} database-strategy: inline: sharding-column: user_id algorithm-expression: ds${user_id % 2} key-generator: type: SNOWFLAKE column: id user: actual-data-nodes: ds${0..1}.user_${0..1} table-strategy: inline: sharding-column: id algorithm-expression: user_${id > 5000 ? 1 : 0} database-strategy: inline: sharding-column: id algorithm-expression: ds${id % 2} key-generator: type: SNOWFLAKE column: id master-slave-rules: ds0: master-data-source-name: ds0 slave-data-source-names: ds0_slave ds1: master-data-source-name: ds1 slave-data-source-names: ds1_slave
這段配置是用于設(shè)置 Apache ShardingSphere(ShardingJDBC 的一個部分)的 YAML 格式的配置文件,專門用于 Spring Boot 項目。它定義了數(shù)據(jù)源(包括主從數(shù)據(jù)源),表的分片策略,以及主從復(fù)制規(guī)則。讓我們逐個部分進(jìn)行詳細(xì)解釋:
數(shù)據(jù)源配置
datasource: names: ds0,ds0_slave,ds1,ds1_slave ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: YOUR_DRIVER_CLASS jdbc-url: JDBC_URL_FOR_DS0 username: YOUR_USERNAME password: YOUR_PASSWORD ...
names
: 定義了所有數(shù)據(jù)源的名稱,這里有四個數(shù)據(jù)源:ds0
,ds0_slave
,ds1
,ds1_slave
。ds0
,ds0_slave
,ds1
,ds1_slave
: 分別定義了四個數(shù)據(jù)源的詳細(xì)配置。type
: 數(shù)據(jù)源類型,這里使用的是 HikariCP 連接池。driver-class-name
: 數(shù)據(jù)庫驅(qū)動類。jdbc-url
: 數(shù)據(jù)庫的 JDBC URL。username
和password
: 數(shù)據(jù)庫的登錄用戶名和密碼。
分片配置
sharding: tables: order: actual-data-nodes: ds${0..1}.order_${0..1} table-strategy: inline: sharding-column: id algorithm-expression: order_${id % 2} database-strategy: inline: sharding-column: user_id algorithm-expression: ds${user_id % 2} key-generator: type: SNOWFLAKE column: id user: ...
sharding
: 定義了分片的總體配置。tables
: 在這里定義具體的表和它們的分片策略。order
: 這是一個表的名稱。actual-data-nodes
: 定義實際的數(shù)據(jù)節(jié)點,ds${0..1}.order_${0..1}
表示order
表在ds0
和ds1
數(shù)據(jù)源上都有兩個分片,即order_0
和order_1
。table-strategy
: 定義表的分片策略。sharding-column
: 分片鍵,這里使用id
。algorithm-expression
: 分片算法表達(dá)式,這里是簡單的模 2 運算。
database-strategy
: 定義數(shù)據(jù)庫的分片策略,類似于表的分片策略。key-generator
: 定義主鍵生成策略,這里使用的是雪花算法(SNOWFLAKE
)。
主從配置
master-slave-rules: ds0: master-data-source-name: ds0 slave-data-source-names: ds0_slave ds1: master-data-source-name: ds1 slave-data-source-names: ds1_slave
實體類和數(shù)據(jù)訪問層:
定義 Order
和 User
實體類,以及對應(yīng)的 JPA 倉庫或 MyBatis 映射。
Order 實體類
@Entity @Table(name = "order") public class Order { @Id private Long id; @Column(name = "user_id") private Long userId; @Column(name = "order_amount") private BigDecimal orderAmount; // 標(biāo)準(zhǔn)的構(gòu)造函數(shù)、getter 和 setter public Order() { } // ... 省略其他構(gòu)造函數(shù)、getter 和 setter 方法 // ... 可以添加業(yè)務(wù)邏輯方法 }
User 實體類
@Entity @Table(name = "user") public class User { @Id private Long id; @Column(name = "username") private String username; @Column(name = "email") private String email; // 標(biāo)準(zhǔn)的構(gòu)造函數(shù)、getter 和 setter public User() { } // ... 省略其他構(gòu)造函數(shù)、getter 和 setter 方法 // ... 可以添加業(yè)務(wù)邏輯方法 }
OrderRepository 接口
@Repository public interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByUserId(Long userId); // ... 可以根據(jù)需要添加其他查詢方法 }
UserRepository 接口
@Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); // ... 可以根據(jù)需要添加其他查詢方法 }
補(bǔ)充一個mybatis的寫法:
@Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User findById(Long id); @Insert("INSERT INTO user (username, email) VALUES (#{username}, #{email})") void insert(User user); // 更多的 MyBatis SQL 映射可以根據(jù)需要添加 }
總結(jié)
ShardingJDBC 的源碼實現(xiàn)體現(xiàn)了其作為一個數(shù)據(jù)庫中間件框架的復(fù)雜性和靈活性。它將 SQL 解析、路由、改寫、執(zhí)行和結(jié)果集合并等多個步驟封裝成一系列高度解耦的組件和接口。這種設(shè)計使得 ShardingJDBC 能夠靈活地適應(yīng)各種數(shù)據(jù)庫和 SQL 方言,同時提供豐富的分片策略和讀寫分離功能。
以上就是使用ShardingJDBC進(jìn)行數(shù)據(jù)分片以及讀寫分離的詳細(xì)內(nèi)容,更多關(guān)于ShardingJDBC數(shù)據(jù)分片的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
startActivityForResult和setResult案例詳解
這篇文章主要介紹了startActivityForResult和setResult案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Java連接數(shù)據(jù)庫步驟解析(Oracle、MySQL)
本文主要介紹了Java連接Oracle數(shù)據(jù)庫和MySQL數(shù)據(jù)庫的步驟解析。具有很好的參考價值,需要的朋友一起來看下吧2016-12-12SpringBoot深入探究@Conditional條件裝配的使用
這篇文章主要為大家介紹了SpringBoot底層注解@Conditional的使用分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Kotlin中使用Java數(shù)據(jù)類時引發(fā)的Bug解決方式
這篇文章主要介紹了Kotlin中使用Java數(shù)據(jù)類時引發(fā)的一個Bug,本文給大家分享問題解決方式,感興趣的朋友跟隨小編一起看看吧2023-09-09一篇文章了解Jackson注解@JsonFormat及失效解決辦法
這篇文章主要給大家介紹了關(guān)于如何通過一篇文章了解Jackson注解@JsonFormat及失效解決辦法的相關(guān)資料,@JsonFormat注解是一個時間格式化注解,用于格式化時間,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11Java實現(xiàn)WebSocket客戶端詳細(xì)步驟
這篇文章主要介紹了如何使用Java實現(xiàn)一個功能全面的WebSocket客戶端,包括引入依賴、創(chuàng)建客戶端類、實現(xiàn)連接、發(fā)送和接收消息、處理復(fù)雜消息、實現(xiàn)心跳機(jī)制、重連策略、異常處理、線程安全的隊列以及測試和調(diào)試,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03springboot之Validation參數(shù)校驗詳細(xì)解讀
這篇文章主要介紹了springboot之Validation參數(shù)校驗詳細(xì)解讀,本篇是關(guān)于springboot的參數(shù)校驗知識,當(dāng)然也適用其它java應(yīng)用,讀完本篇將學(xué)會基本的參數(shù)校驗,自定義參數(shù)校驗和分組參數(shù)校驗,需要的朋友可以參考下2023-10-10JavaWeb實體類轉(zhuǎn)為json對象的實現(xiàn)方法
這篇文章主要介紹了JavaWeb實體類轉(zhuǎn)為json對象的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12