分散式資料層中介軟體詳解:如何實現分庫分表+動態資料來源+讀寫分離
優知學院 2018-10-13 12:10:20
分散式資料層中介軟體:
1.簡介:
分散式資料訪問層中介軟體,旨在為供一個通用資料訪問層服務,支援MySQL動態資料來源、讀寫分離、分散式唯一主鍵生成器、分庫分表、動態化配置等功能,並且支援從客戶端角度對資料來源的各方面(比如連線池、SQL等)進行監控,後續考慮支援NoSQL、Cache等多種資料來源。
2.功能特性
- 動態資料來源
- 讀寫分離
- 分散式唯一主鍵生成器
- 分庫分表
- 連線池及SQL監控
- 動態化配置
常見的資料層中介軟體:
1. TDDL
淘寶根據自己的業務特點開發了TDDL框架,主要解決了分庫分表對應用的透明化以及異構資料庫之間的資料複製,它是一個基於集中式配置的JDBC datasource實現。
特點
實現動態資料來源、讀寫分離、分庫分表。
缺點
分庫分表功能還未開源,當前公佈文件較少,並且需要依賴diamond(淘寶內部使用的一個管理持久配置的系統)
2. Atlas
Qihoo 360開發維護的一個基於MySQL協議的資料中間層專案。它實現了MySQL的客戶端與服務端協議,作為服務端與應用程式通訊,同時作為客戶端與MySQL通訊
特點:
實現讀寫分離、單庫分表
缺點:
不支援分庫分表
3.MTDDL(Meituan Distributed Data Layer)
美團點評分散式資料訪問層中介軟體
特點
實現動態資料來源、讀寫分離、分庫分表,與tddl類似。
下面我以MTDDL為例,也可以參考淘寶tddl,完整詳解分散式資料層中介軟體的架構設計。
分散式資料層中介軟體架構設計
下圖是一次完整的DAO層insert方法呼叫時序圖,簡單闡述了MTDDL的整個邏輯架構。
其中包含了:
1.分散式唯一主鍵的獲取
2.動態資料來源的路由
3.以及SQL埋點監控等過程:
分散式資料層中介軟體:具體實現
1.動態資料來源及讀寫分離
在Spring JDBC AbstractRoutingDataSource的基礎上擴展出MultipleDataSource動態資料來源類,通過動態資料來源註解及AOP實現。
2.動態資料來源
MultipleDataSource動態資料來源類,繼承於Spring JDBC AbstractRoutingDataSource抽象類,實現了determineCurrentLookupKey方法,通過setDataSourceKey方法來動態調整dataSourceKey,進而達到動態調整資料來源的功能。其類圖如下:
3.動態資料來源AOP
ShardMultipleDataSourceAspect動態資料來源切面類,針對DAO方法進行功能增強,通過掃描DataSource動態資料來源註解來獲取相應的dataSourceKey,從而指定具體的資料來源。具體流程圖如下:
4.配置和使用方式舉例
/** * 參考配置 */ <bean id="multipleDataSource" class="com.sankuai.meituan.waimai.datasource.multi.MultipleDataSource"> /** 資料來源配置 */ <property name="targetDataSources"> <map key-type="java.lang.String"> /** 寫資料來源 */ <entry key="dbProductWrite" value-ref="dbProductWrite"/> /** 讀資料來源 */ <entry key="dbProductRead" value-ref="dbProductRead"/> </map> </property> </bean> /** * DAO使用動態資料來源註解 */ public interface WmProductSkuDao { /** 增刪改走寫資料來源 */ @DataSource("dbProductWrite") public void insert(WmProductSku sku); /** 查詢走讀資料來源 */ @DataSource("dbProductRead") public void getById(long sku_id); }
5.分散式唯一主鍵生成器
眾所周知,分庫分表首先要解決的就是分散式唯一主鍵的問題,業界也有很多相關方案:
序號實現方案優點缺點UUID本地生成,不需要RPC,低延時;
擴充套件性好,基本沒有效能上限無法保證趨勢遞增;
UUID過長128位,不易儲存,往往用字串表示2Snowflake或MongoDB ObjectId分散式生成,無單點;
趨勢遞增,生成效率快沒有全域性時鐘的情況下,只能保證趨勢遞增;
當通過NTP進行時鐘同步時可能會出現重複ID;
資料間隙較大3proxy服務+資料庫分段獲取ID分散式生成,段用完後需要去DB獲取,同server有序可能產生資料空洞,即有些ID沒有分配就被跳過了,主要原因是在服務重啟的時候發生;
無法保證有序,需要未來解決,可能會通過其他介面方案實現
綜上,方案3的缺點可以通過一些手段避免,但其他方案的缺點不好處理,所以選擇第3種方案:分散式ID生成系統Leaf。
6.分散式ID生成系統Leaf
分散式ID生成系統Leaf,其實是一種基於DB的Ticket服務,通過一張通用的Ticket表來實現分散式ID的持久化,執行update更新語句來獲取一批Ticket,這些獲取到的Ticket會在記憶體中進行分配,分配完之後再從DB獲取下一批Ticket。
整體架構圖如下:
每個業務tag對應一條DB記錄,DB MaxID欄位記錄當前該Tag已分配出去的最大ID值。
IDGenerator服務啟動之初向DB申請一個號段,傳入號段長度如 genStep = 10000,DB事務置 MaxID = MaxID + genStep,DB設定成功代表號段分配成功。每次IDGenerator號段分配都通過原子加的方式,待分配完畢後重新申請新號段。
7.唯一主鍵生成演算法擴充套件
MTDDL不僅集成了Leaf演算法,還支援唯一主鍵演算法的擴充套件,通過新增唯一主鍵生成策略類實現IDGenStrategy介面即可。IDGenStrategy介面包含兩個方法:getIDGenType用來指定唯一主鍵生成策略,getId用來實現具體的唯一主鍵生成演算法。其類圖如下:
8.分庫分表
在動態資料來源AOP的基礎上擴展出分庫分表AOP,通過分庫分表ShardHandle類實現分庫分表資料來源路由及分表計算。ShardHandle關聯了分庫分表上下文ShardContext類,而ShardContext封裝了所有的分庫分表演算法。其類圖如下:
分庫分表流程圖如下:
9.分庫分表取模演算法
分庫分表目前預設使用的是取模演算法,分表演算法為 (#shard_key % (group_shard_num * table_shard_num)),分庫演算法為 (#shard_key % (group_shard_num * table_shard_num)) / table_shard_num,其中group_shard_num為分庫個數,table_shard_num為每個庫的分表個數。
例如把一張大表分成100張小表然後散到2個庫,則0-49落在第一個庫、50-99落在第二個庫。核心實現如下:
public class ModStrategyHandle implements ShardStrategy { @Override public String getShardType() { return "mod"; } @Override public DataTableName handle(String tableName, String dataSourceKey, int tableShardNum, int dbShardNum, Object shardValue) { /** 計算散到表的值 */ long shard_value = Long.valueOf(shardValue.toString()); long tablePosition = shard_value % tableShardNum; long dbPosition = tablePosition / (tableShardNum / dbShardNum); String finalTableName = new StringBuilder().append(tableName).append("_").append(tablePosition).toString(); String finalDataSourceKey = new StringBuilder().append(dataSourceKey).append(dbPosition).toString(); return new DataTableName(finalTableName, finalDataSourceKey); } }
10.分庫分表演算法擴充套件
MTDDL不僅支援分庫分表取模演算法,還支援分庫分表演算法的擴充套件,通過新增分庫分表策略類實現ShardStrategy介面即可。ShardStrategy介面包含兩個方法:getShardType用來指定分庫分表策略,handle用來實現具體的資料來源及分表計算邏輯。其類圖如下:
11.全註解方式接入
為了儘可能地方便業務方接入,MTDDL採用全註解方式使用分庫分表功能,通過ShardInfo、ShardOn、IDGen三個註解實現。
ShardInfo註解用來指定具體的分庫分表配置:包括分表名字首tableName、分表數量tableShardNum、分庫數量dbShardNum、分庫分表策略shardType、唯一鍵生成策略idGenType、唯一鍵業務方標識idGenKey;ShardOn註解用來指定分庫分表字段;IDGen註解用來指定唯一鍵欄位。具體類圖如下:
12.配置和使用方式舉例
// 動態資料來源 @DataSource("dbProductSku") // tableName:分表名字首,tableShardNum:分表數量,dbShardNum:分庫數量,shardType:分庫分表策略,idGenType:唯一鍵生成策略,idGenKey:唯一鍵業務方標識 @ShardInfo(tableName="wm_food", tableShardNum=100, dbShardNum=1, shardType="mod", idGenType=IDGenType.LEAF, idGenKey=LeafKey.SKU) @Component public interface WmProductSkuShardDao { // @ShardOn("wm_poi_id") 將該註解修飾的物件的wm_poi_id欄位作為shardValue // @IDGen("id") 指定要設定唯一鍵的欄位 public void insert(@ShardOn("wm_poi_id") @IDGen("id") WmProductSku sku); // @ShardOn 將該註解修飾的引數作為shardValue public List<WmProductSku> getSkusByWmPoiId(@ShardOn long wm_poi_id); }
連線池及SQL監控
DB連線池使用不合理容易引發很多問題,如連線池最大連線數設定過小導致執行緒獲取不到連線、獲取連線等待時間設定過大導致很多執行緒掛起、空閒連接回收器執行週期過長導致空閒連接回收不及時等等,如果缺乏有效準確的監控,會造成無法快速定位問題以及追溯歷史。
連線池監控
實現方案
結合Spring完美適配c3p0、dbcp1、dbcp2、mtthrift等多種方案,自動發現新加入到Spring容器中的資料來源進行監控,通過美團點評統一監控元件JMonitor上報監控資料。整體架構圖如下:
連線數量監控
監控連線池active、idle、total連線數量,Counter格式:(連線池型別.資料來源.active/idle/total_connection),效果圖如下:
獲取連線時間監控
監控獲取空閒連線時間,Counter格式:(ds.getConnection.資料來源.time),效果圖如下:
更多架構師進階:資料獲取方式
關注+轉發後,私信關鍵詞 【架構】即可獲取!
重要的事情說三遍,轉發、轉發、轉發後再發私信,才可以拿到哦!