分庫分表中間件TDDL解析

前言

在開始講解淘寶的 TDDL(Taobao Distribute Data Layer) 技術之前,請允許筆者先吐槽一番。首先要開噴的是淘寶的社區支持做的無比的爛, TaoCode 開源社區上面,幾乎從來都是有人提問,無人響應。再者版本迭代速度也同樣差強人意 , 就目前而言 TDDL 的版本已經全線開源(Group、Atom、Matrix)大家可以在Github上下載源碼 。

目錄

一、互聯網當下的數據庫拆分過程

二、 TDDL 的架構原型

三、下載 TDDL 的 Atom 層和 Group 層源代碼

四、 Diamond 簡介

五、 Diamond 的安裝和使用

六、動態數據源層的 Master/Salve 讀寫分離 配置與實現

七、 Matrix 層的分庫分表配置與實現

一、互聯網當下的數據庫拆分過程

對於一個剛上線的互聯網項目來說,由於前期活躍用戶數量並不多,併發量也相對較小,所以此時企業一般都會選擇將所有數據存放在 一個數據庫 中進行訪問操作。但隨着後續的市場推廣力度不斷加強,用戶數量和併發量不斷上升,這時如果僅靠一個數據庫來支撐所有訪問壓力,幾乎是在 自尋死路 。所以一旦到了這個階段,大部分 Mysql DBA 就會將數據庫設置成 讀寫分離狀態,也就是一個 Master 節點對應多個 Salve 節點。經過 Master/Salve 模式的設計後,完全可以應付單一數據庫無法承受的負載壓力,並將訪問操作分攤至多個 Salve 節點上,實現真正意義上的讀寫分離。但大家有沒有想過,單一的 Master/Salve 模式又能抗得了多久呢?如果用戶數量和併發量出現 量級 上升,單一的 Master/Salve 模式照樣抗不了多久,畢竟一個 Master 節點的負載還是相對比較高的。爲了解決這個難題, Mysql DBA 會在單一的 Master/Salve 模式的基礎之上進行數據庫的 垂直分區 (分庫)。所謂垂直分區指的是可以根據業務自身的不同,將原本冗餘在一個數據庫內的業務表拆散,將數據分別存儲在不同的數據庫中,同時仍然保持 Master/Salve 模式。經過垂直分區後的 Master/Salve 模式完全可以承受住難以想象的高併發訪問操作,但是否可以永遠高枕無憂 了?答案是否定的,一旦業務表中的數據量大了,從維護和性能角度來看,無論是任何的 CRUD 操作,對於數據庫而言都是一件極其耗費資源的事情。即便設置了索引, 仍然無法掩蓋因爲數據量過大從而導致的數據庫性能下降的事實 ,因此這個時候 Mysql DBA 或許就該對數據庫進行 水平分區 (分表, sharding ),所謂水平分區指的是將一個業務表拆分成多個子表,比如 user_table0 、 user_table1 、 user_table2 。子表之間通過某種契約關聯在一起,每一張子表均按段位進行數據存儲,比如 user_table0 存儲 1-10000 的數據,而 user_table1 存儲 10001-20000 的數據,最後 user_table3 存儲 20001-30000 的數據。經過水平分區設置後的業務表,必然能夠將原本一張表維護的海量數據分配給 N 個子表進行存儲和維護,這樣的設計在國內一流的互聯網企業比較常見,如圖 1-1 所示:

圖 1-1 水平分區

上述筆者簡單的講解了數據庫的分庫分表原理。接下來請大家認真思考下。原本一個數據庫能夠完成的訪問操作,現在如果按照分庫分表模式設計後,將會顯得非常麻煩,這種麻煩尤其體現在 訪問操作 上。因爲持久層需要判斷出對應的數據源,以及數據源上的水平分區,這種訪問方式我們稱之爲訪問 “ 路由 ” 。按照常理來說,持久層不應該負責數據訪問層 (DAL) 的工作,它應該只關心 one to one 的操作形式,所以淘寶的 TDDL 框架誕生也就順其自然了。

 

二、 TDDL 的架構原型

淘寶根據自身業務需求研發了 TDDL ( Taobao Distributed Data Layer )框架, 主要用於解決分庫分表場景下的訪問路由(持久層與數據訪問層的配合)以及異構數據庫之間的數據同步,它是一個基於集中式配置的 JDBC DataSource 實現,具有分庫分表、 Master/Salve 、動態數據源配置等功能。

就目前而言,許多大廠也在出一些更加優秀和社區支持更廣泛的 DAL 層產品,比如 Hibernate Shards 、 Ibatis-Sharding 等。如果你要問筆者還爲什麼還要對 TDDL 進行講解,那麼筆者只能很無奈 的表示公司要這麼幹,因爲很多時候技術選型並不是筆者說了算,而是客戶說了算。當筆者費勁所有努力在 google 上尋找 TDDL 的相關使用說明和介紹時,心裏一股莫名的火已經開始在蔓延,對於更新緩慢(差不多一年沒更新過 SVN ),幾乎沒社區支持(提問從不響應)的產品來說,除了蝸居在企業內部,必定走不了多遠,最後的結局註定是 悲哀 的。好了,既然抱怨了一番,無論如何還是要堅持講解完。 TDDL 位於數據庫和持久層之間,它直接與數據庫建立交道,如圖 1-2 所示:

圖 1-2 TDDL 所處領域模型定位

傳說淘寶很早以前就已經對數據進行過分庫分表處理,應用層連接多個數據源,中間有一個叫做 DBRoute 的技術對數據庫進行 統一 的路由訪問。 DBRoute 對數據進行多庫的操作、數據的整合,讓應用層像操作一個數據源一樣操作多個數據庫。但是隨着數據量的增長,對於庫表的分法有了更高的要求,例如,你的商品數據到了百億級別的時候,任何一個庫都無法存放了,於是分成 2 個、 4 個、 8 個、 16 個、 32 個 …… 直到 1024 個、 2048 個。好,分成這麼多,數據能夠存放了,那怎麼查詢它?這時候,數據查詢的中間件就要能夠承擔這個重任了,它對上層來說,必須像查詢一個數據庫一樣來查詢數據,還要像查詢一個數據庫一樣快( 每條查詢要求在幾毫秒內完成 ), TDDL 就承擔了這樣一個工作( 其他 DAL 產品做得更好 ),如圖 1-3 所示:

圖 1-3 TDDL 分庫分表查詢策略

上述筆者描述了 TDDL 在分庫分表環境下的查詢策略,那麼接下來筆者有必要從淘寶官方 copy它們自己對 TDDL 優點的一些描述,真實性不敢保證,畢竟沒完全開源,和社區零支持,大家看一看就算了,別認真。

淘寶人自定的 TDDL 優點:

、數據庫主備和動態切換; 
、帶權重的讀寫分離; 
、單線程讀重試; 
、集中式數據源信息管理和動態變更; 
、剝離的穩定 jboss 數據源; 
、支持 mysql 和 oracle 數據庫; 
、基於 jdbc 規範,很容易擴展支持實現 jdbc 規範的數據源; 
、無 server,client-jar 形式存在,應用直連數據庫; 
、讀寫次數 併發度流程控制,動態變更; 
10 
、可分析的日誌打印 日誌流控,動態變更;

注意 

TDDL 必須要依賴 diamond 配置中心( diamond 是淘寶內部使用的一個管理持久配置的系統,目前淘寶內部絕大多數系統的配置)。

接下來,筆者將會帶領各位一起分析 TDDL 的體系架構。 TDDL 其實主要可以劃分爲 3 層架構,分別是 Matrix 層、 Group 層和 Atom 層。 Matrix 層用於實現分庫分表邏輯,底層持有多個 Group 實例。而 Group 層和 Atom 共同組成了 動態數據源 , Group 層實現了數據庫的 Master/Salve模式的寫分離邏輯,底層持有多個 Atom 實例。最後 Atom 層 (TAtomDataSource) 實現數據庫 ip,port,password,connectionProperties 等信息的動態推送 , 以及持有原子的數據源分離的 JBOSS 數據源)。

圖 1-4 TDDL 體系結構

        章節的最後,我們還需要對 TDDL 的原理進行一次剖析。因爲我們知道持久層只關心對數據源的 CRUD 操作,而多數據源的訪問,並不應該由它來關心。也就是說 TDDL 透明給持久層的數據源接口應該是統一且 “ 單一 ” 的,至於數據庫 到底如何分庫分表,持久層無需知道,也無需編寫對應的 SQL 去實行 應對策略 。這個時候對 TDDL 一些疑問就出現了, TDDL 需要對 SQL 進行二次解析和拼裝嗎?答案是 不解析僅拼裝 。說白了 TDDL 只需要從持久層拿到發出的 SQL

再按照一些分庫分表條件,進行特定的 SQL 擴充以此滿足訪問路路由操作。

以下是淘寶團隊對 TDDL 的官方原理解釋:

1 、 TDDL 除了拿到分庫分表條件外,還需要拿到 order by 、 group by 、 limit 、 join 等信息,SUM 、

MAX 、 MIN 等聚合函數信息, DISTINCT 信息。具有這些關鍵字的 SQL 將會在單庫和多庫情況下進行 , 語義是不同的。 TDDL 必須對使用這些關鍵字的 SQL 返回的結果做出合適的處理;

2 、 TDDL 行復制需要重新拼寫 SQL, 帶上 sync_version 字段;

3 、不通過 sql 解析 , 因爲 TDDL 遵守 JDBC 規範 , 它不可能去擴充 JDBC 規範裏面的接口 , 所以只能通過 SQL 中加額外的字符條件 ( 也就是 HINT 方式 ) 或者 ThreadLocal 方式進行傳遞 , 前者使 SQL 過長 , 後者難以維護 , 開發 debug 時不容易跟蹤 , 而且需要判定是在一條 SQL 執行後失效還是 1 個連接關閉後才失效;

4 、 TDDL 現在也同時支持 Hint 方式和 ThreadLocal 方式傳遞這些信息;

 

三、下載 TDDL 的 Atom 層和 Group 層源代碼

         前面我們談及了 TDDL 的動態數據源主要由 2 部分構成,分別是 Atom 和 Group 。 Group用於實現數據庫的 Master/Salve 模式的寫分離邏輯,而 Atom 層則是持有數據源。非常遺憾的 TDDL 中還有一層叫做 Matrix ,該層是整個 TDDL 最爲核心的地方,淘寶也並沒有對這一層實現開源,而 Matrix 層主要是建立在動態數據源之上的分庫分表實現。換句話說, TDDL 是基於模塊化結構的,開發人員可以選用 TDDL 中的部分子集。

       大家可以從淘寶的 TaoCode 上下載 TDDL 的源碼帶,然後進行構件的打包。 TDDL 的項目主要是基於 Maven 進行管理的,所以建議大家如果不瞭解 Maven 的使用,還是參考下筆者的博文《 Use Maven3.x 》。

       大家下載好 TDDL 的源代碼後,通過 IDE 工具導入進來後可以發現,開源的 TDDL 的工程結構有如下幾部份組成:

tddl-all –

— tbdatasource

— tddl-atom-datasource

— tddl-common

— tddl-group-datasource

— tddl-interact

— tddl-sample

大家可以使用 Maven 的命令“ mvn package “將 TDDL 的源代碼打包成構件。如果你的電腦上並沒有安裝 Maven 的插件到不是沒有辦法實現構件打包,你可以使用 eclipse 的導出命令,將源代碼導出成構件形式也可以。

四、 Diamond 簡介

       使用任何一種框架都需要配置一些配置源信息,畢竟每一種框架都有自己的規範,使用者務必遵守這些規範來實現自己的業務與基礎框架的整合。自然 TDDL 也不例外,也是有配置信息需要顯式的進行配置,在 TDDL 中,配置可以基於 2 種方式,一種是基於本地配置文件的形式,另外一種則是基於 Diamond 的形式進行配置,在實際開發過程中,由於考慮到配置信息的集中管理所帶來的好處,大部分開發人員願意選擇將 TDDL 的配置信息託管給 Diamond ,所以本文還是以Diamond 作爲 TDDL 的配置源。

       diamond 是淘寶內部使用的一個管理持久配置的系統,它的特點是簡單、可靠、易用,目前淘寶內部絕大多數系統的配置,由 diamond 來進行統一管理。 diamond 爲應用系統提供了獲取配置的服務,應用不僅可以在啓動時從 diamond 獲取相關的配置,而且可以在運行中對配置數據的變化進行感知並獲取變化後的配置數據。

五、 Diamond 的安裝和使用

       Diamond 和 TDDL 不同,它已經實現了完全意義上的開源。大家可以從淘寶的 TaoCode

上下載 Diamond 的源代碼, SVN 下載地址爲 http://code.taobao.org/svn/diamond/trunk 。當大家成功下載好 Diamond 的源代碼後,我們接下來就需要開始 Diamond 的環境搭建工作。

       首先我們需要安裝好 Mysql 數據庫,以 root 用戶登錄,建立用戶並賦予權限,建立數據庫,然後建表,語句分別如下:

create database diamond;

grant all on diamond.* to zh@’%’   identified by ‘abc’;

use diamond

create table config_info (

‘ id’ bigint(64) unsigned NOT NULL auto_increment,

‘ data_id’ varchar(255) NOT NULL default ’ ’,

‘ group_id’ varchar(128) NOT NULL default ’ ’,

‘ content’ longtext NOT NULL,

‘ md5 ′ varchar(32) NOT NULL default ’ ’ ,

‘ gmt_create ’ datetime NOT NULL default ’ 2010-05-05 00:00:00 ′ ,

‘ gmt_modified ’ datetime NOT NULL default ’ 2010-05-05 00:00:00 ′ ,

PRIMARY KEY   (‘id’),

UNIQUE KEY ‘uk_config_datagroup’ (‘data_id’,'group_id’));

完成後,請將數據庫的配置信息( IP ,用戶名,密碼)添加到 diamond-server 工程的 src/resources/jdbc.properties 文件中的 db.url , db.user , db.password 屬性上面,這裏建立的庫名,用戶名和密碼,必須和 jdbc.properties 中對應的屬性相同。

tomcat 是 Damond 的運行容器,在 diamond-server 源代碼根目錄下,執行 mvn clean package -Dmaven.test.skip ,成功後會在 diamond-server/target 目錄下生成 diamond-server.war 。打包完成後,將 diamond-server.war 放在 tomcat 的 webapps 目錄下。最後啓動 tomcat ,即啓動了 Diamond 。

http server 用來存放 diamond server 等地址列表,可以選用任何 http server ,這裏以 tomcat 爲例。一般來講, http server 和 diamond server 是部署在不同機器上的,這裏簡單起見,將二者部署在同一個機器下的同一個 tomcat 的同一個應用中,注意,如果部署在不同的 tomcat 中,端口號一定是 8080 ,不能修改(所以必須部署在不同的機器上)。

在 tomcat 的 webapps 中的 diamond-server 中建立文件 diamond ,文件內容是 diamond-server的地址列表,一行一個地址,地址爲 IP ,例如 127.0.0.1 ,完成這些步驟後,就等於已經完成 Diamond 的安裝。

六、動態數據源層的 Master/Salve 讀寫分離 配置與實現

其實使用 TDDL 並不複雜,只要你會使用 JDBC ,那麼 TDDL 對於你來說無非就只需要將 JDBC 的操作連接替換爲 TDDL 的操作連接,剩餘操作一模一樣。並且由於 TDDL 遵循了 JDBC 規範,所以你完全還可以使用 Spring JDBC 、 Hibernate 等第三方持久層框架進行 ORM 操作。

我們來看看如何 TDDL 中配置 TDDL 的讀寫分離, Atom+Group 組成了 TDDL 的動態數據源,這 2 層主要負責數據庫的讀寫分離。

TGroupDataSource 的配置

1、   配置讀寫分離權重:

KEY : com.taobao.tddl.jdbc.group_V2.4.1_ “ groupKey ” (Matrix 中爲“ dbKey ” )

VALUE : dbKey:r10w0,dbKey2:r0w10

 

TAtomDataSource 的配置(由 部分組成, global 、 app 、 user 

1、   基本數據源信息 (global) 

KEY : com.taobao.tddl.atom.global. “ dbKey ”

VALUE :(

ip= 數據庫 IP

port= 數據庫端口

dbName= 數據庫暱稱

dbType= 數據庫類型

dbStatus=RW )

 

2、   數據庫密碼信息 (user) 

KEY : com.taobao.tddl.atom.passwd. “ dbKey ” . “ dbType ” . “ dbUserName ”

VALUE :數據庫密碼

 

3、   數據庫連接信息( app ,如果不配置時間單位,缺省爲分鐘):

KEY : com.taobao.tddl.atom.app. “ appName ” . “ dbKey ”

VALUE :(

userName= 數據庫用戶

minPoolSize= 最小連接數

maxPoolSize= 最大連接數

idleTimeout= 連接的最大空閒時間

blockingTimeout= 等待連接的最大時間

checkValidConnectionSQL=select 1

connectionProperties=rewriteBatchedStatements=true&characterEncoding=UTF8&connectTimeout=1000&autoReconnect=true&socketTimeout=12000 )

七、 Matrix 層的分庫分表配置與實現

       在上一章節中,筆者演示瞭如何在 Diamond 中配置數據庫的讀寫分離,那麼本章筆者則會演示如果配置 TDDL 的分庫分表。

       TDDL 的 Matrix 層是建立在動態數據源之上的,所以分庫分表的配置和讀寫分離的基本配置也是一樣的,只不過我們需要新添加 dbgroups 和 shardrule 項。 dbgroups 項包含了我們所需要配置的所有 AppName 選項,而 shardrule 則是具體的分庫分表規則。這裏有一點需要提醒各位,在開源版本的 TDDL 中,配置 TGroupDataSource 讀寫分離是使用 dbKey ,然而在 Matrix 中則是使用 appName 

、配置 Group 組:

KEY : com.taobao.tddl.v1_ “ appName ” _dbgroups

VALUE : appName1 , appName2

、配置分庫分表規則:

       KEY : com.taobao.tddl.v1_”appName”_shardrule

         VALUE :(

         <?xml version="1.0" encoding="gb2312"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

         <bean id="root" class="com.taobao.tddl.common.config.beans.AppRule" init-method="init">

                   <property name="readwriteRule" ref="readwriteRule" />

         </bean>

    <bean id="readwriteRule" class="com.taobao.tddl.common.config.beans.ShardRule">

                   <property name="dbtype" value="MYSQL" />

                   <property name="tableRules">

                            <map>

                                     <entry key="tddl_table" value-ref="tddl_table" />

                            </map>

                   </property>

         </bean>

         <bean id="tddl_table"   init-method="init"

                   class="com.taobao.tddl.common.config.beans.TableRule">

                   <!-- 數據庫組 index 號   -->

         <property name="dbIndexes" value="tddl_test,tddl_test2" />

                   <!-- 分庫規則 -->

                   <property name="dbRuleArray" value="(#id#.longValue() % 4).intdiv(2)"/>

                   <!-- 分表規則 , 需要注意的是,因爲 taobao 目前 dba 的要求是所有庫內的表名必須完全不同,因此這裏多加了一個映射的關係

                            簡單來說,分表規則只會算表的 key.

                            倆庫 4 表 : db1(tab1+tab2) db2(tab3+tab4)

                            db1 == key: 0 value tab1

                                   key: 1 value tab2

                           

                            db2 == key: 0 value tab3

                                   key: 1 value tab4

                   -->

                   <property name="tbRuleArray" value="#id#.longValue() % 4 % 2"/>

                   <property name="tbSuffix" value="throughAllDB:[_0-_3]" />

         </bean>

</beans>

 

         TDDL 的分庫分表配置形式完全是採用 Spring 的配置形式,這一點大家應該是非常熟悉的。那麼接下來我們一步一步的分析 TDDL 的分庫分表規則。

         在元素 <map/> 中我們可以定義我們所需要的分表,也就是說,當有多個表需要實現分表邏輯的時候,我們可以在集合中進行定義。當然我們還需要外部引用 <bean/> 標籤中定義的具體的表邏輯的分庫分表規則。

         在分庫分表規則中,我們需要定義 數據庫組 index 號,也就是說我們需要定義我們有多少的 appNames ,接下來我們就可以定義分庫和分表規則了。 TDDL 的分庫分表規則完全是採用取餘方式,比如 <property name="dbRuleArray" value="(#id#.longValue() % 4).intdiv(2)"/> , value 屬性中包含有具體的分庫規則,其中“ #id# ”作爲我們的分庫分表條件,此值在數據庫中對應的類型必須是整類,然後進行取餘後再進行 intdiv 。或許有些朋友看不太明白這個是什麼意思,我們用簡單的一點的話來說就是,“ #id#.longValue() % 4).intdiv(2) ”的含義是我們需要分 2 個庫和 4 個表,那麼我們怎麼知道我們的數據到底落盤到哪一個庫呢?打個比方,如果我們的 id 等於 10 ,首先 10%4 等於 2 ,然後 2/2 等於 1 , TDDL 分庫規則下標從 0 開始,那麼我們的數據就是落盤到第 2個庫。

         當大家明白 TDDL 的分庫規則後,我們接下來再來分析分表規則 <property name="tbRuleArray" value="#id#.longValue() % 4 % 2"/> 。和分庫規則類似的是,我們都採用取餘算法首先進行運算,只不過分表尾運算也是使用取餘,而不是除算。打個比方,如果我們的 id 等於 10 ,首先 10%4 等於 2 ,然後 2%2 等於 0 ,那麼我們的數據就是落盤到第 2 個庫的第 1 張表。


應用層使用 TDDL 示例:

public class UseTDDL {
    private static final String APPNAME = "tddl_test";
    private static final String GROUP_KEY = "tddltest";
    private static TGroupDataSource tGroupDataSource ;
    /* 初始化動態數據源 */
    static {
        tGroupDataSource = new TGroupDataSource();
        tGroupDataSource .setAppName( APPNAME );
        tGroupDataSource .setDbGroupKey( GROUP_KEY );
        tGroupDataSource .init();
    }
    @Test
    public void testQuery() {
        final String LOAD_USER = "SELECT userName FROM tddl_table WHERE userName=?";
        Connection conn = null ;
        PreparedStatement pstmt = null ;
        ResultSet rs = null ;
        try {
            conn = tGroupDataSource .getConnection();
            pstmt = conn.prepareStatement(LOAD_USER);
            pstmt.setString(1, "tddl-test2");
            rs = pstmt.executeQuery();
            while (rs.next())
                System. out .println("data: " + rs.getString(1));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if ( null != rs)
                    rs.close();
                if ( null != pstmt)
                    pstmt.close();
                if ( null != conn)
                    conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

原文鏈接:https://www.tuicool.com/articles/nmeuu2

tddl源碼地址:https://github.com/alibaba/tb_tddl

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章