社區投稿 | DBLE 自定義拆分算法

做者簡介 鍾悅,就任於某大型國有銀行,多年從事MySQL和分佈式中間件的方案設計與實施工做;資深MySQL數據庫專家,架構師;DBLE開源項目積極貢獻者。mysql

文章概要 DBLE默認支持數十種數據拆分算法,基本能知足大部分的社區用戶的使用需求;爲了知足更廣的業務場景,DBLE還支持更加靈活的自定義拆分算法;本文對面向有相似需求的DBLE開發者,提供了一個如何開發和部署自定義的拆分規則的一個指引算法

目錄sql

1. 工做原理數據庫

  • 1.1 函數的加載
  • 1.2 路由計算
  • 1.3 參數查詢

2. 開發和部署數組

  • 2.1 開發
  • 2.2 部署

3. 接口規範架構

  • 3.1 配置項setters
  • 3.2 selfCheck()
  • 3.3 init()
  • 3.4 calculate()和calculateRange()
  • 3.5 getAllProperties()

4. 內置路由函數的縮寫與類名對照表分佈式

1. 工做原理

1.1 函數的加載

路由函數的加載發生在DBLE啓動或重載時。ide

11.jpg

  • DBLE讀取rule.xml時,根據用戶配置的標籤的class屬性函數

  • DBLE經過Java的反射機制,從$DBLE_HOME/lib的jar包中,找到對應的jar(裏的class文件),加載同名的類並建立對象this

  • DBLE會逐個掃描中的標籤,並根據name屬性來調用路由函數的對應setter,以此完成賦值過程——例如,若是用戶配置了2,那麼DBLE就會嘗試找路由函數中叫作setPartitionCount()的方法,並將字符串「2」傳給它

  • DBLE調用路由函數的selfCheck()方法,執行函數編寫者制定的檢查動做,例如檢查賦值獲得的變量值是否有問題

  • DBLE調用路由函數的init()方法,執行函數編寫者制定的準備動做,例如建立後面要用到的一些中間變量

1.2 路由計算

路由函數接受用戶SQL中的分片字段的值,計算出這個值對應的數據記錄應該在哪一個編號的數據分片(邏輯分片)上,DBLE從而知道把這個SQL準確發到這些分片上。

image

1.3 參數查詢

用戶經過管理端口(默認9066),經過SHOW @@ALGORITHM WHERE SCHEMA=? AND TABLE=?來查詢表上的路由算法時,DBLE調用路由算法的getAllProperties()方法,直接從內存中獲取路由信息的配置。

mysql> show @@algorithm where schema=testdb and table=seqtest;
+-----------------+----------------------------------------------------+
| KEY             | VALUE                                              |
+-----------------+----------------------------------------------------+
| TYPE            | SHARDING TABLE                                     |
| COLUMN          | ID                                                 |
| CLASS           | com.actiontech.dble.route.function.PartitionByLong |
| partitionCount  | 2                                                  |
| partitionLength | 1                                                  |
+-----------------+----------------------------------------------------+
5 rows in set (0.05 sec)

2. 開發和部署

2.1 開發

開發時,理論上只須要引入AbstractPartitionAlgorithm抽象類和RuleAlgorithm接口及它們的依賴類就能夠了。但實際上AbstractPartitionAlgorithm抽象類依賴了TableConfig類,由此開啓了環遊世界的依賴之旅。所以,現實的操做仍是引用整個DBLE項目的源代碼會比較直接方便。

開發一個新的路由函數時,必須給這個路由函數的開發新建項目,而後再引用DBLE項目(項目引用項目的方式)。而不該該直接打開DBLE的項目,而後在DBLE的項目裏面直接新建源代碼來直接開發(內嵌開發方式)。經過遵循這個作法,會有如下好處:

  1. 路由函數能夠獨立打包,直接去看路由函數的jar包版本就可以確認函數版本;而把路由函數嵌到DBLE裏的話,就很容易出現DBLE版本同樣,但不清楚裏面的函數是什麼版本的窘況

  2. 路由函數的遞進能夠更加自由,若是DBLE的AbstractPartitionAlgorithm抽象類和RuleAlgorithm接口沒有變更,同一版本的路由函數能夠延續使用好幾個版本的DBLE,而不須要每次DBLE釋放新版就得去重編譯

  3. 可讓路由函數中的受保護代碼免受DBLE自身的開源協議影響

2.2 部署

完成開發以後,成品打包成jar包進行發佈,而不要直接發佈class和依賴的library(其餘項目的jar包或class文件)。

讓DBLE使用上新的路由函數的過程:

  1. 將成品jar包放入$DBLE_HOME/lib目錄中

  2. 調整jar包的全部者權限(chown)和文件權限(chmod),使之與其餘$DBLE_HOME/lib目錄裏的jar包同樣

    | 請注意:2.18.12.0及之後版本建議放在algorithm目錄下,用於區分原生lib和自定義lib,便於管理

  3. 按照原來的思路配置rule.xml,但須要注意標籤的class屬性必需要填寫新的路由函數類的徹底限定名(Fully Qualified Name),例如net.john.DBLE.route.functions.NewFunction

  4. 配置邏輯表之類的必要信息,重啓DBLE後,自動生效。

3. 接口規範

每一個路由函數本質上就是一個繼承了AbstractPartitionAlgorithm抽象類,而且實現了RuleAlgorithm接口的一個類。下面之內置的com.actiontech.DBLE.route.function.PartitionByLong爲例,介紹實現一個路由函數類所須要作的最小工做(必要工做)。

image

3.1 配置項setters

在rule.xml中,咱們須要配置partitionCount和partitionLength兩個配置項。

<function name="hashmod" class="com">
  <property name="partitionCount">4</property>
  <property name="partitionLength">1</property>
</function>

爲了讓DBLE在函數加載過程當中,可以認出這裏的partitionCount(值爲4)和partitionLength(值爲1),所以PartitionByLong類中,就必須有屬性設置方法(setter)setPartitionCount()和setPartitionLength()。而由於rule.xml是個文本型的XML文件,因此這些函數的傳入參數就只能是一個String,數據類型轉換和預處理的動做就由這些setter來處理了。

public void setPartitionCount(String partitionCount) {
  this.count = toIntArray(partitionCount);
    /* 參考本文的getAllProperties()的說明 */
  propertiesMap.put("partitionCount", partitionCount);
}

public void setPartitionLength(String partitionLength) {
  this.length = toIntArray(partitionLength);
    /* 參考本文的getAllProperties()的說明 */
  propertiesMap.put("partitionLength", partitionLength);
}

3.2 selfCheck()

在函數加載過程當中,完成了配置項賦值以後,DBLE會調用這個路由函數對象的selfCheck()方法,讓這個對象自我檢查剛纔讀進來的配置項的值,放在一塊兒是否是有問題。若是有問題的話,路由函數編寫者在這時候,能夠經過拋出RuntimeException來進行報錯,並終止DBLE使用這個函數,固然,因爲RuntimeException的霸道,DBLE本身也會所以而報錯退出。

因爲selfCheck()是RuleAlgorithm接口的要求,並且AbstractPartitionAlgorithm抽象類沒又實現它,對於想偷懶或者沒有必要進行這個檢查的人來講,仍是須要自行定義一個空的同名方法來實現它。

@Override
public void selfCheck() {
}

3.3 init()

在函數加載過程的最後,DBLE調用這個路由函數對象的init()方法,讓這個對象完成一些內部的初始化工做。

在咱們的例子PartitionByLong裏,經過init()方法準備了PartitionUtil對象,其中有一個哈希值的範圍與邏輯分片號對應的數組,這樣在後面的路由計算時就能經過查數組來加速獲得結果。

3.4 calculate()和calculateRange()

DBLE執行用戶SQL時,根據用戶SQL的不一樣,調用calculate()或calculateRange()來肯定用戶的SQL應該發到哪一個數據分片上去。

從IPO(Input-Process-Output)來分析,calculate()和calculateRange()的工做原理是同樣的:

  • Input:用戶SQL中的分片字段值
  • Output:用戶SQL應該要發往的數據分片的編號
  • Process:Input與Output轉換的計算過程,由函數開發者編寫

calculate()和calculateRange()的使用場景不一樣,致使它們存在着一些微小的差別。

函數名 調用場景 Input Output
calculate() 用戶SQL裏分片字段的值是單值的狀況,例如 ... WHERE sharding_key = 1 1個String 1個Integer
calculateRange() 用戶SQL裏分片字段的值是連續範圍,例如 ... WHERE sharding_key BETWEEN 1 AND 5 2個String Integer數組
@Override
public Integer calculate(String columnValue) {
  try {
    if (columnValue == null || columnValue.equalsIgnoreCase("NULL")) {
      return 0;
    }
    long key = Long.parseLong(columnValue);
    return calculate(key);
  } catch (NumberFormatException e) {
    throw new IllegalArgumentException("columnValue:" + columnValue + " Please eliminate any quote and non number within it.", e);
  }
}

@Override
public Integer[] calculateRange(String beginValue, String endValue) {
  long begin = 0;
  long end = 0;
  try {
    begin = Long.parseLong(beginValue);
    end = Long.parseLong(endValue);
  } catch (NumberFormatException e) {
    return new Integer[0];
  }
  int partitionLength = partitionUtil.getPartitionLength();
  if (end - begin >= partitionLength || begin > end) { //TODO: optimize begin > end
    return new Integer[0];
  }
  Integer beginNode = calculate(begin);
  Integer endNode = calculate(end);

  if (endNode > beginNode || (endNode.equals(beginNode) && partitionUtil.isSingleNode(begin, end))) {
    int len = endNode - beginNode + 1;
    Integer[] re = new Integer[len];

    for (int i = 0; i < len; i++) {
      re[i] = beginNode + i;
    }
    return re;
  } else {
    int split = partitionUtil.getSegmentLength() - beginNode;
    int len = split + endNode + 1;
    if (endNode.equals(beginNode)) {
      //remove duplicate
      len--;
    }
    Integer[] re = new Integer[len];
    for (int i = 0; i < split; i++) {
      re[i] = beginNode + i;
    }
    for (int i = split; i < len; i++) {
      re[i] = i - split;
    }
    return re;
  }
}

3.5 getAllProperties()

當用戶找DBLE要路由函數的配置信息時,DBLE經過訪問路由函數的getAllProperties()來得到一個<配置項, 配置值>的哈希表,而後將裏面的內容逐項返回給用戶。

getAllProperties()是RuleAlgorithm接口所規定要實現的,但爲了簡化編寫新的路由函數的工做,在AbstractPartitionAlgorithm抽象類裏,定義了propertiesMap這個私有變量,而且把「將propertiesMap交出去」做爲了實現了getAllProperties()方法的默認實現。

通常來講,這個默認的實現能知足需求,而新路由函數編寫者只須要在配置項setters處理用戶配置時,將<配置項, 配置值>給put()進propertiesMap裏就行了。

@Override
public Map<String, String> getAllProperties() {
  return propertiesMap;
}

4. 內置路由函數的縮寫與類名對照表

DBLE內置的路由函數都位於com.actiontech.DBLE.route.function命名空間。但實際配置rule.xml的時候,卻不用寫那麼長的徹底限定名,這其實都是XMLRuleLoader類作了轉換,所以實現了簡寫。下面就是7個內置函數的類名和它們的簡寫。

簡寫名 完整類名
date PartitionByDate
enum PartitionByFileMap
hash PartitionByLong
jumpstringhash PartitionByJumpConsistentHash
numberrange AutoPartitionByLong
patternrange PartitionByPattern
stringhash PartitionByString

相關文章
相關標籤/搜索