什麼?你們的測試是小哥哥?那就不要往下看了,讓他們怎麼難怎麼來。
建議
根據你的業務特點,單表 > 分區 > 單庫分表 > 分庫分表,在滿足業務前提下,優先級從左到右,不接受任何反駁。嘿嘿
背景
做過分表的(單庫分表或者分庫分表)都知道,在你沒有依賴任何中間件之前,使用Navicat或者其他類似工具操作MySQL,那将是災難,如下圖所示:
sharding table view
如果是類似取模這類簡單算法分表還好說,能一眼根據分片鍵知道分表結,比如根據使用者ID對128取模分表,那麼使用者ID為128的使用者資料就在表tb_user_0中。但是如果是采用類似一緻性hash算法或者更複雜的分表算法,那麼我們首先需要利用程式根據分片鍵算出分表結果,然後再到Navicat中對該表進行CRUD。
剛才提到的隻能搞定帶有分片鍵的操作,對于那些沒有帶有分片鍵條件的操作,例如查詢最新的10條資料,那真的隻能呵呵了。開發尚且如此困難,測試小姐姐能不哭嘛。馬雲爸爸說:哪裡有困難,哪裡就有機會。是以,這就是你和測試小姐姐拉近機會、表現自己的時候。當然不是要你送下面這輛奔馳大G給小姐姐,畢竟不是每個人都是王思聰。不過利用我們所學,給測試小姐姐解決一些問題,還是可以的嘛(這種機會都不抓住,活該你單身):
本文要介紹的,可以優雅解決這個問題的工具就是 sharding-proxy(MyCAT也有類似工具,隻是易用性簡直就是渣渣)。話不多說,我們先來看看部署sharding-proxy前後使用Navicat通路MySQL的對比效果圖,開不開森,激不激動:
image.png
是以,sharding-proxy能帶來什麼呢?先說優點吧:
- 完全屏蔽sharding細節,把它當做一張普通的表增删改查即可(分表算法內建在sharding-proxy的配置檔案中,使用者不需要care)
- 打開資料庫後,不再是看到滿屏的表,而是隻有幾張簡單的表(如上圖左側所示)。
- 一個sharding-proxy可以代理多個資料源,測試隻需要對接一個sharding-proxy即可。
再說缺點:
- 無法操作"設計表",即不能看到DDL。但是可以通過F6快捷鍵後show create table table_name檢視表結果(以Navicat為例);
- sharding-sphere不支援的文法(非常有限),也不能在Navicat上操作。
是不是感覺很厲害的樣子,OK,讓我們花10分鐘給測試小姐姐一個驚喜吧(部署sharding-proxy)。
sharding-proxy
部署sharding-proxy非常簡單,隻需如下幾個簡單的步驟。另外,本次部署以sharding-proxy-3.0.0版本為例。
下載下傳
下載下傳位址:https://github.com/sharding-sphere/sharding-sphere-doc/raw/master/dist/sharding-proxy-3.0.0.tar.gz
下載下傳後解壓,我們看到隻有簡單的三個目錄:lib目錄就是sharding-proxy核心代碼,以及依賴的JAR包;bin目錄就是存放啟停腳本的;conf目錄就是存放所有配置檔案,包括sharding-proxy服務的配置檔案、資料源以及sharding規則配置檔案和項目日志配置檔案。
lia目錄沒什麼好說的。bin目錄裡面就是window或者Linux環境啟停腳本。sharding-proxy啟動的預設端口是3307,如果要自定義端口(比如3308),執行sh start.sh 3308即可(window環境修改start.bat即可)。所有重要的配置都在conf目錄下。
配置
- logback.xml
首先就是最簡單的日志配置檔案logback.xml,筆者對其簡單的修改了一下,你可以任意自定義,這個沒什麼好說的,非常簡單。
- server.xml
接下來就是與sharding-proxy服務相關的配置檔案server.yaml,筆者的配置檔案如下所示:
orchestration:
name: orchestration_afei
overwrite: true
registry:
serverLists: 172.29.3.245:2181,172.29.3.245:2182,172.29.3.245:2183
namespace: orchestration_afei
# 使用者通過Navicat通路sharding-proxy的使用者名密碼
authentication:
username: afei
password: afei
# sharding-proxy相關配置,建議sql.show設定為true,友善定位問題
props:
max.connections.size.per.query: 1
acceptor.size: 16
executor.size: 16
proxy.transaction.enabled: false
proxy.opentracing.enabled: false
sql.show: true
- config.xml
接下來就是最重要的資料源以及sharding規則配置檔案config-*.xml了。需要說明的是,一個sharding-proxy執行個體能支援多個資料源,隻需多個config.yaml即可。例如支付和賬戶兩個資料源,那麼添加兩個配置檔案即可:config-pay.yaml,config-account.yaml,配置參考:
schemaName: afei
dataSources:
afei:
url: jdbc:mysql://172.29.2.239:3311/afei?serverTimezone=UTC&useSSL=false
username: afei
password: afei
autoCommit: true
connectionTimeout: 10000
idleTimeout: 60000
maxLifetime: 1800000
maximumPoolSize: 50
shardingRule:
tables:
# tb_batch_trade_detail的分表算法是根據trade_id對128取模
tb_batch_trade_detail:
actualDataNodes: afei.tb_batch_trade_detail${0..127}
tableStrategy:
inline:
shardingColumn: trade_id
algorithmExpression: tb_batch_trade_detail${trade_id % 128}
# tb_repay_file_detail的分表算法是在RepayFileDetailShardingAlgorithm中自定義
tb_repay_file_detail:
actualDataNodes: afei.tb_repay_file_detail${0..127}
tableStrategy:
standard:
shardingColumn: project_no
preciseAlgorithmClassName: com.afei.sharding.RepayFileDetailShardingAlgorithm
bindingTables:
# 預設資料庫沒有分的政策
defaultDatabaseStrategy:
none:
# 預設表沒有分的政策
defaultTableStrategy:
none:
defaultKeyGeneratorClassName: io.shardingsphere.core.keygen.DefaultKeyGenerator
對應自定義分表算法,隻需要将對應算法的class檔案放到conf目錄下即可,參考目錄結構:
com
├── afei/sharding/RepayFileDetailShardingAlgorithm.class
config-pay.yaml
config-account.yaml
logback.xml
server.yaml
這個自定義分表算法的源碼非常簡單,參考sharding-sphere中的分表,實作自己的算法即可,參考源碼:
import io.shardingsphere.api.algorithm.sharding.PreciseShardingValue;
import io.shardingsphere.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
import java.util.Collection;
public class RepayFileDetailShardingAlgorithm implements PreciseShardingAlgorithm<String> {
private static final int SHARDING_COUNT = 128;
@Override
public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<String> shardingValue) {
String hashCode = String.valueOf(shardingValue.getValue().hashCode());
long segment = Math.abs(Long.parseLong(hashCode)) % SHARDING_COUNT;
for (String each : availableTargetNames) {
if (each.equals( "tb_repay_file_detail"+segment )) {
return each;
}
}
throw new UnsupportedOperationException();
}
}
- Navicat