分布式數據層中間件:實現分庫分表+動態數據源+讀寫分離


分布式數據層中間件

1.簡介

分布式數據訪問層中間件,旨在為供一個通用數據訪問層服務,支持MySQL動態數據源、讀寫分離、分布式唯一主鍵生成器、分庫分表、動態化配置等功能,並且支持從客戶端角度對數據源的各方面(比如連接池、SQL等)進行監控,后續考慮支持NoSQL、Cache等多種數據源。

2.作用

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

  • 動態數據源
  • 讀寫分離
  • 分布式唯一主鍵生成器
  • 分庫分表
  • 連接池及SQL監控
  • 動態化配置等

 

2.如何實現分庫分表

既然知道了分庫分表的原因,那么業內都是如何實現分庫分庫的呢?

分庫分表的技術方案總體上來講分為兩大類:應用層依賴類中間件、中間層代理類中間件。

  • 應用層依賴類中間件

這類分庫分表中間件的特點就是和應用強耦合,需要應用顯示依賴相應的jar包(以Java為例),比如知名的TDDL、當當開源的sharding-jdbc、蘑菇街的TSharding、攜程開源的Ctrip-DAL等。我們以sharding-jdbc為例,其架構圖如下所示:

此類中間件的基本思路,就是重新實現JDBC的API,通過重新實現DataSource、PrepareStatement等操作數據庫的接口,讓應用層在基本不改變業務代碼(需業務自行定義路由規則)的情況下透明地實現分庫分表的能力。

中間件給上層應用提供熟悉的JDBC API,內部通過一系列的准備工作獲取真正可執行的sql,然后底層再按照傳統的方法(比如數據庫連接池)獲取物理連接來執行sql,最后把數據結果合並處理成ResultSet返回給應用層。

優點

無需額外部署,性能損耗低,無中心化

缺點

不支持異構語言,與應用強耦合,SQL支持能力較弱(受應用影響),連接消耗數高
  • 中間層代理類中間件

這類分庫分表中間件的核心原理是在應用和數據庫的連接之間搭起一個代理層,上層應用以標准的MySQL協議來連接代理層,然后代理層負責轉發請求到底層的MySQL物理實例,這種方式對應用只有一個要求,就是只要用MySQL協議來通信即可,所以用MySQL Workbench這種純的客戶端都可以直接連接你的分布式數據庫,自然也天然支持所有的編程語言。比較有代表性的產品有開創性質的Amoeba、阿里開源的Cobar、Mycat 、當當開源的sharding-proxy,奇虎360開源的Atlas等。我們以sharding-proxy為例,其架構圖如下所示:

優點

支持異構語言,與應用完全解耦,SQL支持能力較強,連接消耗數低

缺點

需連接消耗數,性能損耗略高,存在中心化

無論應用層依賴類中間件還是中間層代理類中間件,其核心流程都是相同的,即:SQL解析->SQL路由->SQL重寫->SQL執行->結果處理(排序,聚合,合並)。除了分庫分表功能之外,分布式數據庫中間件還可以集成分布式自增主鍵,數據庫治理,分布式事務等功能。

SQL解析是整個分布式數據庫中間件的核心,SQL解析和程序代碼解析類似,它按照SQL語法對SQL文本進行解析,識別出文本中各個部分然后以抽象語法樹(AST)的形式輸出。開源產品使用的SQL解析引擎各不相同。不同的解析引擎偏重的能力有所不同,有的更注重解析SQL的性能,有的更注重支持SQL的能力,還有的更注重擴展性和兼容性。使用比較多的有JSQLParser,Druid,ANTLR等。

常見的數據層中間件

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

1.TDDL

淘寶根據自己的業務特點開發了TDDL框架,主要解決了分庫分表對應用的透明化以及異構數據庫之間的數據復制,它是一個基於集中式配置的JDBC datasource實現。

特點

實現動態數據源、讀寫分離、分庫分表。

缺點

分庫分表功能還未開源,當前公布文檔較少,並且需要依賴diamond(淘寶內部使用的一個管理持久配置的系統)

2.DRDS

阿里分布式關系型數據庫服務(Distribute Relational Database Service,簡稱DRDS)是一種水平拆分、可平滑擴縮容、讀寫分離的在線分布式數據庫服務。

前身為淘寶 TDDL下一代是 DRDS,整合雲服務,收費、Cobar、TDDL整合,商用,首選。

2.Atlas

Atlas是由 Qihoo 360公司Web平台部基礎架構團隊開發維護的一個基於MySQL協議的數據中間層項目。

它在MySQL官方推出的MySQL-Proxy 0.8.2版本的基礎上,修改了大量bug,添加了很多功能特性。目前該項目在360公司內部得到了廣泛應用,很多MySQL業務已經接入了Atlas平台,每天承載的讀寫請求數達幾十億條。

主要功能:

1.讀寫分離

2.從庫負載均衡

3.IP過濾

4.自動分表

5.DBA可平滑上下線DB

6.自動摘除宕機的DB

 

3.MTDDL(Meituan Distributed Data Layer)

美團點評分布式數據訪問層中間件

特點

實現動態數據源、讀寫分離、分庫分表,與tddl類似。

下面我以MTDDL為例,也可以參考淘寶tddl,完整詳解分布式數據層中間件的架構設計。

 

分布式數據層中間件架構設計

下圖是一次完整的DAO層insert方法調用時序圖,簡單闡述了MTDDL的整個邏輯架構。

其中包含了:

1.分布式唯一主鍵的獲取

2.動態數據源的路由

3.以及SQL埋點監控等過程:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

分布式數據層中間件:具體實現

1.動態數據源及讀寫分離

在Spring JDBC AbstractRoutingDataSource的基礎上擴展出MultipleDataSource動態數據源類,通過動態數據源注解及AOP實現。

2.動態數據源

MultipleDataSource動態數據源類,繼承於Spring JDBC AbstractRoutingDataSource抽象類,實現了determineCurrentLookupKey方法,通過setDataSourceKey方法來動態調整dataSourceKey,進而達到動態調整數據源的功能。其類圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

3.動態數據源AOP

ShardMultipleDataSourceAspect動態數據源切面類,針對DAO方法進行功能增強,通過掃描DataSource動態數據源注解來獲取相應的dataSourceKey,從而指定具體的數據源。具體流程圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

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。

整體架構圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

每個業務tag對應一條DB記錄,DB MaxID字段記錄當前該Tag已分配出去的最大ID值。

IDGenerator服務啟動之初向DB申請一個號段,傳入號段長度如 genStep = 10000,DB事務置 MaxID = MaxID + genStep,DB設置成功代表號段分配成功。每次IDGenerator號段分配都通過原子加的方式,待分配完畢后重新申請新號段。

7.唯一主鍵生成算法擴展

MTDDL不僅集成了Leaf算法,還支持唯一主鍵算法的擴展,通過新增唯一主鍵生成策略類實現IDGenStrategy接口即可。IDGenStrategy接口包含兩個方法:getIDGenType用來指定唯一主鍵生成策略,getId用來實現具體的唯一主鍵生成算法。其類圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

8.分庫分表

在動態數據源AOP的基礎上擴展出分庫分表AOP,通過分庫分表ShardHandle類實現分庫分表數據源路由及分表計算。ShardHandle關聯了分庫分表上下文ShardContext類,而ShardContext封裝了所有的分庫分表算法。其類圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

分庫分表流程圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

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用來實現具體的數據源及分表計算邏輯。其類圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

11.全注解方式接入

為了盡可能地方便業務方接入,MTDDL采用全注解方式使用分庫分表功能,通過ShardInfo、ShardOn、IDGen三個注解實現。

ShardInfo注解用來指定具體的分庫分表配置:包括分表名前綴tableName、分表數量tableShardNum、分庫數量dbShardNum、分庫分表策略shardType、唯一鍵生成策略idGenType、唯一鍵業務方標識idGenKey;ShardOn注解用來指定分庫分表字段;IDGen注解用來指定唯一鍵字段。具體類圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

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上報監控數據。整體架構圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

連接數量監控

監控連接池active、idle、total連接數量,Counter格式:(連接池類型.數據源.active/idle/total_connection),效果圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

獲取連接時間監控

監控獲取空閑連接時間,Counter格式:(ds.getConnection.數據源.time),效果圖如下:

分布式數據層中間件詳解:如何實現分庫分表+動態數據源+讀寫分離-mikechen的互聯網架構

以上!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM