做者簡介 鍾悅,就任於某大型國有銀行,多年從事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. 內置路由函數的縮寫與類名對照表分佈式
路由函數的加載發生在DBLE啓動或重載時。ide
DBLE讀取rule.xml時,根據用戶配置的標籤的class屬性函數
DBLE經過Java的反射機制,從$DBLE_HOME/lib的jar包中,找到對應的jar(裏的class文件),加載同名的類並建立對象this
DBLE會逐個掃描中的標籤,並根據name屬性來調用路由函數的對應setter,以此完成賦值過程——例如,若是用戶配置了2,那麼DBLE就會嘗試找路由函數中叫作setPartitionCount()的方法,並將字符串「2」傳給它
DBLE調用路由函數的selfCheck()方法,執行函數編寫者制定的檢查動做,例如檢查賦值獲得的變量值是否有問題
DBLE調用路由函數的init()方法,執行函數編寫者制定的準備動做,例如建立後面要用到的一些中間變量
路由函數接受用戶SQL中的分片字段的值,計算出這個值對應的數據記錄應該在哪一個編號的數據分片(邏輯分片)上,DBLE從而知道把這個SQL準確發到這些分片上。
用戶經過管理端口(默認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)
開發時,理論上只須要引入AbstractPartitionAlgorithm抽象類和RuleAlgorithm接口及它們的依賴類就能夠了。但實際上AbstractPartitionAlgorithm抽象類依賴了TableConfig類,由此開啓了環遊世界的依賴之旅。所以,現實的操做仍是引用整個DBLE項目的源代碼會比較直接方便。
開發一個新的路由函數時,必須給這個路由函數的開發新建項目,而後再引用DBLE項目(項目引用項目的方式)。而不該該直接打開DBLE的項目,而後在DBLE的項目裏面直接新建源代碼來直接開發(內嵌開發方式)。經過遵循這個作法,會有如下好處:
路由函數能夠獨立打包,直接去看路由函數的jar包版本就可以確認函數版本;而把路由函數嵌到DBLE裏的話,就很容易出現DBLE版本同樣,但不清楚裏面的函數是什麼版本的窘況
路由函數的遞進能夠更加自由,若是DBLE的AbstractPartitionAlgorithm抽象類和RuleAlgorithm接口沒有變更,同一版本的路由函數能夠延續使用好幾個版本的DBLE,而不須要每次DBLE釋放新版就得去重編譯
可讓路由函數中的受保護代碼免受DBLE自身的開源協議影響
完成開發以後,成品打包成jar包進行發佈,而不要直接發佈class和依賴的library(其餘項目的jar包或class文件)。
讓DBLE使用上新的路由函數的過程:
將成品jar包放入$DBLE_HOME/lib目錄中
調整jar包的全部者權限(chown)和文件權限(chmod),使之與其餘$DBLE_HOME/lib目錄裏的jar包同樣
| 請注意:2.18.12.0及之後版本建議放在algorithm目錄下,用於區分原生lib和自定義lib,便於管理
按照原來的思路配置rule.xml,但須要注意標籤的class屬性必需要填寫新的路由函數類的徹底限定名(Fully Qualified Name),例如net.john.DBLE.route.functions.NewFunction
配置邏輯表之類的必要信息,重啓DBLE後,自動生效。
每一個路由函數本質上就是一個繼承了AbstractPartitionAlgorithm抽象類,而且實現了RuleAlgorithm接口的一個類。下面之內置的com.actiontech.DBLE.route.function.PartitionByLong爲例,介紹實現一個路由函數類所須要作的最小工做(必要工做)。
在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); }
在函數加載過程當中,完成了配置項賦值以後,DBLE會調用這個路由函數對象的selfCheck()方法,讓這個對象自我檢查剛纔讀進來的配置項的值,放在一塊兒是否是有問題。若是有問題的話,路由函數編寫者在這時候,能夠經過拋出RuntimeException來進行報錯,並終止DBLE使用這個函數,固然,因爲RuntimeException的霸道,DBLE本身也會所以而報錯退出。
因爲selfCheck()是RuleAlgorithm接口的要求,並且AbstractPartitionAlgorithm抽象類沒又實現它,對於想偷懶或者沒有必要進行這個檢查的人來講,仍是須要自行定義一個空的同名方法來實現它。
@Override public void selfCheck() { }
在函數加載過程的最後,DBLE調用這個路由函數對象的init()方法,讓這個對象完成一些內部的初始化工做。
在咱們的例子PartitionByLong裏,經過init()方法準備了PartitionUtil對象,其中有一個哈希值的範圍與邏輯分片號對應的數組,這樣在後面的路由計算時就能經過查數組來加速獲得結果。
DBLE執行用戶SQL時,根據用戶SQL的不一樣,調用calculate()或calculateRange()來肯定用戶的SQL應該發到哪一個數據分片上去。
從IPO(Input-Process-Output)來分析,calculate()和calculateRange()的工做原理是同樣的:
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; } }
當用戶找DBLE要路由函數的配置信息時,DBLE經過訪問路由函數的getAllProperties()來得到一個<配置項, 配置值>的哈希表,而後將裏面的內容逐項返回給用戶。
getAllProperties()是RuleAlgorithm接口所規定要實現的,但爲了簡化編寫新的路由函數的工做,在AbstractPartitionAlgorithm抽象類裏,定義了propertiesMap這個私有變量,而且把「將propertiesMap交出去」做爲了實現了getAllProperties()方法的默認實現。
通常來講,這個默認的實現能知足需求,而新路由函數編寫者只須要在配置項setters處理用戶配置時,將<配置項, 配置值>給put()進propertiesMap裏就行了。
@Override public Map<String, String> getAllProperties() { return propertiesMap; }
DBLE內置的路由函數都位於com.actiontech.DBLE.route.function命名空間。但實際配置rule.xml的時候,卻不用寫那麼長的徹底限定名,這其實都是XMLRuleLoader類作了轉換,所以實現了簡寫。下面就是7個內置函數的類名和它們的簡寫。
簡寫名 | 完整類名 |
---|---|
date | PartitionByDate |
enum | PartitionByFileMap |
hash | PartitionByLong |
jumpstringhash | PartitionByJumpConsistentHash |
numberrange | AutoPartitionByLong |
patternrange | PartitionByPattern |
stringhash | PartitionByString |