前言 實戰背景介紹
背景描述
-
剛開始我們的系統只用了單機數據庫
-
隨着用戶的不斷增多,考慮到系統的高可用和越來越多的用戶請求,我們開始使用數據庫主從架構
-
當用戶量級和業務進一步提升后,寫請求越來越多,這時我們開始使用了分庫分表
遇到的問題
-
用戶請求量太大
單服務器TPS、內存、IO都是有上限的,需要將請求打散分布到多個服務器 -
單庫數據量太大
單個數據庫處理能力有限;單庫所在服務器的磁盤空間有限;單庫上的操作IO有瓶頸 -
單表數據量太大
查詢、插入、更新操作都會變慢,在加字段、加索引、機器遷移都會產生高負載,影響服務
如何解決
-
垂直拆分
-
垂直分庫
微服務架構時,業務切割得足夠獨立,數據也會按照業務切分,保證業務數據隔離,大大提
升了數據庫的吞吐能力
-
垂直分表
表中字段太多且包含大字段的時候,在查詢時對數據庫的IO、內存會受到影響,同時更新數
據時,產生的binlog文件會很大,MySQL在主從同步時也會有延遲的風險
-
水平拆分
-
水平分表
針對數據量巨大的單張表(比如訂單表),按照規則把一張表的數據切分到多張表里面去。
但是這些表還是在同一個庫中,所以庫級別的數據庫操作還是有IO瓶頸。
-
水平分庫
將單張表的數據切分到多個服務器上去,每個服務器具有相應的庫與表,只是表中數據集合
不同。 水平分庫分表能夠有效的緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件
資源等的瓶頸
-
水平分庫規則
不跨庫、不跨表,保證同一類的數據都在同一個服務器上面。
數據在切分之前,需要考慮如何高效的進行數據獲取,如果每次查詢都要跨越多個節點,就需要謹
慎使用。 -
水平分表規則
-
RANGE
-
時間:按照年、月、日去切分。例如order_2020、order_202005、order_20200501
-
地域:按照省或市去切分。例如order_beijing、order_shanghai、order_chengdu
-
大小:從0到1000000一個表。例如1000001-2000000放一個表,每100萬放一個表
-
HASH
-
用戶ID取模
不同的業務使用的切分規則是不一樣,就上面提到的切分規則,舉例如下: -
用戶表
-
范圍法:以用戶ID為划分依據,將數據水平切分到兩個數據庫實例,如:1到1000W在
一張表,1000W到2000W在一張表,這種情況會出現單表的負載較高 -
按照用戶ID HASH盡量保證用戶數據均衡分到數據庫中
如果在登錄場景下,用戶輸入手機號和驗證碼進行登錄,這種情況下,登錄時是
不是需要掃描所有分庫的信息?
最終方案:用戶信息采用ID做切分處理,同時存儲用戶ID和手機號的映射的關系
表(新增一個關系表),關系表采用手機號進行切分。可以通過關系表根據手機
號查詢到對應的ID,再定位用戶信息。
-
流水表
-
時間維度:可以根據每天新增的流水來判斷,選擇按照年份分庫,還是按照月份分庫,
甚至也可以按照日期分庫 -
訂單表
在拉勾網,求職者(下面統稱C端用戶)投遞企業(下面統稱B端用戶)的職位產生的記錄稱
之為訂單表。在線上的業務場景中,C端用戶看自己的投遞記錄,每次的投遞到了哪個狀態,
B端用戶查看自己收到的簡歷,對於合適的簡歷會進行下一步溝通,同一個公司內的員工可以
協作處理簡歷。
如何能同時滿足C端和B端對數據查詢,不進行跨庫處理?
最終方案:為了同時滿足兩端用戶的業務場景,采用空間換時間,將一次的投遞記錄存為兩
份,C端的投遞記錄以用戶ID為分片鍵,B端收到的簡歷按照公司ID為分片鍵
-
主鍵選擇
-
UUID:本地生成,不依賴數據庫,缺點就是作為主鍵性能太差
-
SNOWFLAKE:百度UidGenerator、美團Leaf、基於SNOWFLAKE算法實現
-
數據一致性
-
強一致性:XA協議
-
最終一致性:TCC、saga、Seata
-
數據庫擴容
-
成倍增加數據節點,實現平滑擴容
-
成倍擴容以后,表中的部分數據請求已被路由到其他節點上面,可以清理掉
-
業務層改造
-
基於代理層方式:Mycat、Sharding-Proxy、MySQL Proxy
-
基於應用層方式:Sharding-jdbc
-
分庫后面臨的問題
-
事務問題:一次投遞需要插入兩條記錄,且分布在不同的服務器上,數據需要保障一致性。
-
跨庫跨表的join問題
-
全局表(字典表):基礎數據/配置數據,所有庫都拷貝一份
-
字段冗余:可以使用字段冗余就不用join查詢了
-
系統層組裝:可以在業務層分別查詢出來,然后組裝起來,邏輯較復雜
-
額外的數據管理負擔和數據運算壓力:數據庫擴容、維護成本變高
ShardingSphere實戰
ShardingSphere
Apache ShardingSphere是一款開源的分布式數據庫中間件組成的生態圈。它由Sharding-JDBC、
Sharding-Proxy和Sharding-Sidecar(規划中)這3款相互獨立的產品組成。 他們均提供標准化的數據
分片、分布式事務和數據庫治理功能,可適用於如Java同構、異構語言、容器、雲原生等各種多樣化的
應用場景。
ShardingSphere項目狀態如下:
ShardingSphere定位為關系型數據庫中間件,旨在充分合理地在分布式的場景下利用關系型數據庫的
計算和存儲能力,而並非實現一個全新的關系型數據庫。
-
Sharding-JDBC:被定位為輕量級Java框架,在Java的JDBC層提供的額外服務,以jar包形式使用。
-
Sharding-Proxy:被定位為透明化的數據庫代理端,提供封裝了數據庫二進制協議的服務端版
本,用於完成對異構語言的支持。 -
Sharding-Sidecar:被定位為Kubernetes或Mesos的雲原生數據庫代理,以DaemonSet的形式代
理所有對數據庫的訪問。
Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar三者區別如下:
ShardingSphere安裝包下載:https://shardingsphere.apache.org/document/current/cn/download
s/
使用Git下載工程:git clone https://github.com/apache/incubator-shardingsphere.git
Sharding-JDBC
Sharding-JDBC定位為輕量級Java框架,在Java的JDBC層提供的額外服務。 它使用客戶端直連數據庫,
以jar包形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM
框架的使用。
-
適用於任何基於Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使
用JDBC。 -
基於任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
-
支持任意實現JDBC規范的數據庫。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
Sharding-JDBC主要功能:
-
數據分片
-
分庫、分表
-
讀寫分離
-
分片策略
-
分布式主鍵
-
分布式事務
-
標准化的事務接口
-
XA強一致性事務
-
柔性事務
-
數據庫治理
-
配置動態化
-
編排和治理
-
數據脫敏
-
可視化鏈路追蹤
Sharding-JDBC 內部結構:
-
圖中黃色部分表示的是Sharding-JDBC的入口API,采用工廠方法的形式提供。 目前有
ShardingDataSourceFactory和MasterSlaveDataSourceFactory兩個工廠類。 -
ShardingDataSourceFactory支持分庫分表、讀寫分離操作
-
MasterSlaveDataSourceFactory支持讀寫分離操作
-
圖中藍色部分表示的是Sharding-JDBC的配置對象,提供靈活多變的配置方式。
ShardingRuleConfiguration是分庫分表配置的核心和入口,它可以包含多個TableRuleConfiguration和MasterSlaveRuleConfiguration。 -
TableRuleConfiguration封裝的是表的分片配置信息,有5種配置形式對應不同的
Configuration類型。 -
MasterSlaveRuleConfiguration封裝的是讀寫分離配置信息。
-
圖中紅色部分表示的是內部對象,由Sharding-JDBC內部使用,應用開發者無需關注。ShardingJDBC通過ShardingRuleConfiguration和MasterSlaveRuleConfiguration生成真正供
ShardingDataSource和MasterSlaveDataSource使用的規則對象。ShardingDataSource和
MasterSlaveDataSource實現了DataSource接口,是JDBC的完整實現方案。
Sharding-JDBC初始化流程:
-
根據配置的信息生成Configuration對象
-
通過Factory會將Configuration對象轉化為Rule對象
-
通過Factory會將Rule對象與DataSource對象封裝
-
Sharding-JDBC使用DataSource進行分庫分表和讀寫分離操作
Sharding-JDBC 使用過程:
- 引入maven依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>${latest.release.version}</version>
</dependency>
注意: 請將${latest.release.version}更改為實際的版本號。
- 規則配置
Sharding-JDBC可以通過Java,YAML,Spring命名空間和Spring Boot Starter四種方式配置,開
發者可根據場景選擇適合的配置方式。 - 創建DataSource
通過ShardingDataSourceFactory工廠和規則配置對象獲取ShardingDataSource,然后即可通過
DataSource選擇使用原生JDBC開發,或者使用JPA, MyBatis等ORM工具。
DataSource dataSource =
ShardingDataSourceFactory.createDataSource(dataSourceMap,
shardingRuleConfig, props);
數據分片剖析實戰
核心概念
-
表概念
-
真實表
數據庫中真實存在的物理表。例如b_order0、b_order1 -
邏輯表
在分片之后,同一類表結構的名稱(總成)。例如b_order。 -
數據節點
在分片之后,由數據源和數據表組成。例如ds0.b_order1 -
綁定表
指的是分片規則一致的關系表(主表、子表),例如b_order和b_order_item,均按照
order_id分片,則此兩個表互為綁定表關系。綁定表之間的多表關聯查詢不會出現笛卡爾積
關聯,可以提升關聯查詢效率。
b_order:b_order0、b_order1
b_order_item:b_order_item0、b_order_item1
select * from b_order o join b_order_item i on(o.order_id=i.order_id)
where o.order_id in (10,11);
如果不配置綁定表關系,采用笛卡爾積關聯,會生成4個SQL
select * from b_order0 o join b_order_item0 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
select * from b_order0 o join b_order_item1 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
select * from b_order1 o join b_order_item0 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
select * from b_order1 o join b_order_item1 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
如果配置綁定表關系,生成2個SQL
select * from b_order0 o join b_order_item0 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
select * from b_order1 o join b_order_item1 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
-
廣播表
在使用中,有些表沒必要做分片,例如字典表、省份信息等,因為他們數據量不大,而且這
種表可能需要與海量數據的表進行關聯查詢。廣播表會在不同的數據節點上進行存儲,存儲
的表結構和數據完全相同。 -
分片算法(ShardingAlgorithm)
由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提
煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。目前提供4種分片算
法。 -
精確分片算法PreciseShardingAlgorithm
用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。 -
范圍分片算法RangeShardingAlgorithm
用於處理使用單一鍵作為分片鍵的BETWEEN AND、>、<、>=、<=進行分片的場景。 -
復合分片算法ComplexKeysShardingAlgorithm
用於處理使用多鍵作為分片鍵進行分片的場景,多個分片鍵的邏輯較復雜,需要應用開發者
自行處理其中的復雜度。 -
Hint分片算法HintShardingAlgorithm
用於處理使用Hint行分片的場景。對於分片字段非SQL決定,而由其他外置條件決定的場
景,可使用SQL Hint靈活的注入分片字段。例:內部系統,按照員工登錄主鍵分庫,而數據
庫中並無此字段。SQL Hint支持通過Java API和SQL注釋兩種方式使用。 -
分片策略(ShardingStrategy)
分片策略包含分片鍵和分片算法,真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策
略。目前提供5種分片策略。 -
標准分片策略StandardShardingStrategy
只支持單分片鍵,提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。
提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。
PreciseShardingAlgorithm是必選的,RangeShardingAlgorithm是可選的。但是SQL中使用
了范圍操作,如果不配置RangeShardingAlgorithm會采用全庫路由掃描,效率低。 -
復合分片策略ComplexShardingStrategy
支持多分片鍵。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。由
於多分片鍵之間的關系復雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片
操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。 -
行表達式分片策略InlineShardingStrategy
只支持單分片鍵。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,對於
簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發。如: t_user_$->
{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0到t_user_7。 -
Hint分片策略HintShardingStrategy
通過Hint指定分片值而非從SQL中提取分片值的方式進行分片的策略。 -
不分片策略NoneShardingStrategy
不分片的策略。 -
分片策略配置
對於分片策略存有數據源分片策略和表分片策略兩種維度,兩種策略的API完全相同。 -
數據源分片策略
用於配置數據被分配的目標數據源。 -
表分片策略
用於配置數據被分配的目標表,由於表存在與數據源內,所以表分片策略是依賴數據源分片
策略結果的。
流程剖析
ShardingSphere 3個產品的數據分片功能主要流程是完全一致的,如下圖所示。
-
SQL解析
SQL解析分為詞法解析和語法解析。 先通過詞法解析器將SQL拆分為一個個不可再分的單詞。再使
用語法解析器對SQL進行理解,並最終提煉出解析上下文。
Sharding-JDBC采用不同的解析器對SQL進行解析,解析器類型如下: -
MySQL解析器
-
Oracle解析器
-
SQLServer解析器
-
PostgreSQL解析器
-
默認SQL解析器
-
查詢優化
負責合並和優化分片條件,如OR等。 -
SQL路由
根據解析上下文匹配用戶配置的分片策略,並生成路由路徑。目前支持分片路由和廣播路由。 -
SQL改寫
將SQL改寫為在真實數據庫中可以正確執行的語句。SQL改寫分為正確性改寫和優化改寫。 -
SQL執行
通過多線程執行器異步執行SQL。 -
結果歸並
將多個執行結果集歸並以便於通過統一的JDBC接口輸出。結果歸並包括流式歸並、內存歸並和使
用裝飾者模式的追加歸並這幾種方式。
SQL使用規范
-
SQL使用規范
-
支持項
-
路由至單數據節點時,目前MySQL數據庫100%全兼容,其他數據庫完善中。
-
路由至多數據節點時,全面支持DQL、DML、DDL、DCL、TCL。支持分頁、去重、排
序、分組、聚合、關聯查詢(不支持跨庫關聯)。以下用最為復雜的查詢為例:
SELECT select_expr [, select_expr ...]
FROM table_reference [, table_reference ...]
[WHERE predicates]
[GROUP BY {col_name | position} [ASC | DESC], ...]
[ORDER BY {col_name | position} [ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
-
不支持項(路由至多數據節點)
-
不支持CASE WHEN、HAVING、UNION (ALL)
-
支持分頁子查詢,但其他子查詢有限支持,無論嵌套多少層,只能解析至第一個包含數據表
的子查詢,一旦在下層嵌套中再次找到包含數據表的子查詢將直接拋出解析異常。
例如,以下子查詢可以支持:
SELECT COUNT(*) FROM (SELECT * FROM b_order o)
以下子查詢不支持:
SELECT COUNT(*) FROM (SELECT * FROM b_order o WHERE o.id IN (SELECT id
FROM b_order WHERE status = ?))
簡單來說,通過子查詢進行非功能需求,在大部分情況下是可以支持的。比如分頁、統計總
數等;而通過子查詢實現業務查詢當前並不能支持。
-
由於歸並的限制,子查詢中包含聚合函數目前無法支持。
-
不支持包含schema的SQL。因為ShardingSphere的理念是像使用一個數據源一樣使用多數
據源,因此對SQL的訪問都是在同一個邏輯schema之上。 -
當分片鍵處於運算表達式或函數中的SQL時,將采用全路由的形式獲取結果。
例如下面SQL,create_time為分片鍵:
SELECT * FROM b_order WHERE to_date(create_time, 'yyyy-mm-dd') = '2020-
05-05';
由於ShardingSphere只能通過SQL字面提取用於分片的值,因此當分片鍵處於運算表達式
或函數中時,ShardingSphere無法提前獲取分片鍵位於數據庫中的值,從而無法計算出真正
的分片值。
不支持的SQL示例:
INSERT INTO tbl_name (col1, col2, …) VALUES(1+2, ?, …) //VALUES語句不支持運算
表達式
INSERT INTO tbl_name (col1, col2, …) SELECT col1, col2, … FROM tbl_name
WHERE col3 = ? //INSERT .. SELECT
SELECT COUNT(col1) as count_alias FROM tbl_name GROUP BY col1 HAVING
count_alias > ? //HAVING
SELECT * FROM tbl_name1 UNION SELECT * FROM tbl_name2 //UNION
SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2 //UNION ALL
SELECT * FROM ds.tbl_name1 //包含schema
SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name //同時使用普通聚合函數
和DISTINCT
SELECT * FROM tbl_name WHERE to_date(create_time, ‘yyyy-mm-dd’) = ? //會導致
全路由
- 分頁查詢
完全支持MySQL和Oracle的分頁查詢,SQLServer由於分頁查詢較為復雜,僅部分支持
- 性能瓶頸:
查詢偏移量過大的分頁會導致數據庫獲取數據性能低下,以MySQL為例:
SELECT * FROM b_order ORDER BY id LIMIT 1000000, 10
這句SQL會使得MySQL在無法利用索引的情況下跳過1000000條記錄后,再獲取10條記錄,
其性能可想而知。 而在分庫分表的情況下(假設分為2個庫),為了保證數據的正確性,SQL
會改寫為:
SELECT * FROM b_order ORDER BY id LIMIT 0, 1000010
即將偏移量前的記錄全部取出,並僅獲取排序后的最后10條記錄。這會在數據庫本身就執行
很慢的情況下,進一步加劇性能瓶頸。 因為原SQL僅需要傳輸10條記錄至客戶端,而改寫之
后的SQL則會傳輸1,000,010 * 2的記錄至客戶端。
-
ShardingSphere的優化:
ShardingSphere進行了以下2個方面的優化。 -
首先,采用流式處理 + 歸並排序的方式來避免內存的過量占用。
-
其次,ShardingSphere對僅落至單節點的查詢進行進一步優化。
-
分頁方案優化:
由於LIMIT並不能通過索引查詢數據,因此如果可以保證ID的連續性,通過ID進行分頁是比較
好的解決方案:
SELECT * FROM b_order WHERE id > 1000000 AND id <= 1000010 ORDER BY id
或通過記錄上次查詢結果的最后一條記錄的ID進行下一頁的查詢:
SELECT * FROM b_order WHERE id > 1000000 LIMIT 10
其他功能
- Inline行表達式
InlineShardingStrategy:采用Inline行表達式進行分片的配置。
Inline是可以簡化數據節點和分片算法配置信息。主要是解決配置簡化、配置一體化。
語法格式:
行表達式的使用非常直觀,只需要在配置中使用\({ expression }或\)->{ expression }標識行表達式
即可。例如:
${begin..end} 表示范圍區間
${[unit1, unit2, unit_x]} 表示枚舉值
行表達式中如果出現多個\({}或\)->{}表達式,整個表達式結果會將每個子表達式結果進行笛卡爾
(積)組合。例如,以下行表達式:
${['online', 'offline']}_table${1..3}
$->{['online', 'offline']}_table$->{1..3}
最終會解析為:
online_table1, online_table2, online_table3,
offline_table1, offline_table2, offline_table3
數據節點配置:
對於均勻分布的數據節點,如果數據結構如下:
db0
├── b_order2
└──b_order1
db1
├── b_order2
└──b_order1
用行表達式可以簡化為:
db${0..1}.b_order${1..2}
或者
db$->{0..1}.b_order$->{1..2}
對於自定義的數據節點,如果數據結構如下:
db0
├── b_order0
└──b_order1
db1
├── b_order2
├── b_order3
└──b_order4
用行表達式可以簡化為:
db0.b_order${0..1},db1.b_order${2..4}
分片算法配置:
行表達式內部的表達式本質上是一段Groovy代碼,可以根據分片鍵進行計算的方式,返回相應的
真實數據源或真實表名稱。
ds${id % 10}
或者
ds$->{id % 10}
結果為:ds0、ds1、ds2... ds9
-
分布式主鍵
ShardingSphere不僅提供了內置的分布式主鍵生成器,例如UUID、SNOWFLAKE,還抽離出分布
式主鍵生成器的接口,方便用戶自行實現自定義的自增主鍵生成器。
內置主鍵生成器: -
UUID
采用UUID.randomUUID()的方式產生分布式主鍵。
- SNOWFLAKE
在分片規則配置模塊可配置每個表的主鍵生成策略,默認使用雪花算法,生成64bit的長整型
數據。
自定義主鍵生成器:
-
自定義主鍵類,實現ShardingKeyGenerator接口
-
按SPI規范配置自定義主鍵類
在Apache ShardingSphere中,很多功能實現類的加載方式是通過SPI注入的方式完成的。
注意:在resources目錄下新建META-INF文件夾,再新建services文件夾,然后新建文件的
名字為org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator,打開文件,復制
自定義主鍵類全路徑到文件中保存。 -
自定義主鍵類應用配置
#對應主鍵字段名
spring.shardingsphere.sharding.tables.t_book.key-generator.column=id
#對應主鍵類getType返回內容
spring.shardingsphere.sharding.tables.t_book.keygenerator.type=LAGOUKEY
讀寫分離剖析實戰
讀寫分離是通過主從的配置方式,將查詢請求均勻的分散到多個數據副本,進一步的提升系統的處理能
力。
主從架構:讀寫分離,目的是高可用、讀寫擴展。主從庫內容相同,根據SQL語義進行路由。
分庫分表架構:數據分片,目的讀寫擴展、存儲擴容。庫和表內容不同,根據分片配置進行路由。
將水平分片和讀寫分離聯合使用,能夠更加有效的提升系統性能, 下圖展現了將分庫分表與讀寫分離一
同使用時,應用程序與數據庫集群之間的復雜拓撲關系。
讀寫分離雖然可以提升系統的吞吐量和可用性,但同時也帶來了數據不一致的問題,包括多個主庫之間
的數據一致性,以及主庫與從庫之間的數據一致性的問題。 並且,讀寫分離也帶來了與數據分片同樣的
問題,它同樣會使得應用開發和運維人員對數據庫的操作和運維變得更加復雜。
讀寫分離應用方案
在數據量不是很多的情況下,我們可以將數據庫進行讀寫分離,以應對高並發的需求,通過水平擴展從
庫,來緩解查詢的壓力。如下:
分表+讀寫分離
在數據量達到500萬的時候,這時數據量預估千萬級別,我們可以將數據進行分表存儲。
分庫分表+讀寫分離
在數據量繼續擴大,這時可以考慮分庫分表,將數據存儲在不同數據庫的不同表中,如下:
**透明化讀寫分離所帶來的影響,讓使用方盡量像使用一個數據庫一樣使用主從數據庫集群,是
**ShardingSphere讀寫分離模塊的主要設計目標。
主庫、從庫、主從同步、負載均衡
-
核心功能
-
提供一主多從的讀寫分離配置。僅支持單主庫,可以支持獨立使用,也可以配合分庫分表使
用 -
獨立使用讀寫分離,支持SQL透傳。不需要SQL改寫流程
-
同一線程且同一數據庫連接內,能保證數據一致性。如果有寫入操作,后續的讀操作均從主
庫讀取。 -
基於Hint的強制主庫路由。可以強制路由走主庫查詢實時數據,避免主從同步數據延遲。
-
不支持項
-
主庫和從庫的數據同步
-
主庫和從庫的數據同步延遲
-
主庫雙寫或多寫
-
跨主庫和從庫之間的事務的數據不一致。建議在主從架構中,事務中的讀寫均用主庫操作。
強制路由剖析實戰
在一些應用場景中,分片條件並不存在於SQL,而存在於外部業務邏輯。因此需要提供一種通過在外部
業務代碼中指定路由配置的一種方式,在ShardingSphere中叫做Hint。如果使用Hint指定了強制分片
路由,那么SQL將會無視原有的分片邏輯,直接路由至指定的數據節點操作。
HintManager主要使用ThreadLocal管理分片鍵信息,進行hint強制路由。在代碼中向HintManager添
加的配置信息只能在當前線程內有效。
Hint使用場景:
數據分片操作,如果分片鍵沒有在SQL或數據表中,而是在業務邏輯代碼中
讀寫分離操作,如果強制在主庫進行某些數據操作
Hint使用過程:
編寫分庫或分表路由策略,實現HintShardingAlgorithm接口
public class MyHintShardingAlgorithm implements
HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection,
HintShardingValue<Integer> hintShardingValue) {
//添加分庫或分表路由邏輯
}
}
在配置文件指定分庫或分表策略
#強制路由庫和表
spring.shardingsphere.sharding.tables.b_order.databasestrategy.hint.algorithm-class-name=com.lagou.hint.MyHintShardingAlgorithm
spring.shardingsphere.sharding.tables.b_order.table-strategy.hint.algorithmclass-name=com.lagou.hint.MyHintShardingAlgorithm
spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds$->
{0..1}.b_order$->{0..1}
在代碼執行查詢前使用HintManager指定執行策略值
@Test//路由庫和表
public void test(){
HintManager hintManager = HintManager.getInstance();
hintManager.addDatabaseShardingValue("b_order",1);
hintManager.addTableShardingValue("b_order",1);
List<Order> list = orderRepository.findAll();
hintManager.close();
list.forEach(o -> {
System.out.println(o.getOrderId()+" "+o.getUserId()+"
"+o.getOrderPrice());
});
}
在讀寫分離結構中,為了避免主從同步數據延遲及時獲取剛添加或更新的數據,可以采用強制路由
走主庫查詢實時數據,使用hintManager.setMasterRouteOnly設置主庫路由即可。
數據脫敏剖析實戰
數據脫敏是指對某些敏感信息通過脫敏規則進行數據的變形,實現敏感隱私數據的可靠保護。涉及客戶
安全數據或者一些商業性敏感數據,如身份證號、手機號、卡號、客戶號等個人信息按照規定,都需要
進行數據脫敏。
數據脫敏模塊屬於ShardingSphere分布式治理這一核心功能下的子功能模塊。
- 在更新操作時,它通過對用戶輸入的SQL進行解析,並依據用戶提供的脫敏配置對SQL進行改寫,
從而實現對原文數據進行加密,並將密文數據存儲到底層數據庫。 - 在查詢數據時,它又從數據庫中取出密文數據,並對其解密,最終將解密后的原始數據返回給用
戶。
**Apache ShardingSphere自動化&透明化了數據脫敏過程,讓用戶無需關注數據脫敏的實現細節,像
**使用普通數據那樣使用脫敏數據。
整體架構
ShardingSphere提供的Encrypt-JDBC和業務代碼部署在一起。業務方需面向Encrypt-JDBC進行JDBC編
程。
Encrypt-JDBC將用戶發起的SQL進行攔截,並通過SQL語法解析器進行解析、理解SQL行為,再依據用
戶傳入的脫敏規則,找出需要脫敏的字段和所使用的加解密器對目標字段進行加解密處理后,再與底層
數據庫進行交互。
脫敏規則
-
數據源配置:指DataSource的配置信息
-
加密器配置:指使用什么加密策略進行加解密。目前ShardingSphere內置了兩種加解密策略:
AES/MD5 -
脫敏表配置:指定哪個列用於存儲密文數據(cipherColumn)、哪個列用於存儲明文數據
(plainColumn)以及用戶想使用哪個列進行SQL編寫(logicColumn) -
查詢屬性的配置:當底層數據庫表里同時存儲了明文數據、密文數據后,該屬性開關用於決定是直
接查詢數據庫表里的明文數據進行返回,還是查詢密文數據通過Encrypt-JDBC解密后返回。
脫敏處理流程
下圖可以看出ShardingSphere將邏輯列與明文列和密文列進行了列名映射。
下方圖片展示了使用Encrypt-JDBC進行增刪改查時,其中的處理流程和轉換邏輯,如下圖所示。
加密策略解析
ShardingSphere提供了兩種加密策略用於數據脫敏,該兩種策略分別對應ShardingSphere的兩種加解
密的接口,即Encryptor和QueryAssistedEncryptor。
- Encryptor
該解決方案通過提供encrypt(), decrypt()兩種方法對需要脫敏的數據進行加解密。在用戶進行
INSERT, DELETE, UPDATE時,ShardingSphere會按照用戶配置,對SQL進行解析、改寫、路由,
並會調用encrypt()將數據加密后存儲到數據庫, 而在SELECT時,則調用decrypt()方法將從數據庫
中取出的脫敏數據進行逆向解密,最終將原始數據返回給用戶。
當前,ShardingSphere針對這種類型的脫敏解決方案提供了兩種具體實現類,分別是MD5(不可
逆),AES(可逆),用戶只需配置即可使用這兩種內置的方案。 - QueryAssistedEncryptor
相比較於第一種脫敏方案,該方案更為安全和復雜。它的理念是:即使是相同的數據,如兩個用戶
的密碼相同,它們在數據庫里存儲的脫敏數據也應當是不一樣的。這種理念更有利於保護用戶信
息,防止撞庫成功。
它提供三種函數進行實現,分別是encrypt(), decrypt(), queryAssistedEncrypt()。在encrypt()階
段,用戶通過設置某個變動種子,例如時間戳。針對原始數據+變動種子組合的內容進行加密,就
能保證即使原始數據相同,也因為有變動種子的存在,致使加密后的脫敏數據是不一樣的。在
decrypt()可依據之前規定的加密算法,利用種子數據進行解密。queryAssistedEncrypt()用於生成
輔助查詢列,用於原始數據的查詢過程。
當前,ShardingSphere針對這種類型的脫敏解決方案並沒有提供具體實現類,卻將該理念抽象成
接口,提供給用戶自行實現。ShardingSphere將調用用戶提供的該方案的具體實現類進行數據脫
敏。
分布式事務剖析實戰
分布式事務理論
-
CAP(強一致性)
CAP 定理,又被叫作布魯爾定理。對於共享數據系統,最多只能同時擁有CAP其中的兩個,任意兩
個都有其適應的場景。
-
BASE(最終一致性)
BASE 是指基本可用(Basically Available)、軟狀態( Soft State)、最終一致性( Eventual
Consistency)。它的核心思想是即使無法做到強一致性(CAP 就是強一致性),但應用可以采用
適合的方式達到最終一致性。 -
BA指的是基本業務可用性,支持分區失敗;
-
S表示柔性狀態,也就是允許短時間內不同步;
-
E表示最終一致性,數據最終是一致的,但是實時是不一致的。
原子性和持久性必須從根本上保障,為了可用性、性能和服務降級的需要,只有降低一致性和隔離
性的要求。BASE 解決了 CAP 理論中沒有考慮到的網絡延遲問題,在BASE中用軟狀態和最終一
致,保證了延遲后的一致性。
分布式事務模式
了解了分布式事務中的強一致性和最終一致性理論,下面介紹幾種常見的分布式事務的解決方案。
-
2PC模式(強一致性)
2PC是Two-Phase Commit縮寫,即兩階段提交,就是將事務的提交過程分為兩個階段來進行處
理。事務的發起者稱協調者,事務的執行者稱參與者。協調者統一協調參與者執行。 -
階段 1:准備階段
協調者向所有參與者發送事務內容,詢問是否可以提交事務,並等待所有參與者答復。
各參與者執行事務操作,但不提交事務,將 undo 和 redo 信息記入事務日志中。
如參與者執行成功,給協調者反饋 yes;如執行失敗,給協調者反饋 no。 -
階段 2:提交階段
如果協調者收到了參與者的失敗消息或者超時,直接給每個參與者發送回滾(rollback)消息;
否則,發送提交(commit)消息。
2PC 方案實現起來簡單,實際項目中使用比較少,主要因為以下問題: -
性能問題:所有參與者在事務提交階段處於同步阻塞狀態,占用系統資源,容易導致性能瓶
頸。 -
可靠性問題:如果協調者存在單點故障問題,如果協調者出現故障,參與者將一直處於鎖定
狀態。 -
數據一致性問題:在階段 2 中,如果發生局部網絡問題,一部分事務參與者收到了提交消
息,另一部分事務參與者沒收到提交消息,那么就導致了節點之間數據的不一致。 -
3PC模式(強一致性)
3PC 三階段提交,是兩階段提交的改進版本,與兩階段提交不同的是,引入超時機制。同時在協
調者和參與者中都引入超時機制。三階段提交將兩階段的准備階段拆分為 2 個階段,插入了一個
preCommit 階段,解決了原先在兩階段提交中,參與者在准備之后,由於協調者或參與者發生崩
潰或錯誤,而導致參與者無法知曉處於長時間等待的問題。如果在指定的時間內協調者沒有收到參
與者的消息則默認失敗。 -
階段1:canCommit
協調者向參與者發送 commit 請求,參與者如果可以提交就返回 yes 響應,否則返回 no 響
應。 -
階段2:preCommit
協調者根據階段 1 canCommit 參與者的反應情況執行預提交事務或中斷事務操作。 -
參與者均反饋 yes:協調者向所有參與者發出 preCommit 請求,參與者收到
preCommit 請求后,執行事務操作,但不提交;將 undo 和 redo 信息記入事務日志
中;各參與者向協調者反饋 ack 響應或 no 響應,並等待最終指令。 -
任何一個參與者反饋 no或等待超時:協調者向所有參與者發出 abort 請求,無論收到
協調者發出的 abort 請求,或者在等待協調者請求過程中出現超時,參與者均會中斷事
務。 -
階段3:do Commit
該階段進行真正的事務提交,根據階段 2 preCommit反饋的結果完成事務提交或中斷操作。
相比2PC模式,3PC模式降低了阻塞范圍,在等待超時后協調者或參與者會中斷事務。避免了協調
者單點問題,階段 3 中協調者出現問題時(比如網絡中斷等),參與者會繼續提交事務。 -
XA(強一致性)
XA是由X/Open組織提出的分布式事務的規范,是基於兩階段提交協議。 XA規范主要定義了全局
事務管理器(TM)和局部資源管理器(RM)之間的接口。目前主流的關系型數據庫產品都是實現
了XA接口。
XA之所以需要引入事務管理器,是因為在分布式系統中,從理論上講兩台機器理論上無法達到一
致的狀態,需要引入一個單點進行協調。由全局事務管理器管理和協調的事務,可以跨越多個資源
(數據庫)和進程。
事務管理器用來保證所有的事務參與者都完成了准備工作(第一階段)。如果事務管理器收到所有參
與者都准備好的消息,就會通知所有的事務都可以提交了(第二階段)。MySQL 在這個XA事務中
扮演的是參與者的角色,而不是事務管理器。 -
TCC模式(最終一致性)
TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 於 2007 年發表的一篇名為《Life
beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。TCC 是服務化的兩階段
編程模型,其 Try、Confirm、Cancel 3 個方法均由業務編碼實現: -
Try 操作作為一階段,負責資源的檢查和預留;
-
Confirm 操作作為二階段提交操作,執行真正的業務;
-
Cancel 是預留資源的取消;
TCC事務模式相對於 XA 等傳統模型如下圖所示:
TCC 模式相比於 XA,解決了如下幾個缺點:
-
解決了協調者單點,由主業務方發起並完成這個業務活動。業務活動管理器可以變成多點,
引入集群。 -
同步阻塞:引入超時機制,超時后進行補償,並且不會鎖定整個資源,將資源轉換為業務邏
輯形式,粒度變小。 -
數據一致性,有了補償機制之后,由業務活動管理器控制一致性。
-
消息隊列模式(最終一致性)
消息隊列的方案最初是由 eBay 提出,基於TCC模式,消息中間件可以基於 Kafka、RocketMQ 等
消息隊列。此方案的核心是將分布式事務拆分成本地事務進行處理,將需要分布式處理的任務通過
消息日志的方式來異步執行。消息日志可以存儲到本地文本、數據庫或MQ中間件,再通過業務規
則人工發起重試。
下面描述下事務的處理流程:
-
步驟1:事務主動方處理本地事務。
事務主動方在本地事務中處理業務更新操作和MQ寫消息操作。 -
步驟 2:事務主動方通過消息中間件,通知事務被動方處理事務通知事務待消息。
事務主動方主動寫消息到MQ,事務消費方接收並處理MQ中的消息。 -
步驟 3:事務被動方通過MQ中間件,通知事務主動方事務已處理的消息,事務主動方根據反
饋結果提交或回滾事務。
為了數據的一致性,當流程中遇到錯誤需要重試,容錯處理規則如下:
-
當步驟 1 處理出錯,事務回滾,相當於什么都沒發生。
-
當步驟 2 處理出錯,由於未處理的事務消息還是保存在事務發送方,可以重試或撤銷本地業
務操作。 -
如果事務被動方消費消息異常,需要不斷重試,業務處理邏輯需要保證冪等。
-
如果是事務被動方業務上的處理失敗,可以通過MQ通知事務主動方進行補償或者事務回滾。
-
如果多個事務被動方已經消費消息,事務主動方需要回滾事務時需要通知事務被動方回滾。
-
Saga模式(最終一致性)
Saga這個概念源於 1987 年普林斯頓大學的 Hecto 和 Kenneth 發表的一篇數據庫論文Sagas ,一
個Saga事務是一個有多個短時事務組成的長時的事務。 在分布式事務場景下,我們把一個Saga分
布式事務看做是一個由多個本地事務組成的事務,每個本地事務都有一個與之對應的補償事務。在
Saga事務的執行過程中,如果某一步執行出現異常,Saga事務會被終止,同時會調用對應的補償 事務完成相關的恢復操作,這樣保證Saga相關的本地事務要么都是執行成功,要么通過補償恢復
成為事務執行之前的狀態。(自動反向補償機制)。
Saga 事務基本協議如下: -
每個 Saga 事務由一系列冪等的有序子事務(sub-transaction) Ti 組成。
-
每個 Ti 都有對應的冪等補償動作 Ci,補償動作用於撤銷 Ti 造成的結果。
Saga是一種補償模式,它定義了兩種補償策略: -
向前恢復(forward recovery):對應於上面第一種執行順序,發生失敗進行重試,適用於
必須要成功的場景。 -
向后恢復(backward recovery):對應於上面提到的第二種執行順序,發生錯誤后撤銷掉之前所
有成功的子事務,使得整個 Saga 的執行結果撤銷。
Saga 的執行順序有兩種,如上圖:
-
事務正常執行完成:T1, T2, T3, ..., Tn,例如:減庫存(T1),創建訂單(T2),支付(T3),依次有序完
成整個事務。 -
事務回滾:T1, T2, ..., Tj, Cj,..., C2, C1,其中 0 < j < n,例如:減庫存(T1),創建訂單(T2),支付
(T3),支付失敗,支付回滾(C3),訂單回滾(C2),恢復庫存(C1)。 -
Seata框架
Fescar開源項目,最初願景是能像本地事務一樣控制分布式事務,解決分布式環境下的難題。
Seata(Simple Extensible Autonomous Transaction Architecture)是一套一站式分布式事務解
決方案,是阿里集團和螞蟻金服聯合打造的分布式事務框架。Seata目前的事務模式有AT、TCC、
Saga和XA,默認是AT模式,AT本質上是2PC協議的一種實現。
Seata AT事務模型包含TM(事務管理器),RM(資源管理器),TC(事務協調器)。其中TC是一個獨立
的服務需要單獨部署,TM和RM以jar包的方式同業務應用部署在一起,它們同TC建立長連接,在
整個事務生命周期內,保持RPC通信。 -
全局事務的發起方作為TM,全局事務的參與者作為RM
-
TM負責全局事務的begin和commit/rollback
-
RM負責分支事務的執行結果上報,並且通過TC的協調進行commit/rollback。
在 Seata 中,AT時分為兩個階段的,第一階段,就是各個階段本地提交操作;第二階段會根據第
一階段的情況決定是進行全局提交還是全局回滾操作。具體的執行流程如下:
-
TM 開啟分布式事務,負責全局事務的begin和commit/rollback(TM 向 TC 注冊全局事務記
錄); -
RM 作為參與者,負責分支事務的執行結果上報,並且通過TC的協調進行
commit/rollback(RM 向 TC 匯報資源准備狀態 ); -
RM分支事務結束,事務一階段結束;
-
根據TC 匯總事務信息,由TM發起事務提交或回滾操作;
-
TC 通知所有 RM 提交/回滾資源,事務二階段結束;
Sharding-JDBC整合XA原理
Java通過定義JTA接口實現了XA的模型,JTA接口里的ResourceManager需要數據庫廠商提供XA的驅動
實現,而TransactionManager則需要事務管理器的廠商實現,傳統的事務管理器需要同應用服務器綁
定,因此使用的成本很高。 而嵌入式的事務管器可以以jar包的形式提供服務,同ShardingSphere集成
后,可保證分片后跨庫事務強一致性。
ShardingSphere支持以下功能:
-
支持數據分片后的跨庫XA事務
-
兩階段提交保證操作的原子性和數據的強一致性
-
服務宕機重啟后,提交/回滾中的事務可自動恢復
-
SPI機制整合主流的XA事務管理器,默認Atomikos
-
同時支持XA和非XA的連接池
-
提供spring-boot和namespace的接入端
ShardingSphere整合XA事務時,分離了XA事務管理和連接池管理,這樣接入XA時,可以做到對業務的
零侵入。
- Begin(開啟XA全局事務)
XAShardingTransactionManager會調用具體的XA事務管理器開啟XA的全局事務。 - 執行物理SQL
ShardingSphere進行解析/優化/路由后會生成SQL操作,執行引擎為每個物理SQL創建連接的同
時,物理連接所對應的XAResource也會被注冊到當前XA事務中。事務管理器會在此階段發送
XAResource.start命令給數據庫,數據庫在收到XAResource.end命令之前的所有SQL操作,會被
標記為XA事務。
例如:
XAResource1.start ## Enlist階段執行
statement.execute("sql1"); ## 模擬執行一個分片SQL1
statement.execute("sql2"); ## 模擬執行一個分片SQL2
XAResource1.end ## 提交階段執行
這里sql1和sql2將會被標記為XA事務。
- Commit/rollback(提交XA事務)
XAShardingTransactionManager收到接入端的提交命令后,會委托實際的XA事務管理進行提交
動作,這時事務管理器會收集當前線程里所有注冊的XAResource,首先發送XAResource.end指
令,用以標記此XA事務的邊界。 接着會依次發送prepare指令,收集所有參與XAResource投票,
如果所有XAResource的反饋結果都是OK,則會再次調用commit指令進行最終提交,如果有一個
XAResource的反饋結果為No,則會調用rollback指令進行回滾。 在事務管理器發出提交指令后,
任何XAResource產生的異常都會通過recovery日志進行重試,來保證提交階段的操作原子性,和
數據強一致性。
例如:
XAResource1.prepare ## ack: yes
XAResource2.prepare ## ack: yes
XAResource1.commit
XAResource2.commit
XAResource1.prepare ## ack: yes
XAResource2.prepare ## ack: no
XAResource1.rollback
XAResource2.rollback
Sharding-JDBC整合Saga原理
ShardingSphere的柔性事務已通過第三方servicecomb-saga組件實現的,通過SPI機制注入使用。
ShardingSphere是基於反向SQL技術實現的反向補償操作,它將對數據庫進行更新操作的SQL自動生成
反向SQL,並交由Saga-actuator引擎執行。使用方則無需再關注如何實現補償方法,將柔性事務管理器
的應用范疇成功的定位回了事務的本源——數據庫層面。ShardingSphere支持以下功能:
-
完全支持跨庫事務
-
支持失敗SQL重試及最大努力送達
-
支持反向SQL、自動生成更新快照以及自動補償
-
默認使用關系型數據庫進行快照及事務日志的持久化,支持使用SPI的方式加載其他類型的持久化
Saga柔性事務的實現類為SagaShardingTransactionMananger, ShardingSphere通過Hook的方式攔
截邏輯SQL的解析和路由結果,這樣,在分片物理SQL執行前,可以生成逆向SQL,在事務提交階段再
把SQL調用鏈交給Saga引擎處理。
-
Init(Saga引擎初始化)
包含Saga柔性事務的應用啟動時,saga-actuator引擎會根據saga.properties的配置進行初始化的
流程。 -
Begin(開啟Saga全局事務)
每次開啟Saga全局事務時,將會生成本次全局事務的上下文(SagaTransactionContext),事務
上下文記錄了所有子事務的正向SQL和逆向SQL,作為生成事務調用鏈的元數據使用。 -
執行物理SQL
在物理SQL執行前,ShardingSphere根據SQL的類型生成逆向SQL,這里是通過Hook的方式攔截
Parser的解析結果進行實現。 -
Commit/rollback(提交Saga事務)
提交階段會生成Saga執行引擎所需的調用鏈路圖,commit操作產生ForwardRecovery(正向SQL
補償)任務,rollback操作產生BackwardRecovery任務(逆向SQL補償)。
Sharding-JDBC整合Seata原理
分布式事務的實現目前主要分為兩階段的XA強事務和BASE柔性事務。
Seata AT事務作為BASE柔性事務的一種實現,可以無縫接入到ShardingSphere生態中。在整合Seata
AT事務時,需要把TM,RM,TC的模型融入到ShardingSphere 分布式事務的SPI的生態中。在數據庫
資源上,Seata通過對接DataSource接口,讓JDBC操作可以同TC進行RPC通信。同樣,
ShardingSphere也是面向DataSource接口對用戶配置的物理DataSource進行了聚合,因此把物理
DataSource二次包裝為Seata 的DataSource后,就可以把Seata AT事務融入到ShardingSphere的分片
中。
-
Init(Seata引擎初始化)
包含Seata柔性事務的應用啟動時,用戶配置的數據源會按seata.conf的配置,適配成Seata事務所
需的DataSourceProxy,並且注冊到RM中。 -
Begin(開啟Seata全局事務)
TM控制全局事務的邊界,TM通過向TC發送Begin指令,獲取全局事務ID,所有分支事務通過此全
局事務ID,參與到全局事務中;全局事務ID的上下文存放在當前線程變量中。 -
執行分片物理SQL
處於Seata全局事務中的分片SQL通過RM生成undo快照,並且發送participate指令到TC,加入到
全局事務中。ShardingSphere的分片物理SQL是按多線程方式執行,因此整合Seata AT事務時,
需要在主線程和子線程間進行全局事務ID的上下文傳遞,這同服務間的上下文傳遞思路完全相
同。 -
Commit/rollback(提交Seata事務)
提交Seata事務時,TM會向TC發送全局事務的commit和rollback指令,TC根據全局事務ID協調所
有分支事務進行commit和rollback。
Sharding-JDBC分布式事務實戰
ShardingSphere整合了XA、Saga和Seata模式后,為分布式事務控制提供了極大的便利,我們可以在
應用程序編程時,采用以下統一模式進行使用。
- 引入Maven依賴
//XA模式
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
//Saga模式
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-transaction-base-saga</artifactId>
<version>${shardingsphere-spi-impl.version}</version>
</dependency>
//Seata模式
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
- JAVA編碼方式設置事務類型
TransactionTypeHolder.set(TransactionType.XA);
TransactionTypeHolder.set(TransactionType.BASE);
- 參數配置
ShardingSphere默認的XA事務管理器為Atomikos,通過在項目的classpath中添加jta.properties
來定制化Atomikos配置項。具體的配置規則如下:
#指定是否啟動磁盤日志,默認為true。在生產環境下一定要保證為true,否則數據的完整性無法保
證 co
m.atomikos.icatch.enable_logging=true
#JTA/XA資源是否應該自動注冊
com.atomikos.icatch.automatic_resource_registration=true
#JTA事務的默認超時時間,默認為10000ms
com.atomikos.icatch.default_jta_timeout=10000
#事務的最大超時時間,默認為300000ms。這表示事務超時時間由
UserTransaction.setTransactionTimeout()較大者決定。4.x版本之后,指定為0的話則表示
不設置超時時間
com.atomikos.icatch.max_timeout=300000
#指定在兩階段提交時,是否使用不同的線程(意味着並行)。3.7版本之后默認為false,更早的版本
默認為true。如果為false,則提交將按照事務中訪問資源的順序進行。
com.atomikos.icatch.threaded_2pc=false
#指定最多可以同時運行的事務數量,默認值為50,負數表示沒有數量限制。在調用
UserTransaction.begin()方法時,可能會拋出一個”Max number of active transactions
reached”異常信息,表示超出最大事務數限制
com.atomikos.icatch.max_actives=50
#是否支持subtransaction,默認為true
com.atomikos.icatch.allow_subtransactions=true
#指定在可能的情況下,否應該join 子事務(subtransactions),默認值為true。如果設置為
false,對於有關聯的不同subtransactions,不會調用XAResource.start(TM_JOIN)
com.atomikos.icatch.serial_jta_transactions=true
#指定JVM關閉時是否強制(force)關閉事務管理器,默認為false
com.atomikos.icatch.force_shutdown_on_vm_exit=false
#在正常關閉(no-force)的情況下,應該等待事務執行完成的時間,默認為Long.MAX_VALUE
com.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807
========= 日志記錄配置=======
#事務日志目錄,默認為./。
com.atomikos.icatch.log_base_dir=./
#事務日志文件前綴,默認為tmlog。事務日志存儲在文件中,文件名包含一個數字后綴,日志文件
以.log為擴展名,如tmlog1.log。遇到checkpoint時,新的事務日志文件會被創建,數字增加。
com.atomikos.icatch.log_base_name=tmlog
#指定兩次checkpoint的時間間隔,默認為500
com.atomikos.icatch.checkpoint_interval=500
=========日志恢復配置=============
#指定在多長時間后可以清空無法恢復的事務日志(orphaned),默認86400000ms
com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
#指定兩次恢復掃描之間的延遲時間。默認值為與com.atomikos.icatch.default_jta_timeout
相同
com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout
} #
提交失敗時,再拋出一個異常之前,最多可以重試幾次,默認值為5
com.atomikos.icatch.oltp_max_retries=5
#提交失敗時,每次重試的時間間隔,默認10000ms
com.atomikos.icatch.oltp_retry_interval=10000
Saga可以通過在項目的classpath中添加 saga.properties 來定制化Saga事務的配置項。配置項
的屬性及說明如下:
屬性名稱 | 默認 值 | 說明 |
---|---|---|
saga.actuator.executor.size | 5 | 使用的線程池大小 |
saga.actuator.transaction.max.retries | 5 | 失敗SQL的最大重試次 數 |
saga.actuator.compensation.max.retries | 5 | 失敗SQL的最大嘗試補 償次數 |
saga.actuator.transaction.retry.delay.milliseconds | 5000 | 失敗SQL的重試間隔, 單位毫秒 |
saga.actuator.compensation.retry.delay.milliseconds | 3000 | 失敗SQL的補償間隔, 單位毫秒 |
saga.persistence.enabled | false | 是否對日志進行持久 化 |
saga.persistence.ds.url | 無 | 事務日志數據庫JDBC 連接 |
saga.persistence.ds.username | 無 | 事務日志數據庫用戶 名 |
saga.persistence.ds.password | 無 | 事務日志數據庫密碼 |
saga.persistence.ds.max.pool.size | 50 | 事務日志連接池最大 連接數 |
saga.persistence.ds.min.pool.size | 1 | 事務日志連接池最小 連接數 |
saga.persistence.ds.max.life.time.milliseconds | 0(無 限制) | 事務日志連接池最大 存活時間,單位毫秒 |
saga.persistence.ds.idle.timeout.milliseconds | 60 * 1000 | 事務日志連接池空閑 回收時間,單位毫秒 |
saga.persistence.ds.connection.timeout.milliseconds | 30 * 1000 | 事務日志連接池超時 時間,單位毫秒 |
SPI 加載剖析
在Apache ShardingSphere中,很多功能實現類的加載方式是通過SPI注入的方式完成的。 Service
Provider Interface (SPI)是Java提供的一套被第三方實現或擴展的API,它可以用於實現框架擴展或
組件替換。
-
SQL解析
SQL解析的接口用於規定用於解析SQL的ANTLR語法文件。
主要接口是SQLParserEntry,其內置實現類有MySQLParserEntry, PostgreSQLParserEntry,
SQLServerParserEntry和OracleParserEntry。 -
數據庫協議
數據庫協議的接口用於Sharding-Proxy解析與適配訪問數據庫的協議。
主要接口是DatabaseProtocolFrontendEngine,其內置實現類有
MySQLProtocolFrontendEngine和PostgreSQLProtocolFrontendEngine。 -
數據脫敏
數據脫敏的接口用於規定加解密器的加密、解密、類型獲取、屬性設置等方式。
主要接口有兩個:Encryptor和QueryAssistedEncryptor,其中Encryptor的內置實現類有
AESEncryptor和MD5Encryptor。 -
分布式主鍵
分布式主鍵的接口主要用於規定如何生成全局性的自增、類型獲取、屬性設置等。
主要接口為ShardingKeyGenerator,其內置實現類有UUIDShardingKeyGenerator和
SnowflakeShardingKeyGenerator。 -
分布式事務
分布式事務的接口主要用於規定如何將分布式事務適配為本地事務接口。
主要接口為ShardingTransactionManager,其內置實現類有XAShardingTransactionManager和
SeataATShardingTransactionManager。 -
XA事務管理器
XA事務管理器的接口主要用於規定如何將XA事務的實現者適配為統一的XA事務接口。
主要接口為XATransactionManager,其內置實現類有AtomikosTransactionManager,
NarayanaXATransactionManager和BitronixXATransactionManager。 -
注冊中心
注冊中心的接口主要用於規定注冊中心初始化、存取數據、更新數據、監控等行為。
主要接口為RegistryCenter,其內置實現類有Zookeeper。
編排治理剖析
編排治理模塊提供配置中心/注冊中心(以及規划中的元數據中心)、配置動態化、數據庫熔斷禁用、
調用鏈路等治理能力。
-
配置中心
配置集中化:越來越多的運行時實例,使得散落的配置難於管理,配置不同步導致的問題十分嚴
重。將配置集中於配置中心,可以更加有效進行管理。
配置動態化:配置修改后的分發,是配置中心可以提供的另一個重要能力。它可支持數據源、表與
分片及讀寫分離策略的動態切換。 -
配置中心數據結構
配置中心在定義的命名空間的config下,以YAML格式存儲,包括數據源,數據分片,讀寫分
離、Properties配置,可通過修改節點來實現對於配置的動態管理。
config
├──authentication # Sharding-Proxy權限配置
├──props # 屬性配置
├──schema # Schema配置
├ ├──sharding_db # SchemaName配置
├ ├ ├──datasource # 數據源配置
├ ├ ├──rule # 數據分片規則配置
├ ├──masterslave_db # SchemaName配置
├ ├ ├──datasource # 數據源配置
├ ├ ├──rule # 讀寫分離規則
- config/authentication
password: root
username: root
- config/sharding/props
sql.show: true
- config/schema/schemeName/datasource
多個數據庫連接池的集合,不同數據庫連接池屬性自適配(例如:DBCP,C3P0,Druid,
HikariCP)。
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
properties:
url: jdbc:mysql://127.0.0.1:3306/lagou1?
serverTimezone=UTC&useSSL=false
password: root
username: root
maxPoolSize: 50
minPoolSize: 1
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
properties:
url: jdbc:mysql://127.0.0.1:3306/lagou2?
serverTimezone=UTC&useSSL=false
password: root
username: root
maxPoolSize: 50
minPoolSize: 1
config/schema/sharding_db/rule
數據分片配置,包括數據分片配置。
tables:
b_order:
actualDataNodes: ds_$->{0..1}.b_order_$->{0..1}
databaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_$->{user_id % 2}
keyGenerator:
column: order_id
logicTable: b_order
tableStrategy:
inline:
shardingColumn: order_id
algorithmExpression: b_order_$->{order_id % 2}
b_order_item:
actualDataNodes: ds_$->{0..1}.b_order_item_$->{0..1}
databaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_$->{user_id % 2}
keyGenerator:
column: order_item_id
logicTable: b_order_item
tableStrategy:
inline:
shardingColumn: order_id
algorithmExpression: b_order_item_$->{order_id % 2}
- config/schema/masterslave/rule讀寫分離獨立使用時使用該配置。
name: ds_ms
masterDataSourceName: master
slaveDataSourceNames:
- ds_slave0
- ds_slave1
loadBalanceAlgorithmType: ROUND_ROBIN
-
動態生效
在注冊中心上修改、刪除、新增相關配置,會動態推送到生產環境並立即生效。 -
注冊中心
相對於配置中心管理配置數據,注冊中心存放運行時的動態/臨時狀態數據,比如可用的proxy的實
例,需要禁用或熔斷的datasource實例。通過注冊中心,可以提供熔斷數據庫訪問程序對數據庫
的訪問和禁用從庫的訪問的編排治理能力。治理仍然有大量未完成的功能(比如流控等) -
`注冊中心數據結構
注冊中心在定義的命名空間的state下,創建數據庫訪問對象運行節點,用於區分不同數據庫
訪問實例。包括instances和datasources節點。
instances
├──your_instance_ip_a@-@your_instance_pid_x
├──your_instance_ip_b@-@your_instance_pid_y
├──....
datasources
├──ds0
├──ds1
├──....
-
state/instances
數據庫訪問對象運行實例信息,子節點是當前運行實例的標識。 運行實例標識由運行服務器
的IP地址和PID構成。運行實例標識均為臨時節點,當實例上線時注冊,下線時自動清理。
注冊中心監控這些節點的變化來治理運行中實例對數據庫的訪問等。 -
state/datasources
可以控制讀寫分離,可動態添加刪除以及禁用。 -
熔斷實例
可在IP地址@-@PID節點寫入DISABLED(忽略大小寫)表示禁用該實例,刪除DISABLED表
示啟用。
Zookeeper命令如下:
[zk: localhost:2181(CONNECTED) 0] set
/your_zk_namespace/your_app_name/state/instances/your_instance_ip_a@-
@your_instance_pid_x DISABLED
- 禁用從庫
在讀寫分離場景下,可在數據源名稱子節點中寫入DISABLED表示禁用從庫數據源,刪除
DISABLED或節點表示啟用。
Zookeeper命令如下:
[zk: localhost:2181(CONNECTED) 0] set
/your_zk_namespace/your_app_name/state/datasources/your_slave_datasource_nam
e DISABLED
- 支持的配置中心和注冊中心
ShardingSphere在數據庫治理模塊使用SPI方式載入數據到配置中心/注冊中心,進行實例熔斷和
數據庫禁用。 目前,ShardingSphere內部支持Zookeeper和Etcd這種常用的配置中心/注冊中
心。 此外,您可以使用其他第三方配置中心/注冊中心,例如Apollo、Nacos等,並通過SPI的方式
注入到ShardingSphere,從而使用該配置中心/注冊中心,實現數據庫治理功能。 - 應用性能監控
APM是應用性能監控的縮寫。目前APM的主要功能着眼於分布式系統的性能診斷,其主要功能包
括調用鏈展示,應用拓撲分析等。
ShardingSphere並不負責如何采集、存儲以及展示應用性能監控的相關數據,而是將SQL解析與
SQL執行這兩塊數據分片的最核心的相關信息發送至應用性能監控系統,並交由其處理。 換句話
說,ShardingSphere僅負責產生具有價值的數據,並通過標准協議遞交至相關系統。
ShardingSphere可以通過兩種方式對接應用性能監控系統。
- 使用OpenTracing API發送性能追蹤數據。面向OpenTracing協議的APM產品都可以和
ShardingSphere自動對接,比如SkyWalking,Zipkin和Jaeger。 - 使用SkyWalking的自動探針。 ShardingSphere團隊與SkyWalking團隊共同合作,在
SkyWalking中實現了ShardingSphere自動探針,可以將相關的應用性能數據自動發送到
SkyWalking中。
Sharding-Proxy實戰
Sharding-Proxy是ShardingSphere的第二個產品,定位為透明化的數據庫代理端,提供封裝了數據庫
二進制協議的服務端版本,用於完成對異構語言的支持。 目前先提供MySQL版本,它可以使用任何兼
容MySQL協議的訪問客戶端(如:MySQL Command Client, MySQL Workbench等操作數據,對DBA更
加友好。
- 向應用程序完全透明,可直接當做MySQL使用
- 適用於任何兼容MySQL協議的客戶端
Sharding-Proxy的優勢在於對異構語言的支持,以及為DBA提供可操作入口。
Sharding-Proxy使用過程:
-
下載Sharding-Proxy的最新發行版;
-
解壓縮后修改conf/server.yaml和以config-前綴開頭的文件,進行分片規則、讀寫分離規則配置
編輯%SHARDING_PROXY_HOME%\conf\config-xxx.yaml
編輯%SHARDING_PROXY_HOME%\conf\server.yaml -
引入依賴jar
如果后端連接MySQL數據庫,需要下載MySQL驅動, 解壓縮后將mysql-connector-java-
5.1.48.jar拷貝到${sharding-proxy}\lib目錄。
如果后端連接PostgreSQL數據庫,不需要引入額外依賴。
- Linux操作系統請運行bin/start.sh,Windows操作系統請運行bin/start.bat啟動Sharding-Proxy。
使用默認配置啟動:\({sharding-proxy}\bin\start.sh 配置端口啟動:\){sharding-proxy}\bin\start.sh ${port}
-
使用客戶端工具連接。如: mysql -h 127.0.0.1 -P 3307 -u root -p root
若想使用Sharding-Proxy的數據庫治理功能,則需要使用注冊中心實現實例熔斷和從庫禁用功能。
Sharding-Proxy默認提供了Zookeeper的注冊中心解決方案。只需按照配置規則進行注冊中心的配置,
即可使用。
注意事項 -
Sharding-Proxy 默認不支持hint,如需支持,請在conf/server.yaml中,將props的屬性
proxy.hint.enabled設置為true。在Sharding-Proxy中,HintShardingAlgorithm的泛型只能是
String類型。 -
Sharding-Proxy默認使用3307端口,可以通過啟動腳本追加參數作為啟動端口號。如:
bin/start.sh 3308 -
Sharding-Proxy使用conf/server.yaml配置注冊中心、認證信息以及公用屬性。
-
Sharding-Proxy支持多邏輯數據源,每個以"config-"做前綴命名yaml配置文件,即為一個邏輯數
據源。