面試問:“你為啥自研路由組件,咋不用sharding-jdbc”
作者:小傅哥
博客:https://bugstack.cn
面試問:“你為什么要自研,市面不是有嗎,怎么回答?” 可以從以下3個(gè)點(diǎn)解答;
- 維護(hù)性;市面的路由組件比如 shardingsphere 但過于龐大,還需要隨著版本做一些升級(jí),市面上已經(jīng)有很多版本了。而我們需要更少的維護(hù)成本。
- 擴(kuò)展性;結(jié)合自身的業(yè)務(wù)需求,我們的路由組件可以分庫分表、自定義路由協(xié)議,掃描指定庫表數(shù)據(jù)等各類方式。研發(fā)擴(kuò)展性好,簡單易用。
- 安全性;自研的組件更好的控制了安全問題。當(dāng)然,我們的組件主要是為了更好的適應(yīng)目前系統(tǒng)的訴求,所以使用自研的方式處理。甚至各個(gè)大廠也都自研一整套分布式服務(wù),來讓自己的系統(tǒng)更加穩(wěn)定可控。
接著問,你們?yōu)槭裁捶謳旆直恚?/p>
我們分庫分表用的非常熟。但不能為了等到系統(tǒng)到了200萬數(shù)據(jù),才拆。那么工作量會(huì)非常大 我們的做法是,因?yàn)橛谐墒旆桨?,所以前期就分庫分表了。但,為了解釋服?wù)器空間。所以把分庫分表的庫,用服務(wù)器虛擬出來機(jī)器安裝。這樣即不過多的占用服務(wù)器資源,也方便后續(xù)數(shù)據(jù)量真的上來了,好拆分。
同時(shí),Xxx系統(tǒng),是瞬時(shí)峰值較高的系統(tǒng),歷史數(shù)據(jù)不一定多。所以我們希望,用戶可以快速的檢索到個(gè)人數(shù)據(jù),做最優(yōu)響應(yīng)。因?yàn)榇蠹叶贾溃琗xx這東西,push發(fā)完,基本就1~3分鐘結(jié)束,10分鐘人都沒了。所以我們這也是做了分庫分表的理由。
不過用和不用是一方面,會(huì)和不會(huì)是另外一方面。不能因?yàn)椴粫?huì)所以不用,不用是因?yàn)椴煌瑘?chǎng)景的所需。像 Apache ShardingSphere 在很多大的場(chǎng)景還是非常好用的,所以我們需要學(xué)習(xí)積累。積累不同的思想和設(shè)計(jì),以及積累技術(shù)的運(yùn)用。
- 碎片知識(shí):https://bugstack.cn/md/road-map/road-map.html
- 實(shí)戰(zhàn)項(xiàng)目:https://bugstack.cn/md/zsxq/introduce.html
本文的宗旨在于通過簡單干凈實(shí)踐的方式教會(huì)讀者,快速 Easy 的使用上 sharding-jdbc 這個(gè)笨重的大家伙!—— 這篇文章并不復(fù)雜,但市面上的案例,還真的很少有能拿過來就能運(yùn)行起來的!
之所以說"笨重",是因?yàn)?Apache ShardingSphere 不只是簡單意義上的路由組件,而是一款分布式 SQL 事務(wù)和查詢引擎,可通過數(shù)據(jù)分片、彈性伸縮、加密等能力對(duì)任意數(shù)據(jù)庫進(jìn)行增強(qiáng)。同時(shí)它又在迭代過程中,衍生出了很多的版本,以及對(duì)應(yīng)了不同的使用方式。并在 ShardingSphere 5.3 以后又做了不小的架構(gòu)調(diào)整。所以很多伙伴在使用的時(shí)候,經(jīng)常是找了一上午的資料,到下午下班還沒對(duì)接上。
本文涉及的工程:
- xfg-dev-tech-shardingjdbc:https://gitcode.net/KnowledgePlanet/road-map/xfg-dev-tech-shardingjdbc
- 官網(wǎng):https://shardingsphere.apache.org/index_zh.html
一、路由本質(zhì)
分庫分表的本質(zhì)是數(shù)據(jù)的散列,分?jǐn)倲?shù)據(jù)庫資源壓力。如把原本在一臺(tái)機(jī)器上的數(shù)據(jù)庫存放1000萬數(shù)據(jù),分?jǐn)偟絥臺(tái)機(jī)上,拆分這1000萬的數(shù)據(jù)和后續(xù)的增量。讓每個(gè)數(shù)據(jù)庫資源來分?jǐn)傇拘枰慌_(tái)數(shù)據(jù)庫所提供的服務(wù)。
圖片
- 當(dāng)使用分庫分表以后,并確定如使用用戶ID作為路由分片鍵。那么所做的CRUD操作,都是需要使用到這個(gè)用戶ID,并根據(jù)ID做路由庫表計(jì)算。
- 在大廠中,開發(fā)項(xiàng)目。并不會(huì)說目前這個(gè)業(yè)務(wù)需求規(guī)模不大,就不使用分庫分表,而是分庫分表都是非常成熟的方案,并不會(huì)因?yàn)槭褂昧司徒档秃艽蟮拈_發(fā)效率。所以基本就是默認(rèn)就使用了。
- 那么使用了分庫分表,就會(huì)很占用資源嗎。也不會(huì),因?yàn)閯傞_始業(yè)務(wù)體量不大的時(shí)候,都是虛擬機(jī)交叉使用,你的1臺(tái)物理機(jī)虛出來10個(gè)虛擬機(jī),大家交叉使用主備。這樣你只是使用了分庫分表,但庫表的實(shí)際資源沒占用那么多。
二、使用案例
- jdk 1.8 +
- ShardingSphere 5.4.1
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc-core -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
- 因?yàn)樾枰馕?yaml 但默認(rèn)的 SpringBoot 提供版本不支持 shardingsphere-jdbc-core 使用。
1. 工程結(jié)構(gòu)
圖片
- 工程中,提供了 docker 配置數(shù)據(jù)庫環(huán)境操作,并提供了對(duì)應(yīng)的建表測(cè)試語句。如果你本機(jī)已經(jīng)安裝了數(shù)據(jù)庫,那么只做庫表語句導(dǎo)入以及 yml 配置數(shù)據(jù)庫連接信息就可以。
- sharding-jdbc-dev.yaml 配置了詳細(xì)的分庫分表路由信息,在 algorithms 下配置的是庫表的路由算法。這里的算法要根據(jù)實(shí)際自己使用中庫表數(shù)量來設(shè)置&設(shè)計(jì),避免發(fā)生較大的數(shù)據(jù)偏移。
- 配置完 sharding-jdbc-dev.yaml 需要在 application-dev.yml 中配置上 sharding-jdbc-dev.yaml 路徑,這樣才能正確加載。
- 如果你還需要定義出自己特定的路由算法,它還支持自己寫個(gè)實(shí)現(xiàn)類的方式處理。
2. 算法配置
sharding/sharding-jdbc-dev.yaml
# https://shardingsphere.apache.org/index_zh.html
mode:
# 運(yùn)行模式類型??蛇x配置:內(nèi)存模式 Memory、單機(jī)模式 Standalone、集群模式 Cluster - 目前為單機(jī)模式
type: Standalone
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:13306/xfg_dev_tech_db_00?useUnicode=true&characterEncoding=utf8&autoRecnotallow=true&zeroDateTimeBehavior=convertToNull&serverTimeznotallow=UTC&useSSL=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 15
minPoolSize: 5
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:13306/xfg_dev_tech_db_01?useUnicode=true&characterEncoding=utf8&autoRecnotallow=true&zeroDateTimeBehavior=convertToNull&serverTimeznotallow=UTC&useSSL=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 15
minPoolSize: 5
rules:
- !SHARDING
# 庫的路由
defaultDatabaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: database_inline
# 表的路由
tables:
user_order:
actualDataNodes: ds_$->{0..1}.user_order_$->{0..3}
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: user_order_inline
# 路由算法
shardingAlgorithms:
# 庫-路由算法 2是兩個(gè)庫,庫的數(shù)量。庫的數(shù)量用哈希模2來計(jì)算。
database_inline:
type: INLINE
props:
algorithm-expression: ds_$->{Math.abs(user_id.hashCode()) % 2}
# 表-路由算法 4是一個(gè)庫里,表的數(shù)量。4 - 1 為了獲得 011 這樣的二進(jìn)制值。不推薦 user_order_$->{Math.abs(user_id.hashCode()) % 2} 作為表的路由
user_order_inline:
type: INLINE
props:
algorithm-expression: user_order_$->{(user_id.hashCode() ^ (user_id.hashCode()) >>> 16) & (4 - 1)}
props:
# 是否在日志中打印 SQL。
# 打印 SQL 可以幫助開發(fā)者快速定位系統(tǒng)問題。日志內(nèi)容包含:邏輯 SQL,真實(shí) SQL 和 SQL 解析結(jié)果。
# 如果開啟配置,日志將使用 Topic ShardingSphere-SQL,日志級(jí)別是 INFO。false
sql-show: true
# 是否在日志中打印簡單風(fēng)格的 SQL。false
sql-simple: true
# 用于設(shè)置任務(wù)處理線程池的大小。每個(gè) ShardingSphereDataSource 使用一個(gè)獨(dú)立的線程池,同一個(gè) JVM 的不同數(shù)據(jù)源不共享線程池。
executor-size: 20
# 查詢請(qǐng)求在每個(gè)數(shù)據(jù)庫實(shí)例中所能使用的最大連接數(shù)。1
max-connections-size-per-query: 1
# 在程序啟動(dòng)和更新時(shí),是否檢查分片元數(shù)據(jù)的結(jié)構(gòu)一致性。
check-table-metadata-enabled: false
# 在程序啟動(dòng)和更新時(shí),是否檢查重復(fù)表。false
check-duplicate-table-enabled: false
- mode:運(yùn)行模式,默認(rèn)就單機(jī)模式。
- dataSources:數(shù)據(jù)庫連接信息。
- rules:路由算法。defaultDatabaseStrategy 庫的路由、tables 表的路由。之后在 shardingAlgorithms 中配置具體的路由算法。這里的名稱都是關(guān)聯(lián)的,不要配置錯(cuò)。
- props:一些屬性信息,包括是否打印日志等。
與這個(gè)對(duì)比,如果你使用的路由功能并不那么大,其實(shí)自研會(huì)更加簡單。當(dāng)然你也可以想辦法,簡化 sharding-jdbc 的配置。
3. 配置引入
application-dev.yml
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:sharding/sharding-jdbc-dev.yaml
- 和之前的一些 sharding 版本不同,這里是需要使用具體的 ShardingSphereDriver 和 url 地址,才能加載上我們配置的路由信息。
三、測(cè)試驗(yàn)證
圖片
- 基于工程中 docs/dev-ops/mysql/sql 創(chuàng)建庫表。已經(jīng)提供了庫名、表和測(cè)試數(shù)據(jù)。
- 無論你使用哪種方式,都可以安裝MySql 并使用可視化工具鏈接。這里小傅哥用的是 Sequel Ace
1. 寫入數(shù)據(jù)
@Test
public void test_insert() {
for (int i = 0; i < 1000; i++) {
UserOrderPO userOrderPO = UserOrderPO.builder()
.userName("小傅哥")
.userId("xfg_" + RandomStringUtils.randomAlphabetic(6))
.userMobile("+86 13521408***")
.sku("13811216")
.skuName("《手寫MyBatis:漸進(jìn)式源碼實(shí)踐》")
.orderId(RandomStringUtils.randomNumeric(11))
.quantity(1)
.unitPrice(BigDecimal.valueOf(128))
.discountAmount(BigDecimal.valueOf(50))
.tax(BigDecimal.ZERO)
.totalAmount(BigDecimal.valueOf(78))
.orderDate(new Date())
.orderStatus(0)
.isDelete(0)
.uuid(UUID.randomUUID().toString().replace("-", ""))
.ipv4("127.0.0.1")
.ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334".getBytes())
.extData("{\"device\": {\"machine\": \"IPhone 14 Pro\", \"location\": \"shanghai\"}}")
.build();
userOrderDao.insert(userOrderPO);
}
}
- 測(cè)試數(shù)據(jù)寫入,你可以寫入1000條數(shù)據(jù),觀察散列效果。并可以在這個(gè)過程中,調(diào)試修改 sharding-jdbc-dev.yaml 文件對(duì)庫表路由的計(jì)算方式。
2. 查詢數(shù)據(jù)
@Test
public void test_selectByUserId() {
List<UserOrderPO> list = userOrderDao.selectByUserId("xfg_PrmgwQ");
log.info("測(cè)試結(jié)果:{}", JSON.toJSONString(list));
}
- 查詢的用戶ID是已經(jīng)寫入到數(shù)據(jù)庫表里的數(shù)據(jù),查詢的時(shí)候會(huì)根據(jù)用戶ID繼續(xù)路由計(jì)算。
3. 散列算法
@Test
public void test_idx() {
for (int i = 0; i < 50; i++) {
String user_id = "xfg_" + RandomStringUtils.randomAlphabetic(6);
log.info("測(cè)試結(jié)果 {}", (user_id.hashCode() ^ (user_id.hashCode()) >>> 16) & 3);
}
}
- 你可以嘗試驗(yàn)證和編寫新的散列算法,最終目的都是讓數(shù)據(jù)盡可能散列到庫表。
- 此外,關(guān)于算法的好壞,可以基于雪崩測(cè)試計(jì)算