分佈式數據庫中間件—TDDL的使用介紹

一、分佈式數據庫的演化

Tddl是一個分佈式數據庫中間件,主要是爲了解決分佈式數據庫產生的相關問題,因此在學習tddl之前我們先了解一下分佈式數據庫的演化歷程,所謂磨刀不誤砍柴工,知其然亦要知其所以然,分佈式數據庫與數據庫中間件息息相關,瞭解學習是很有必要的。整個分佈式數據庫的演化過程如下:

\

1、單庫單表

通常剛開始的時候,應用的數據比較少,也不會很複雜,所以應用只有一個數據庫,數據庫中的業務表是一張對應業務的完整的表,這也是我們剛開始接觸數據庫時的數據庫形態。

2、讀寫分離

隨着業務的發展,數據量與數據訪問量不斷增長,很多時候應用的主要業務是讀多寫少的,比如說一些新聞網站,運營在後臺上傳了一堆新聞之後,所有的用戶都會去讀取這些新聞資訊,因此數據庫面臨的讀壓力遠大於寫壓力,那麼這時候在原來數據庫 Master 的基礎上增加一個備用數據庫 Slave,備庫和主庫存儲着相同的數據,但只提供讀服務,不提供寫服務。以後的寫操作以及事務中的讀操作就走主庫,其它讀操作就走備庫,這就是所謂的讀寫分離。

讀寫分離會直接帶來兩個問題:

1)數據複製問題

因爲最新寫入的數據只會存儲在主庫中,之後想要在備庫中讀取到新數據就必須要從主庫複製過來,這會帶來一定的延遲,造成短期的數據不一致性。但這個問題應該也沒有什麼特別好的辦法,主要依賴於數據庫提供的數據複製機制,常用的是根據數據庫日誌 binary-log 實現數據複製。

2)數據源選擇問題

讀寫分離之後我們都知道寫要找主庫,讀要找備庫,但是程序不知道,所以我們在程序中應該根據 SQL 來判斷出是讀操作還是寫操作,進而正確選擇要訪問的數據庫。

3、垂直分庫

數據量與訪問量繼續上升時,主備庫的壓力都在變大,這時候可以根據業務特點考慮將數據庫垂直拆分,即把數據庫中不同的業務單元的數據劃分到不同的數據庫裏面。比如說,還是新聞網站,註冊用戶的信息與新聞是沒有多大關係的,數據庫訪問壓力大時可以嘗試把用戶信息相關的表放在一個數據庫,新聞相關的表放在一個數據庫中,這樣大大減小了數據庫的訪問壓力。

垂直分庫會帶來以下問題:

1)ACID 被打破

數據分到不同的數據庫之後,原來的事務操作將會受很大影響,比如說註冊賬戶的時候需要在用戶表和用戶信息表中插入一條數據,單機數據庫利用事務可以很好地完成這個任務,但是多機將會變得比較麻煩。以下兩點也是因爲數據庫在不同的機子上而導致簡單的操作變得複雜。

2)Join 操作困難

3)外鍵約束受影響

4、水平分表

經過長時間積累,特別對於 ugc 的業務,數據量會達到驚人的地步,每張表存放着大量的數據,任何 CRUD 都變成了一次極其消耗性能的操作,這個時候就會考慮水平分表,把一張表內大量的數據拆分成多張子表,比如說原來一張表中存放 50000 條數據,水平分成 5 張表之後,每張表只要存放 10000 條數據。這種結構可以比較容易的存儲和維護海量數據。

水平分表會在垂直分庫的基礎上帶來更多的影響:

1)自增主鍵會有影響

這個影響很明顯,分表中如果使用的是自增主鍵的話,那麼就不能產生唯一的 ID 了,因爲邏輯上來說多個分表其實都屬於一張表,自增主鍵無法標識每一條數據。

2)有些單表查詢會變成多表

比如說 count 操作,原來是一張表的問題,現在要從多張分表中共同查詢才能得到結果。

二、TDDL介紹

1、產生背景

由一中可以看出,數據庫從單機走向分佈式將會面臨很多很多的問題,分佈式數據庫中間件就是爲了解決這些問題而生的。Tddl作爲開源分佈式數據庫中間件之一,由阿里研發,其出現解決了以上分佈式數據庫需求帶來的問題,總結起來如下:

1)單一數據庫無法滿足性能需求

隨着業務的發展,數據量急劇增大,使用單庫單表時,數據庫壓力過大,因此通過分庫分表,讀寫分離的方式,減輕數據庫的壓力。

2)系統容災

使用單機數據庫時,如果數據庫宕機,則無法對外提供服務,因此產生了主備數據庫的方案,當主庫宕機後,會自動切換到備庫,不影響對外的服務。

3)運維管理

直接連接單機數據庫時,無法動態的切換數據源,使用TDDL中間件,可以動態的切換數據源。

2、發展史

1)TDDL 2.0 (2009~2011) 第一個流行版本

2)TDDL 3.1 (2012~) 規則版本升級

3)TDDL 3.3 (2013~) 引入druid鏈接池

4)Andor (2012~2013) 一次全新的嘗試,支持跨庫查詢

5)TDDL 5.0 (2013) 基於Andor + TDDL3.3的發展而來,保留各自的優點

6)TDDL 5.1 (2014~) 集成cobar,提供server模式,解決跨語言查詢

三、TDDL原理解析

1、整體結構

1)包結構

\

2)模型結構

Tddl主要部署在ibatis、mybatis或者其他ORM框架之下,JDBC Driver之上。整個中間件實現了JDBC規範,所以可以將Tddl當做普通數據源實例並且注入到各種ORM框架中使用。由下圖中的結構模型圖可以看出,tddl是JDBC或者持久框架層與底層JDBC驅動交互的橋樑,或者也可以稱之爲中轉站,起到進出的加工作用。

\

3)分佈式數據庫邏輯結構

分佈式數據庫在經過讀寫分離和分庫分表之後,數據庫的結構變得複雜,邏輯上大致如下圖所示:

\

除了最底層的物理DB,上兩層的邏輯層是分佈式數據庫中間件着重要解決的問題,tddl的設計亦是以圖中的三層邏輯模型爲基礎進行的。爲了解決每一層所要應對的問題,tddl在結構上至上而下也分了三層,分別是 Matrix 層、Group層以及 Atom 層。具體來說,從邏輯上來看,最頂層是要進行分庫分表才過渡到中間層的,分庫分表所帶來的問題是在 Matrix 層解決 ,包括SQL解釋、優化和執行等;中間層是經過讀寫分離和主備切換纔會出現最底層,因此讀寫分離與主備切換的工作由 Group 層解決;至於 Atom 層,它面對的是實實在在的每一個數據庫,更多的工作在與對數據庫的連接管理,比如說當數據庫的 IP 地址發生改變時,Atom 層要動態感知,以免連接找不到地址。

2、三層數據源結構(核心組件

TDDL作爲中間件,其作用是根據路由規則,將sql路由到正確的分庫、分表上去執行,再將結果進行彙總,返回給用戶,對於用戶,不需要了解TDDL的原理,可以像使用單庫單表一樣去使用分佈式數據庫。在解析執行和封裝這些類似於單庫單表的SQL語句時,tddl有專門的組件來進行處理,這些組件就是核心的三層數據源,分別是Matrix層、Group層以及Atom層,其結構如下圖所示:

\

1)Matrix層

該層功能在於分庫分表路由,SQL語句的解釋、優化和執行,事務的管理規則的管理,各個子表查詢出來結果集的Merge等。

如上所述,Matrix 層可以解決分庫分錶帶來的問題,從本質上來看,分庫分錶帶來的最直接的影響是數據訪問的路由。單庫單表的時候,什麼都不用想,就是去這個 DB 中找到這張 Table 再進行查詢,但是多庫多表的時候就必須要考慮數據存到哪個數據庫,爲什麼要存到這個數據庫諸如此類的問題。這裏面涉及到數據訪問路由算法,它規定了數據的存儲位置,同樣也由它來指明該去哪裏查詢數據,常見的數據訪問路由算法有以下幾種:

a)固定哈希算法

固定哈希就再簡單不過了,就是根據某個字段(如整形的 id 或者字符串的 hashcode)對分庫的數量或者分表的數量進行取模,根據餘數路由到對應的位置。下面圖中的例子,數據庫垂直拆分成 4 個,其中有一張表水平拆分成兩張,利用固定哈希算法進行路由的過程如下:

b)一致性哈希算法

固定哈希算法足夠簡單實用,基本能保證數據均勻分佈,它也是 TDDL 的默認路由算法,但是在數據庫擴容的時候,固定哈希算法帶來的數據遷移成本也是不得不考慮的。依然是上面的例子,數據庫拆分成 4 個,當需要增加數據庫的時候,假設變成 5 個,由於取模的結果發生變化,原來數據庫中的絕大部分數據都要進行遷移,只有在數據庫倍增的時候,數據遷移量纔是最少的,但也高達 50%,而且倍增的成本比較高。

所以一致性哈希算法應景而生,它的原理就是通過該算法計算出 key 的 hashcode 之後對 2^32 取模,那麼數據都會落在 0~2^32 所組成的環中;同樣的,可以利用一致性哈希算法對機器的唯一屬性計算所在位置,然後數據存儲在順時針方向最近的機器上。如圖所示:

\

對於一致性哈希,增刪機器的成本就降低很多了,比如說在上圖 node2 與 node4 之間增加一臺機器 node5,那麼需要遷移的數據只分布在 node2 與 node5 之間,相比固定哈希來說遷移量小了很多。

\

c)虛擬節點

一致性哈希已經可以解決大部分需求了,但是對於數據集中在熱點的情況,一致性哈希同樣面臨比較大的挑戰。比如說,上圖的 node2 與 node4 之間集中了整個環中的大部分數據,當加入 node5 之後,其實起到的效果比較有限,因爲還是要有大量的數據進行遷移。引入虛擬節點之後,情況就不一樣了,所謂虛擬節點,它就是物理節點的映射,一個物理節點可以複製出多個虛擬節點,儘可能的讓它均勻分佈在環上,那麼即使數據再集中,其實也會存儲在不同的節點上,很好地起到了負載均衡的作用。

d)自定義路由規則

這是最不常用的方法,不過 TDDL 也支持,你可以實現自己的算法進行數據訪問路由,但一般要麼效果很差要麼成本很高。

2)Group層

該層的作用在於數據庫讀寫分離,基本上主數據庫負責讀寫,備份數據庫只負責讀;主備切換狀態對調後備庫變爲主庫,主庫變爲備庫;權重的選擇 根據權重選擇要去讀哪些庫;數據保護,數據庫down掉後的線程保護, 數據庫掛掉後的線程保護,不會因爲一個數據庫掛掉導致所有線程卡死。

讀寫分離與主備切換帶來的問題是 Group 層解決的核心。首先簡單介紹一下主備切換,由於主庫或者備庫都有可能掛掉,但很小概率同時掛,所以當一方掛掉的時候,Group 層要馬上把流量切到另一個庫,保證掛掉一個不會讓應用掛掉。

讀寫分離最大的問題是數據複製,通常有兩種複製場景,一種是鏡像複製,即主庫和從庫的數據結構是一模一樣的,通常根據主庫上的日誌變化,在從庫中執行相同的操作;另外一種是非對稱複製,意思就是主庫與備庫是以不同的方式分庫的,它們的結構雖然相同,但是主備庫中存儲的記錄是不相同的,主要目的是查詢條件不同時,把請求分發到更加適合的庫去操作。舉個例子,對於訂單數據庫,買家會根據自己的 ID 去查自己的交易記錄,所以主庫可以用買家 ID 分庫,保證單個買家的記錄在同一個數據庫中;但是賣家如果想看交易記錄的話可能就得從多個庫中進行查詢,這時候可以利用賣家 ID 進行分庫作爲備庫,這樣一來主備庫的複製就不能簡單的鏡像複製了,在進行復制操作之前還需要進行路由。

3)Atom層

Atom 模塊真正和物理數據庫交互,提供數據庫配置動態修改能力。

改層負責動態創建,添加,減少數據源。管理着底層的數據庫IP,連接等信息;底層對物理數據庫做了代理,對單庫的JDBC做了一層封裝,執行底層單庫的SQL;線程數、執行次數等狀態的統計等。

3、執行流程

1)執行流程

\

TDDL的工作流程類似上圖,client發送一條SQL的執行語句,會優先傳遞給Matrix層。由Martix 解釋 SQL語句,優化,並根據查詢條件路由到各個group,轉發sql進行查詢,各個group根據權重選擇其中一個Atom進行查詢,各個Atom再將結果返回給Matrix,Matrix將結果合併返回給client。具體的工作流程的可以拆分成如下圖:

\

Matrix層會先執行以下四個過程:

a)Sql的解析。首先將Sql語句解析成一顆抽象語法樹(Abstract Syntax Tree),解析成我們比較好處理的一個結構

b)規則的匹配與計算。基於上一步創建的語法樹查找匹配的規則,再根據規則去確定分庫分表的結果。這裏有一個概念就是規則,規則這裏可以簡單的看做就是定義數據庫怎麼進行分庫分表,要分成幾張庫幾張表,庫名和表名的命名是怎麼樣的。規則的匹配就是根據SQL的語句確定,具體查詢的子表是哪幾張。

c)表名替換。對於開發人員來說,它查詢的表直接就是select * from A.B limit 10(A爲數據庫名,B爲數據表名)。但底層其實會把這些表名替換成類似select * from A_000.B_001,select * from A_000.B_002,select * from A_001.TABLE_001這樣的形式。表名替換就是把總表的名稱替換爲這些子表的名字。

d)Sql的轉發。將上一步生成的各個sql語句轉發到對應的Group進行執行。這裏如上圖,我查詢的條件是where id = 2 or 3。那麼轉發給Group0的查詢爲where id=3,轉發給group1的查詢爲where id =2 。查詢的條件也會發生一定修改。

這樣四個步驟可以在Matrix層就實現了分庫分表的功能,對原始的Sql進行分解,將原本單庫單表的查詢語句,底層轉發到多庫多表並行的進行執行,提高了數據庫讀寫的性能。

接下來由Group執行兩個過程:

e)根據權重選擇AtomDs。通常會在主節點和副節點上讀取數據,只在主節點上寫入數據。

f)具有重試的策略地在AtomDs上執行SQL。這個可以防止單個的AtomDs發生故障,那麼會進入讀重試,以確保儘可能多的數據訪問可以在正常數據庫中訪問。

然後是Atom層執行兩個過程:

g)讀寫數控制、線程併發數控制 。同時會統計線程數、執行次數等信息。

h)執行sql,返回結果集。Atom底層利用druid進行連接池的管理,具體查詢還是對JDBC做了一定封裝。執行完Sql後對將結果返回給Matrix。

最後Matrix執行最後一個過程:

i)結果集合並。Matrix將Atom層的返回的各個結果集進行合併Merge,返回給Client端。

2)路由與擴容(固定哈希算法爲例)

a)數據庫水平拆分路由

\

f(pavarotti17)= hash(pavarotti17) % 1024,然後根據該值找對應的DB,

\

b)擴容

固定哈希算法是常用的算法,其擴容一般推薦每次以2倍的形式擴容,這樣只需要遷移一半的數據。

\

4、Senquence全局唯一id標識生成原理

1)背景

目前基於tddl進行分庫分表後,原本一個數據庫上的自增id的結果,在分庫分表下並不是全局唯一的。所以,分庫分表後需要有一種技術可以生成全局的唯一id。

2)工作原理

a)主要職責

生成全局唯一的id;保持高性能;保持高可用。

b)目前常見的幾種全局ID的思路

方案一

oracle sequence:基於第三方oracle的SEQ.NEXTVAL來獲取一個ID

優勢:簡單可用。

缺點:需要依賴第三方oracle數據庫。

方案二

mysql id區間隔離:不同分庫設置不同的起始值和步長,比如2臺mysql,就可以設置一臺只生成奇數,另一臺生成偶數. 或者1臺用0~10億,另一臺用10~20億.。

優勢:利用mysql自增id 。

缺點:運維成本比較高,數據擴容時需要重新設置步長。

方案三

基於數據庫更新+內存分配:在數據庫中維護一個ID,獲取下一個ID時,會對數據庫進行ID=ID+100 WHERE ID=XX,拿到100個ID後,在內存中進行分配 。

優勢:簡單高效。

缺點:無法保證自增順序。

目前tddl sequence也是選擇的方案3進行實現,但會有幾點額外的要求:

a. 只要生成id的數據庫不全部掛掉,均可以順暢提供服務;

b. 生成id的數據庫數量不定,按照應用對容災的需求指定不同機架不同機房的數據庫; (比如需要考慮單元化多機房的id生成)

c. 支持生成id的數據庫hang住快速略過和恢復自動加入 。

總結一下:生成id的數據庫可以是多機,其中的一個或者多個數據庫掛了,不能影響id獲取,保證嚴格高可用。

目前我們針對多機的id生成方案: 每個數據庫只拿自己的那一段id,如下圖左:

\

sample_group_0-sample_group_3是我們生成全局唯一id的4個數據庫,那麼每個數據庫對於同一個id有一個起始值,比如間隔是1000。

應用真正啓動的時候,可能某一臺機器上去取id,隨機取到了sample_group_1,那麼這臺機器上的應用會拿到1000-1999這一千個id(批量取,這個也就保證了應用端取id性能),而這個時候4個數據庫上id起始值會變成右圖所示,你也許注意到了,下次從sample_group_1上取得的id就變成了4000-4999。那麼也就是這樣,完全避免了多機上取id的重複。比如sample_group_1他會永遠只會取到1000-1999,4000-4999,8000-8999,12000-12999…其他數據庫也一樣,相互不會重疊。

這種產生全局唯一id的方式相當有效,保證基本的全局唯一特性和高性能的同時,可以對生成id的數據庫分機架分機房部署達到容災的目的。

3)配置

?

1

2

3

4

<bean class="com.taobao.tddl.client.sequence.impl.GroupSequence" id="sequence" init-method="init">

<property name="sequenceDao" ref="sequenceDao_one_db">

<property name="name" value="ni">

</property></property></bean>

5、tddl適用場景

1)高併發實時交易場景

面向客戶端的電商、金融、O2O、零售等行業普遍存在用戶基數大、營銷活動頻繁、核心交易系統數據庫響應日益變慢的問題,制約業務發展。 TDDL 提供線性水平擴展能力,能夠實時提升數據庫處理能力,提高訪問效率,峯值 TPS 達150萬+,輕鬆應對高併發的實時交易場景。

2)海量數據存儲訪問場景

企業客戶隨着業務的快速發展,業務數據增長迅猛,會產生超過單機數據庫存儲能力極限的數據,造成數據庫容量瓶頸,限制業務發展。 TDDL 可以線性擴展存儲空間,提供 PB 級存儲能力,可廣泛應用於工業製造、智能家居、車聯網等超大規模數據存儲訪問場景。

3)高性價比數據庫解決方案

初創型企業初期發展階段技術積累相對比較薄弱,資金投入有限,業務發展快,數據庫的穩定性風險高。TDDL 繼承了阿里巴巴多年的分佈式數據庫技術積累,能夠提供簡單易用的數據庫運維繫統,降低企業的技術運維成本,賦予企業強大的數據庫支撐能力。

當業務數據和訪問量增加到一定量時,如政務機構、大型企業、銀行等行業爲了支持大規模數據存儲和高併發數據庫訪問,傳統方案需要強依賴小型機和高端存儲等高成本的商業解決方案,以達到擴展服務能力的目的。TDDL 能夠利用普通服務器提供阿里巴巴雙十一同等處理能力的高性價比國產數據庫解決方案。

4)數據存儲平滑擴容

當應用單機存儲(MySQL)出現容量或性能瓶頸時,TDDL 提供在線數據擴容功能(該功能需要結合阿里其它內部中間件使用)。傳統數據庫容量擴展往往意味着服務中斷,很難做到業務無感知或者少感知。

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