日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

隨著系統(tǒng)數(shù)據(jù)量的日益增長,在說起數(shù)據(jù)庫架構(gòu)和數(shù)據(jù)庫優(yōu)化的時候,我們難免會常常聽到分庫分表這樣的名詞。

當然,分庫分表有很多的方法論,比如垂直拆分、水平拆分;也有很多的中間件產(chǎn)品,比如MyCat、ShardingJDBC。

根據(jù)業(yè)務場景選擇合適的拆分方法,再選擇一個熟悉的開源框架,就能幫助我們完成項目中所涉及到的數(shù)據(jù)拆分工作。

本文并不打算就這些方法論和開源框架展開深入的探討,筆者想討論另外一個場景:

如果系統(tǒng)中需要拆分的表并不多,只是1個或者少量的幾個,我們是否值得引入一些相對復雜的中間件產(chǎn)品;特別是,如果我們對它們的原理不甚了解,是否有信心駕馭它們 ?

基于此,如果你的系統(tǒng)中有少量的表需要拆分,也沒有專門的資源去研究開源組件,那么我們可以自己來實現(xiàn)一個簡單的分庫分表插件;當然,如果你的系統(tǒng)比較復雜,業(yè)務量較大,還是采用開源組件或者團隊自研組件來解決這事較為穩(wěn)妥。

分庫分表簡單?那我想問如何實現(xiàn)“分庫分表插件”?

 

一、原理

分庫分表這事說簡單也簡單,說復雜那也挺復雜...

簡單是因為它的核心流程比較明確。就是解析SQL語句,然后根據(jù)預先配置的規(guī)則,重寫或路由到真實的數(shù)據(jù)庫表中去;

復雜在于,SQL語句復雜且靈活,比如分頁、去重、排序、分組、聚合、關(guān)聯(lián)查詢等操作,如何正確的解析它們。

所以就算是ShardingJDBC,在官網(wǎng)中也明確了支持項和不支持項。

二、注解式配置

相對于復雜的配置文件,我們采用較為輕便的注解式配置,它的定義如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public%20@interface%20Sharding%20{
%20%20%20%20String%20tableName();%20%20%20%20%20//邏輯表名
%20%20%20%20String%20field();%20%20%20%20%20%20%20%20%20//分片鍵
%20%20%20%20String%20mode();%20%20%20%20%20%20%20%20%20%20//算法模式
%20%20%20%20int%20length()%20default%200;%20//分表數(shù)量
}

那么,在哪里使用它呢%20?%20比如我們的用戶表需要分表,那就在User這個實體對象上標注。

@Data
@Sharding(tableName%20=%20"user",field%20=%20"id",mode%20=%20"hash",length%20=%2016)
public%20class%20User%20{
%20%20%20%20private%20Long%20id;
%20%20%20%20private%20String%20name;
%20%20%20%20private%20String%20address;
%20%20%20%20private%20String%20tel;
%20%20%20%20private%20String%20email;
}

這就說明了,我一共有%2016%20張用戶表,根據(jù)用戶ID,使用Hash算法來計算它的位置。

當然,我們不止有Hash算法,還可以根據(jù)日期范圍來定義。

@Data
@Sharding(tableName%20=%20"car",field%20=%20"creatTime",mode%20=%20"range")
public%20class%20Car%20{
%20%20%20%20private%20long%20id;
%20%20%20%20private%20String%20number;
%20%20%20%20private%20String%20brand;
%20%20%20%20private%20String%20creatTime;
%20%20%20%20private%20long%20userId;
}

三、分片算法

在這里,筆者實現(xiàn)了兩種分片方式,就是HashAlgorithm和RangeAlgorithm%20。

1、范圍分片

如果你的系統(tǒng)中有使用冷熱數(shù)據(jù)分離,我們可以按照日期將不同月的數(shù)據(jù)分散到不同的表中。

比如車輛的創(chuàng)建時間是2019-12-10%2015:30:00,這條數(shù)據(jù)將會被分配到car_201912這張表中去。

我們通過截取時間的年月部分,然后再加上邏輯表名即可。

public%20class%20RangeAlgorithm%20implements%20Algorithm%20{
%20%20%20%20@Override
%20%20%20%20public%20String%20doSharding(String%20tableName,%20Object%20value,int%20length)%20{
%20%20%20%20%20%20%20%20if%20(value!=null){
%20%20%20%20%20%20%20%20%20%20%20%20try{
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20DateUtil.parseDateTime(value.toString());
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20String%20replace%20=%20value.toString().substring(0,%207).replace("-",%20"");
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20String%20newName%20=%20tableName+"_"+replace;
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20newName;
%20%20%20%20%20%20%20%20%20%20%20%20}catch%20(DateException%20ex){
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20logger.error("時間格式不符合要求!傳入?yún)?shù):{},正確格式:{}",value.toString(),"yyyy-MM-dd%20HH:mm:ss");
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName;
%20%20%20%20%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20return%20tableName;
%20%20%20%20}
}

2、Hash分片

在Hash分片算法中,我們可以先判斷表的數(shù)量,是不是2的冪次方。如果不是,就通過算數(shù)方式獲取下標,如果是呢,就通過位運算的方式獲取下標。當然了,這是在HashMap源碼中學到的哦。

public%20class%20HashAlgorithm%20implements%20Algorithm%20{
%20%20%20%20@Override
%20%20%20%20public%20String%20doSharding(String%20tableName,%20Object%20value,int%20length)%20{
%20%20%20%20%20%20%20%20if%20(this.isEmpty(value)){
%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName;
%20%20%20%20%20%20%20%20}else{
%20%20%20%20%20%20%20%20%20%20%20%20int%20h;
%20%20%20%20%20%20%20%20%20%20%20%20int%20hash%20=%20(h%20=%20value.hashCode())%20^%20(h%20>>>%2016);
%20%20%20%20%20%20%20%20%20%20%20%20int%20index;
%20%20%20%20%20%20%20%20%20%20%20%20if%20(is2Power(length)){
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%20=%20(length%20-%201)%20&%20hash;
%20%20%20%20%20%20%20%20%20%20%20%20}else%20{
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%20=%20Math.floorMod(hash,%20length);
%20%20%20%20%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName+"_"+index;
%20%20%20%20%20%20%20%20}
%20%20%20%20}
}

四、攔截器

配置和分片算法都有了,接下來就是重頭戲了。在這里,我們使用Mybatis攔截器將它們派上用場。

常年CRUD的我們,都知道一條業(yè)務SQL肯定逃不出它們的范圍。其中,在業(yè)務上我們的刪除功能一般都是邏輯刪除,所以,基本上不會有DELETE操作。

相較而言,新增和修改SQL都比較簡單且格式固定,查詢SQL往往比較靈活且復雜。所以,在這里筆者定義了兩個攔截器。

不過,在介紹攔截器之前,我們有理由要了解另外兩個東西:SQL語法解析器和分片算法處理器。

1、JSqlParser

JSqlParser負責解析SQL語句,并轉(zhuǎn)化為JAVA類的層次結(jié)構(gòu)。我們可以先看個簡單的例子來認識它。

public%20static%20void%20main(String[]%20args)%20throws%20JSQLParserException%20{

	String%20insertSql%20=%20"insert%20into%20user%20(id,name,age)%20value(1001,'范閑',20)";
	Statement%20parse%20=%20CCJSqlParserUtil.parse(insertSql);
	Insert%20insert%20=%20(Insert)%20parse;

	String%20tableName%20=%20insert.getTable().getName();
	List<Column>%20columns%20=%20insert.getColumns();
	ItemsList%20itemsList%20=%20insert.getItemsList();
	System.out.println("表名:"+tableName+"%20列名:"+columns+"%20屬性:"+itemsList);
}
輸出:%20表名:user%20列名:[id,%20name,%20age]%20屬性:(1001,%20'范閑',%2020)

我們可以看到,JSqlParser可以解析出SQL的語法信息。相應的,我們也可以更改對象內(nèi)容,從而達到修改SQL語句的目的。

2、算法處理器

我們的分片算法有多個,具體應該調(diào)用哪一個是在程序運行期來決定的。所以,我們使用一個Map先將算法注冊起來,然后根據(jù)分片模式來調(diào)用它。這也是策略模式的體現(xiàn)。

@Component
public%20class%20AlgorithmHandler%20{
%20%20%20%20private%20Map<String,%20Algorithm>%20algorithm%20=%20new%20HashMap<>();
%20%20%20%20@PostConstruct
%20%20%20%20public%20void%20init(){
%20%20%20%20%20%20%20%20algorithm.put("range",new%20RangeAlgorithm());
%20%20%20%20%20%20%20%20algorithm.put("hash",new%20HashAlgorithm());
%20%20%20%20}
%20%20%20%20public%20String%20handler(String%20mode,String%20name,Object%20value,int%20length){
%20%20%20%20%20%20%20%20return%20algorithm.get(mode).doSharding(name,%20value,length);
%20%20%20%20}
}

3、攔截器

我們知道,MyBatis允許你在已映射語句執(zhí)行過程中的某一點進行攔截調(diào)用。

如果你對它的原理還不熟悉,那么可以先看看筆者的文章:Mybatis攔截器的原理。

整體來看,它的流程如下:

  • 通過Mybatis攔截待執(zhí)行的SQL;
  • 通過JSqlParser解析SQL,獲取邏輯表名等;
  • 調(diào)用分片算法獲取真實表名;
  • 修改SQL,并修改BoundSql;
  • Mybatis執(zhí)行修改后的SQL,達成目的。

比如,對于insert語句和update語句,它的核心代碼如下:

 

五、查詢及分頁

事實上,新增和修改都比較簡單,較為復雜的是查詢語句。

但是,我們的插件并不在于要滿足所有的查詢語句,而是可以根據(jù)真實的業(yè)務場景來擴展修改。

不過分頁功能基本上是逃不開的。拿PageHelper為例,它的原理也是通過Mybatis攔截器來實現(xiàn)的。如果它和我們的分表插件在一起,可能會產(chǎn)生沖突。

所以在分表插件中,筆者也集成了分頁功能,基本上和PageHelper一樣,但并未直接使用它。另外,對于查詢來說,在查詢條件中是否帶有分片鍵,也是很關(guān)鍵的地方。

1、查詢

在范圍算法中,在業(yè)務上我們要求只查詢特定某一個月或者近幾個月的數(shù)據(jù)即可;在Hash算法中,我們則要求每次都帶有主鍵。

但第二個條件往往不能成立,業(yè)務方也滿足不了每次都必須帶有主鍵。

針對這種情況,我們只能遍歷所有的表,查詢符合條件的數(shù)據(jù),然后再匯總返回;

 

分庫分表簡單?那我想問如何實現(xiàn)“分庫分表插件”?

 

這種方式的缺點顯而易見,性能較差。還有一種方式就是可以將常用的查詢條件與分片鍵建立映射關(guān)系,在查詢時先根據(jù)查詢條件找到分片鍵的字段值,然后再根據(jù)分片鍵查詢。

2、分頁

如上所言,插件中集成了分頁功能,實現(xiàn)流程與PageHelper一樣,但考慮到?jīng)_突,并未直接使用。

 

分庫分表簡單?那我想問如何實現(xiàn)“分庫分表插件”?

 


作者:清幽之地
原文鏈接:https://juejin.im/post/5dfc6cc0518825126f3735d7

分享到:
標簽:分庫分表
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定