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