作為資料庫核心成員,如何讓淘寶不卡頓?

阿里技術發表於2020-09-25

作為資料庫核心成員,如何讓淘寶不卡頓?

阿里妹導讀:TDDL(Tabao Distributed Data Layer)是淘寶開源的一個用於訪問資料庫的中介軟體,整合了分庫分表,主備,讀寫分離,權重調配,動態資料庫配置等功能。本文以2007年TDDL初誕生時的視角,介紹TDDL是如何一步步設計成型的,希望能幫助同學們簡單收穫:常規資料庫效率問題解決思路、TDDL框架設計基本思路以及分散式資料庫設計思路等。


時間倒轉穿越回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讀寫比例場景。

  • 單表單庫資料量太大。

  • 小型機與單機場景,抗不住當前規模。

  • 截止2007年,淘寶網成交額突破400億,日活使用者達1000萬。

  • 全天有1000萬使用者訪問淘寶。而絕大多數使用者都是在網上逛,什麼也不買。

1.2  當前的問題

1.2.1  使用者體驗與反饋

使用者普遍反饋逛淘寶卡頓,操作延遲特別明顯。

1.2.2  分析核心原因
  • 大量的使用者在瀏覽商品,並不下單。這個人數和場景的比例有20:1。

  • 說明:資料庫模式事務,寫操作會對錶或者行加寫鎖,阻塞讀操作。

  • 業務資料集中在一張表裡,如user表。一張表裡資料破幾千萬。查詢一條資料需要好幾秒(單表資料量太大)。

  • 說明:一張表資料提升,必然會導致檢索變慢, 這是必然事實。不論如何加索引或者最佳化都無法解決的。

  • 所有表集中在一個庫裡,所有庫集中在一個機器裡。資料庫集中在一臺機器上,動不動就說硬碟不夠了(單機單庫)。

  • 說明:所有業務共用一份物理機器資源。機器存在瓶頸:磁碟和CPU不夠用且後期擴充性不佳。

1.2.3  總結問題
  • 20:1讀寫比例場景。

  • 單表單庫資料量太大。

  • 小型機與單機場景,抗不住當前規模。

  • 截止2007年,淘寶網成交額突破400億,日活使用者達1000萬。

  • 全天有1000萬使用者訪問淘寶。而絕大多數使用者都是在網上逛,什麼也不買。

1.2  當前的問題

1.2.1  使用者體驗與反饋

使用者普遍反饋逛淘寶卡頓,操作延遲特別明顯。

1.2.2  分析核心原因
  • 大量的使用者在瀏覽商品,並不下單。這個人數和場景的比例有20:1。

  • 說明:資料庫模式事務,寫操作會對錶或者行加寫鎖,阻塞讀操作。

  • 業務資料集中在一張表裡,如user表。一張表裡資料破幾千萬。查詢一條資料需要好幾秒(單表資料量太大)。

  • 說明:一張表資料提升,必然會導致檢索變慢, 這是必然事實。不論如何加索引或者最佳化都無法解決的。

  • 所有表集中在一個庫裡,所有庫集中在一個機器裡。資料庫集中在一臺機器上,動不動就說硬碟不夠了(單機單庫)。

  • 說明:所有業務共用一份物理機器資源。機器存在瓶頸:磁碟和CPU不夠用且後期擴充性不佳。

1.2.3  總結問題
  • 20:1讀寫比例場景。

  • 單表單庫資料量太大。

  • 小型機與單機場景,抗不住當前規模。

作為資料庫核心成員,如何讓淘寶不卡頓?
當前現狀

二  我要做什麼?
  • 如何滿足當前每天1000萬使用者逛淘寶的需求,且使用者體驗好(最基本要求:響應快)。

  • 如何滿足未來有上億使用者的訪問,甚至是同時訪問,且使用者體驗好(最基本要求:響應快)。

高築牆,廣積糧,積極做好準備。

提煉核心:

  1. 提高資料庫操作速度。

  2. 同時能應對未來規模變化。

三  我能做什麼?

為實現以上兩大目標,我能做什麼?

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解析為一棵語法樹。

例:
SELECT id, member_id FROM wp_image WHERE member_id = ‘123’
作為資料庫核心成員,如何讓淘寶不卡頓?
sql語法樹:
作為資料庫核心成員,如何讓淘寶不卡頓?
4.1.2  最佳化器

核心:
  • 在sql解析成sql語法樹後,使用sql最佳化規則(1. 語法最佳化 2. 下推最佳化), 透過對樹進行左旋,右旋,刪除子樹來對語法樹進行重構sql語法樹。

  • 將重構的語法樹進行遍歷得到最佳化後的sql。


(1)語法最佳化
  • 函式提前計算

a. id = 1 + 1  => id = 2
  • 判斷永真/永假式

1 = 1 and id = 1 => id = 10 = 1 and id = 1 => 空結果

  • 合併範圍

id > 1 or id < 5 => 永真式id > 1 and id = 3 => id = 3

  • 型別處理

id = ‘1’  => id為數字型別,自動Long.valueof(1)create=‘2015-02-14 12:12:12’ => create為timestamp型別,解析為時間型別

(2)下推最佳化

  • Where條件下推

select from (A) o where o.id = 1=>select from (A.query(id = 1))

說明:提前條件過濾,提前獲取資料,減少後期計算/IO/網路成本。
  • JOIN中非join列的條件下推

A join B on A.id = B.id where A.name = 1 and B.title = 2=> A.query(name = 1) join B.query(title = 2) on A.id = B.id

說明:提前過濾,減輕後期join計算成本,達到“小表驅動”的目的。

  • 等值條件的推導

A join B on A.id = B.id where A.id = 1 => B.id = 1=> A join B.query(B.id=1) on A.id = B.id
說明:同理,提前過濾。

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需要拆解,進行轉發路由。

例:
select * from tb1 where member_id in ('test1234', 'pavaretti17', 'abcd');=>select * from tb1 where member_id in ('test1234', 'pavaretti17', 'abcd');select * from tb1 where member_id in ('abcd');
作為資料庫核心成員,如何讓淘寶不卡頓?
拆分表的資料訪問——SQL轉發

其中拆分和尋找的演算法:怎麼知道對應哪個表?即自動路由演算法。常見的有:固定雜湊演算法和一致性雜湊演算法。

a)固定雜湊演算法

作為資料庫核心成員,如何讓淘寶不卡頓?

b)一致性雜湊演算法

 一致性雜湊演算法在1997年由麻省理工學院提出,是一種特殊的雜湊演算法,目的是解決分散式快取的問題。

一致性雜湊演算法的優勢:
  • 極好的應對了伺服器當機的場景。

  • 很好的支援後期伺服器擴容。

  • 在引入虛擬節點後:能很好的平衡各節點的資料分佈。

由於一致性雜湊演算法的優勢,此演算法幾乎是所有分散式場景下使用的方案,包括mysql的分散式、redis的分散式等。
作為資料庫核心成員,如何讓淘寶不卡頓?
(2) 結果合併
作為資料庫核心成員,如何讓淘寶不卡頓?
昇華:引入fork-Join,提升操作速度(多執行緒併發重點場景,程式碼中也很常用哦)。
  • 任務拆分

  • 多路並行操作

  • 結果合併

作為資料庫核心成員,如何讓淘寶不卡頓?
(3)全域性唯一主鍵

演算法:基於資料庫更新+記憶體分配。在資料庫中維護一個ID,獲取下一個ID時,會對資料庫進行ID=ID+100 WHERE ID=XX,拿到100個ID後,在記憶體中進行分配。
  • 優勢:簡單高效。

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

例:
水平分庫分表:一拆三場景。主鍵分隔值:1000。
  • 表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 官方文件
http://mw.alibaba-inc.com/products/tddl/_book/

TDD產品原理介紹

http://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個不同點) 

https://blog.csdn.net/qq_35642036/article/details/82820178

相關文章