阿里妹導讀:TDDL(Tabao Distributed Data Layer)是淘寶開源的一個用於訪問數據庫的中間件,集成了分庫分表,主備,讀寫分離,權重調配,動態數據庫配置等功能。本文以2007年TDDL初誕生時的視角,介紹TDDL是如何一步步設計成型的,希望能幫助同學們簡單收穫:常規數據庫效率問題解決思路、TDDL框架設計基本思路以及分佈式數據庫設計思路等。
文末福利:《MySQL實操》技術公開課。
時間倒轉穿越回2007年年底
一覺醒來,我還是照常去上班,走到西溪濕地附近,馬路沒有,高樓沒有,有的是小山坡和金色的稻田。一番打聽之後,才知道此時沒有什麼西溪園區。沒辦法,硬着頭皮去濱江上班,一刷卡,才發現我並不是我,我現在的身份是淘寶數據庫團隊的核心成員。
此時全國上下在迎接着奧運的到來,一片祥和。淘寶網成交額突破400億,日活用户達1000萬。工程師們都非常興奮,磨刀霍霍。但是也遇到了棘手的問題。
一 分析當前的現狀
1.1 現有業務背景
淘寶網給中國市場提供了全新的購物形式,在互聯網的大潮下,用户暴增,成交量暴增,公司持續飛速增長。
截止2007年,淘寶網成交額突破400億,日活用户達1000萬。
全天有1000萬用户訪問淘寶。而絕大多數用户都是在網上逛,什麼也不買。
1.2 當前的問題
1.2.1 用户體驗與反饋
用户普遍反饋逛淘寶卡頓,操作延遲特別明顯。
1.2.2 分析核心原因
大量的用户在瀏覽商品,並不下單。這個人數和場景的比例有20:1。
説明:數據庫模式事務,寫操作會對錶或者行加寫鎖,阻塞讀操作。
業務數據集中在一張表裏,如user表。一張表裏數據破幾千萬。查詢一條數據需要好幾秒(單表數據量太大)。
説明:一張表數據提升,必然會導致檢索變慢, 這是必然事實。不論如何加索引或者優化都無法解決的。
所有表集中在一個庫裏,所有庫集中在一個機器裏。數據庫集中在一台機器上,動不動就説硬盤不夠了(單機單庫)。
説明:所有業務共用一份物理機器資源。機器存在瓶頸:磁盤和CPU不夠用且後期拓展性不佳。
1.2.3 總結問題
20:1讀寫比例場景。
單表單庫數據量太大。
小型機與單機場景,抗不住當前規模。
當前現狀
二 我要做什麼?
如何滿足當前每天1000萬用户逛淘寶的需求,且用户體驗好(最基本要求:響應快)。
如何滿足未來有上億用户的訪問,甚至是同時訪問,且用户體驗好(最基本要求:響應快)。
高築牆,廣積糧,積極做好準備。
提煉核心:
提高數據庫操作速度。
同時能應對未來規模變化。
三 我能做什麼?
為實現以上兩大目標,我能做什麼?
3.1 提高數據庫操作速度,通用方法
提煉常見的通用方法:
sql優化
排除語法問題,爛sql
下推優化
下推的目的:提前過濾數據 -> 減少網絡傳輸、並行計算。
提前過濾數據
小表驅動大表等
建立索引
查詢頻率高的熱點字段
區分度高的(DISTINCT column_name)/COUNT(*),以主鍵為榜樣(1/COUNT(*))
長度小
儘量能覆蓋常用查詢字段
注意索引失效的場景
分庫分表
垂直分庫分表
水平分庫分表
讀寫分離
緩存的使用
等等。
3.2 如何應對未來的持續變化?
必須支持動態擴容。
必須走分佈式化路線,百分百不動搖。
3.3 結合定位,分析自己能做的
3.3.1 分析我們的架構定位
(1)大前提
我們要做通用型框架,不參與業務。
從軟件設計原則出發,開閉原則:對擴展開放,對修改關閉。
説明:大修改就意味着不穩定,因此:我們要做到儘可能少的修改原來的代碼。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。
(2)當前架構現狀
淘寶網主要使用hibernate/ibatis傳統框架:
初始框架
(3)分析我們的架構定位
淘寶數據庫團隊當時使用映射框架(hibernate/ibatis)作為數據庫交互入庫,為了不讓他們修改代碼,那我們只能在ibatis/hibenate這類映射框架之下。
同時jdbc是與底層數據庫交互的Java數據庫連接驅動程序,是基礎能力,我們要使用它,而不是改造它。
結論:我得把TDDL安插於ibatis/jdbc之間,於是有了第一張架構圖:
TDDL的定位
3.4 總結,我們能做什麼?
結合我們的目標,通用方法,大前提以及架構定位,分析下我們能做和不能做的。
不能做的:
索引,因為這個是設計階段,強業務相關。與大前提衝突:我們不參與業務。
能做的:
語法優化
排除sql問題
下推優化
分表分庫(自動水平分表,水平分庫)
讀寫分離(讀寫分離/分佈式化與動態擴容)
四 我們如何做?
4.1 語法優化
為達到語法優化的目的,我們需要具備什麼能力?
簡單來説:
我們需要認識這個別人提交給我的sql。
我能拆解sql。
優化與重組這個sql。
專業點來説:語義分析能力。
sql解析
sql規則制定
sql優化
sql重組
因此:我們需要設計一個sql解析器,sql優化器。
4.1.1 解析器
解析器的核心是詞法分析、語法語義分析,也就是説來了一條 select/update/insert/delete語句,你能認識它,而且你知道下一步該怎麼處理,同時為後面的優化器打下基礎。
核心:將sql解析為一棵語法樹。
例:
sql語法樹:
4.1.2 優化器
核心:
在sql解析成sql語法樹後,使用sql優化規則(1. 語法優化 2. 下推優化), 通過對樹進行左旋,右旋,刪除子樹來對語法樹進行重構sql語法樹。
將重構的語法樹進行遍歷得到優化後的sql。
(1)語法優化
函數提前計算
判斷永真/永假式
合併範圍
類型處理
(2)下推優化
Where條件下推
説明:提前條件過濾,提前獲取數據,減少後期計算/IO/網絡成本。
JOIN中非join列的條件下推
説明:提前過濾,減輕後期join計算成本,達到“小表驅動”的目的。
等值條件的推導
説明:同理,提前過濾。
4.1.3 總結
sql解析器
負責將sql語句化為sql語法樹。
sql優化器
負責將sql語法樹利用sql優化規則,重構sql語法樹。
將sql語法樹轉化為sql語句。
4.2 分表分庫
單庫單表的問題:
幾年前,業務簡單,應用的數據比較少,表結構也不復雜。只有一個數據庫,數據庫中的表是一張完整的表。而到了今天,2007年了,業務複雜起來了,數據量爆增,單表數據破千萬甚至上億條,一條DML語句,死慢死慢的。這種情況下加索引已不再有顯著的效果。
這個時候,數據庫效率瓶頸不是靠加索引,sql優化能搞定的。
正確出路:分表分庫,通過將表拆分,來降低單表數據量,進而提高數據庫操作效率。
分表分為:
垂直分表
水平分表
分庫分為:
垂直分庫
水平分庫
由於TDDL不參與業務,而垂直分庫分表是強業務相關的,因此TDDL暫不參與垂直分庫分表,只在水平分庫分表方向上努力。
4.2.1 垂直分表
垂直拆分是將一張表垂直拆成多個表。往往是把常用的列獨立成一張主表。不常用的列以及特別長的列拆分成另一張拓展表。
簡單垂直分表舉例
核心要素:
冷熱分離,把常用的列放在一個表,不常用的放在一個表。
大字段列獨立存放,如描述信息。
關聯關係的列緊密的放在一起。
它帶來的提升是:
為了避免IO爭搶並減少鎖表的幾率,查看詳情的用户與商品信息瀏覽互不影響。
充分發揮熱門數據的操作效率,商品信息的操作的高效率不會被商品描述的低效率所拖累。
4.2.2 水平分表
水平分表是在同一個數據庫內,把同一個表的數據按一定規則拆到多個表中。
簡單水平分表舉例
簡單點的技巧:按照枚舉類型區分。
作用總結:
庫內的水平分表,解決了單一表數據量過大的問題,分出來的小表中只包含一部分數據,從而使得單個表的數據量變小,提高檢索性能。
避免IO爭搶並減少鎖表的幾率。
4.2.3 垂直分庫
垂直分庫是指按照業務將表進行分類,分佈到不同的數據庫上面,每個庫可以放在不同的服務器上,它的核心理念是專庫專用。
垂直分庫
作用總結:
解決業務層面的耦合,業務清晰。
高併發場景下,垂直分庫一定程度的提升IO、數據庫連接數、降低單機硬件資源的瓶頸。
能對不同業務的數據進行分級管理、維護、監控、擴展等。
垂直分庫通過將表按業務分類,然後分佈在不同數據庫,並且可以將這些數據庫部署在不同服務器上,從而達到多個服務器共同分攤壓力的效果,但是依然沒有解決單表數據量過大的問題。
4.2.4 水平分庫(TDDL 核心)
水平分庫是把同一個表的數據按一定規則拆到不同的數據庫中,每個庫可以放在不同的服務器上。
水平分庫
作用總結:
解決了單庫單表數據量過大的問題,理論上解決了高併發的性能瓶頸。
水平分庫核心要解決的問題:
如何知道數據在哪個庫裏?- 路由問題
結果合併
全局唯一主鍵ID
分佈式事務(暫時不支持)
4.2.5 水平分庫——問題解決
(1)自動路由算法
sql轉發:在水平拆分後,數據被分散到多張表裏。原來的一個sql需要拆解,進行轉發路由。
例:
拆分表的數據訪問——SQL轉發
其中拆分和尋找的算法:怎麼知道對應哪個表?即自動路由算法。常見的有:固定哈希算法和一致性哈希算法。
a)固定哈希算法
b)一致性哈希算法
一致性哈希算法在1997年由麻省理工學院提出,是一種特殊的哈希算法,目的是解決分佈式緩存的問題。
一致性哈希算法的優勢:
極好的應對了服務器宕機的場景。
很好的支持後期服務器擴容。
在引入虛擬節點後:能很好的平衡各節點的數據分佈。
由於一致性哈希算法的優勢,此算法幾乎是所有分佈式場景下使用的方案,包括mysql的分佈式、redis的分佈式等。
(2) 結果合併
昇華:引入fork-Join,提升操作速度(多線程併發重點場景,代碼中也很常用哦)。
任務拆分
多路並行操作
結果合併
(3)全局唯一主鍵
優勢:簡單高效。
缺點:無法保證自增順序。
例:
表1新增一條數據,於是給表1分配1000個主鍵ID, 直到它用完。
同理,表2、表3在新增數據時,也給它們分配1000個主鍵ID。直到它用完。
當它們的1000個主鍵ID用完後,繼續給它們分配1000個即可。
重複下去,可保證各庫表上的主鍵不重疊,唯一。
這種產生全局唯一id的方式相當有效,保證基本的全局唯一特性和高性能的同時,可以對生成id的數據庫分機架分機房部署達到容災的目的。
4.2.6 分表分庫總結
架構師角度:
優先考慮緩存降低對數據庫的讀操作。
再考慮讀寫分離,降低數據庫寫操作。
最後開始數據拆分,切分模式:首先垂直(縱向)拆分、再次水平拆分。
首先考慮按照業務垂直拆分。
再考慮水平拆分:先分庫(設置數據路由規則,把數據分配到不同的庫中)。
最後再考慮分表,單表拆分到數據1000萬以內。
個人開發角度:
優先使用分表分庫框架(直接使用)。
優先考慮緩存降低對數據庫的讀操作。
自己垂直分表。
自己水平分表。
之所以先垂直拆分才水平拆分,是因為垂直拆分後數據業務清晰而且單一,更加方便指定水平的標準。
4.3 分佈式化
分佈式化是大潮,是大規模服務器最後都要走的一步。
分佈式數據庫架構演變
4.3.1 讀寫分離
設計讀寫分離的數據庫,有兩大意義:
主從只負責各自的寫和讀,極大程度的緩解X鎖和S鎖的競爭。
從庫可配置myisam引擎,提升查詢性能以及節約系統開銷。
説明:myisam查詢效率高於默認的innodb效率。參考:myisam和innodb的區別。
核心問題:
數據的備份同步問題:參考4.4.3。
讀寫比例支持動態設置:結合業務,如淘寶可設置為20:1。
4.3.2 容災
主備倒換:提高可靠性 > 應對個別數據庫宕機場景,尤其主庫宕機。
主備倒換
説明:DB2主庫宕機後,自動將主庫轉為DB3。
核心問題:
數據的備份同步問題:binlog 參考4.4.3。
檢測數據庫的在線狀態:心跳機制。
4.3.3 數據備份與同步
當只有單機或者一份數據時,一但數據庫出問題,那麼整體服務將不可用,而且更嚴重的是會造成數據損害丟失不可逆。
在讀寫分離與主備倒換的場景下,核心要解決的是多個數據庫的數據同步與備份問題。
當前主流的是採用日誌備份方式(redis也類似)。
實現原理:binlog日誌備份。
數據備份:bin-log同步
説明:
主庫負責寫操作,在數據變更時,會寫入binlog,同時通知各從庫。
從庫收到通知後,IO線程會主動過來讀取主庫的binlog,並寫入自己的log。
寫完從庫log後,通知sql線程,sql線程讀取自己的日誌,寫入從庫。
4.3.4 動態擴容
動態擴容的意義在於:隨着後期業務量增大,數據庫個數可以通過增多的方式來應對,而相對的改造代價很小。
擴容前:
擴容後:
核心內容:
在添加新庫時
註冊機器與庫
路由算法調整:固定哈希算法-調整模數/一致性哈希算法天然支持擴容
可選的權重調整
修改權重,數據插入偏向於新庫5。
在各庫數量平衡時,觸發修改回原來平衡的權重,以保證後續的均衡分配。
五 架構成型
sql流向
下圖介紹sql從流入TDD到流入數據庫,期間TDDL各模塊對Sql的處理。
架構圖
下圖介紹了TDDL三層的位置以及作用。
核心能力圖
TDDL 核心能力,核心組建示意圖,其中標出了各模塊核心要解決功能,核心算法等。
參考
TDDL 官方文檔
https://mw.alibaba-inc.com/products/tddl/_book/
TDD產品原理介紹
https://gitlab.alibaba-inc.com/middleware/tddl5-wiki/raw/master/docs/Tddl_Intro.ppt
TDDL(07-10年)初始版本介紹
https://wenku.baidu.com/view/9cb630ab7f1922791788e825.html
阿里雲SQL調優指南
https://help.aliyun.com/document_detail/144293.html
一致性哈希算法原理
https://www.cnblogs.com/lpfuture/p/5796398.html
TDDL初期源碼(碼雲)
https://gitee.com/justwe9891/TDDL
MyISAM與InnoDB 的區別(9個不同點)
MySQL實操
RDS MySQL版基於阿里雲分佈式文件系統和SSD盤高性能存儲,提供了容災、備份、恢復、監控、遷移等全套解決方案。本課程共36課時,講解MySQL基於阿里云云服務器ECS的安裝、配置,通過學習與實操,掌握MySQL的工作原理、使用技巧以及優化,並具備雲端使用MySQL的能力。