做爲數據庫核心成員,如何讓淘寶不卡頓?

簡介: TDDL(Tabao Distributed Data Layer)是淘寶開源的一個用於訪問數據庫的中間件,集成了分庫分表,主備,讀寫分離,權重調配,動態數據庫配置等功能。本文以2007年TDDL初誕生時的視角,介紹TDDL是如何一步步設計成型的,但願能幫助同窗們簡單收穫:常規數據庫效率問題解決思路、TDDL框架設計基本思路以及分佈式數據庫設計思路等。html

image.png

時間倒轉穿越回2007年年末

一覺醒來,我仍是照常去上班,走到西溪溼地附近,馬路沒有,高樓沒有,有的是小山坡和金色的稻田。一番打聽以後,才知道此時沒有什麼西溪園區。沒辦法,硬着頭皮去濱江上班,一刷卡,才發現我並非我,我如今的身份是淘寶數據庫團隊的核心成員。mysql

此時全國上下在迎接着奧運的到來,一片祥和。淘寶網成交額突破400億,日活用戶達1000萬。工程師們都很是興奮,磨刀霍霍。可是也遇到了棘手的問題。git

一 分析當前的現狀

1.1 現有業務背景

  • 淘寶網給中國市場提供了全新的購物形式,在互聯網的大潮下,用戶暴增,成交量暴增,公司持續飛速增加。
  • 截止2007年,淘寶網成交額突破400億,日活用戶達1000萬。
  • 全天有1000萬用戶訪問淘寶。而絕大多數用戶都是在網上逛,什麼也不買。

1.2 當前的問題

1.2.1 用戶體驗與反饋redis

用戶廣泛反饋逛淘寶卡頓,操做延遲特別明顯。算法

1.2.2 分析核心緣由sql

  • 大量的用戶在瀏覽商品,並不下單。這我的數和場景的比例有20:1。
    • 說明:數據庫模式事務,寫操做會對錶或者行加寫鎖,阻塞讀操做。
  • 業務數據集中在一張表裏,如user表。一張表裏數據破幾千萬。查詢一條數據須要好幾秒(單表數據量太大)。
    • 說明:一張表數據提高,必然會致使檢索變慢, 這是必然事實。不論如何加索引或者優化都沒法解決的。
  • 全部表集中在一個庫裏,全部庫集中在一個機器裏。數據庫集中在一臺機器上,動不動就說硬盤不夠了(單機單庫)。
    • 說明:全部業務共用一份物理機器資源。機器存在瓶頸:磁盤和CPU不夠用且後期拓展性不佳。

1.2.3 總結問題數據庫

  • 20:1讀寫比例場景。
  • 單表單庫數據量太大。
  • 小型機與單機場景,抗不住當前規模。

image.png

二 我要作什麼?

  • 如何知足當前天天1000萬用戶逛淘寶的需求,且用戶體驗好(最基本要求:響應快)。
  • 如何知足將來有上億用戶的訪問,甚至是同時訪問,且用戶體驗好(最基本要求:響應快)。

高築牆,廣積糧,積極作好準備。緩存

提煉核心:服務器

  • 提升數據庫操做速度。
  • 同時能應對將來規模變化。

三 我能作什麼?

爲實現以上兩大目標,我能作什麼?網絡

3.1 提升數據庫操做速度,通用方法

提煉常見的通用方法:

sql優化

  • 排除語法問題,爛sql
  • 下推優化

下推的目的:提早過濾數據 -> 減小網絡傳輸、並行計算。

  • 提早過濾數據
  • 小表驅動大表等

創建索引

  • 查詢頻率高的熱點字段
  • 區分度高的(DISTINCT column_name)/COUNT(_),以主鍵爲榜樣(1/COUNT(_))
  • 長度小
  • 儘可能能覆蓋經常使用查詢字段
  • 注意索引失效的場景

分庫分表

  • 垂直分庫分表
  • 水平分庫分表

讀寫分離

緩存的使用

等等。

3.2 如何應對將來的持續變化?

必須支持動態擴容。

必須走分佈式化路線,百分百不動搖。

3.3 結合定位,分析本身能作的

3.3.1 分析咱們的架構定位

(1)大前提

  • 咱們要作通用型框架,不參與業務。
  • 從軟件設計原則出發,開閉原則:對擴展開放,對修改關閉。

說明:大修改就意味着不穩定,所以:咱們要作到儘量少的修改原來的代碼。在程序須要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。

(2)當前架構現狀

淘寶網主要使用hibernate/ibatis傳統框架:

image.png

(3)分析咱們的架構定位

淘寶數據庫團隊當時使用映射框架(hibernate/ibatis)做爲數據庫交互入庫,爲了避免讓他們修改代碼,那咱們只能在ibatis/hibenate這類映射框架之下。

同時jdbc是與底層數據庫交互的Java數據庫鏈接驅動程序,是基礎能力,咱們要使用它,而不是改造它。

結論:我得把TDDL安插於ibatis/jdbc之間,因而有了第一張架構圖:

image.png

3.4 總結,咱們能作什麼?

image.png

結合咱們的目標,通用方法,大前提以及架構定位,分析下咱們能作和不能作的。

不能作的:

  • 索引,由於這個是設計階段,強業務相關。與大前提衝突:咱們不參與業務。

能作的:

  • 語法優化
    • 排除sql問題
    • 下推優化
  • 分表分庫(自動水平分表,水平分庫)
    • 讀寫分離(讀寫分離/分佈式化與動態擴容)

四 咱們如何作?

4.1 語法優化

爲達到語法優化的目的,咱們須要具有什麼能力?

簡單來講:

  • 咱們須要認識這個別人提交給個人sql。
  • 我能拆解sql。
  • 優化與重組這個sql。

image.png

專業點來講:語義分析能力。

  • sql解析
  • sql規則制定
  • sql優化
  • sql重組

image.png

所以:咱們須要設計一個sql解析器,sql優化器。

4.1.1 解析器

解析器的核心是詞法分析、語法語義分析,也就是說來了一條 select/update/insert/delete語句,你能認識它,並且你知道下一步該怎麼處理,同時爲後面的優化器打下基礎。

核心:將sql解析爲一棵語法樹。

例:

SELECT id, member_id FROM wp_image WHERE member_id = ‘123’

image.png

sql語法樹:

image.png

4.1.2 優化器

核心:

  • 在sql解析成sql語法樹後,使用sql優化規則(1. 語法優化 2. 下推優化), 經過對樹進行左旋,右旋,刪除子樹來對語法樹進行重構sql語法樹。
  • 將重構的語法樹進行遍歷獲得優化後的sql。

(1)語法優化

  • 函數提早計算
a. id = 1 + 1  => id = 2
  • 判斷永真/永假式
1 = 1 and id = 1 => id = 1
0 = 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語句。

image.png

4.2 分表分庫

單庫單表的問題:

幾年前,業務簡單,應用的數據比較少,表結構也不復雜。只有一個數據庫,數據庫中的表是一張完整的表。而到了今天,2007年了,業務複雜起來了,數據量爆增,單表數據破千萬甚至上億條,一條DML語句,死慢死慢的。這種狀況下加索引已再也不有顯著的效果。

這個時候,數據庫效率瓶頸不是靠加索引,sql優化能搞定的。

正確出路:分表分庫,經過將表拆分,來下降單表數據量,進而提升數據庫操做效率。

分表分爲:

  • 垂直分表
  • 水平分表

分庫分爲:

  • 垂直分庫
  • 水平分庫

因爲TDDL不參與業務,而垂直分庫分表是強業務相關的,所以TDDL暫不參與垂直分庫分表,只在水平分庫分表方向上努力。

4.2.1 垂直分表

垂直拆分是將一張表垂直拆成多個表。每每是把經常使用的列獨立成一張主表。不經常使用的列以及特別長的列拆分紅另外一張拓展表。
image.png

簡單垂直分表舉例
核心要素:

  • 冷熱分離,把經常使用的列放在一個表,不經常使用的放在一個表。
  • 大字段列獨立存放,如描述信息。
  • 關聯關係的列緊密的放在一塊兒。

它帶來的提高是:

  • 爲了不IO爭搶並減小鎖表的概率,查看詳情的用戶與商品信息瀏覽互不影響。
  • 充分發揮熱門數據的操做效率,商品信息的操做的高效率不會被商品描述的低效率所拖累。

4.2.2 水平分表

水平分表是在同一個數據庫內,把同一個表的數據按必定規則拆到多個表中。

image.png

簡單點的技巧:按照枚舉類型區分。

做用總結:

  • 庫內的水平分表,解決了單一表數據量過大的問題,分出來的小表中只包含一部分數據,從而使得單個表的數據量變小,提升檢索性能。
  • 避免IO爭搶並減小鎖表的概率。

4.2.3 垂直分庫

垂直分庫是指按照業務將表進行分類,分佈到不一樣的數據庫上面,每一個庫能夠放在不一樣的服務器上,它的核心理念是專庫專用。
image.png

做用總結:

  • 解決業務層面的耦合,業務清晰。
  • 高併發場景下,垂直分庫必定程度的提高IO、數據庫鏈接數、下降單機硬件資源的瓶頸。
  • 能對不一樣業務的數據進行分級管理、維護、監控、擴展等。
  • 垂直分庫經過將表按業務分類,而後分佈在不一樣數據庫,而且能夠將這些數據庫部署在不一樣服務器上,從而達到多個服務器共同分攤壓力的效果,可是依然沒有解決單表數據量過大的問題。

4.2.4 水平分庫(TDDL 核心)

水平分庫是把同一個表的數據按必定規則拆到不一樣的數據庫中,每一個庫能夠放在不一樣的服務器上。

image.png

做用總結:

  • 解決了單庫單表數據量過大的問題,理論上解決了高併發的性能瓶頸。

水平分庫核心要解決的問題:

  • 如何知道數據在哪一個庫裏?- 路由問題
  • 結果合併
  • 全局惟一主鍵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');

image.png

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

a)固定哈希算法

image.png

b)一致性哈希算法

一致性哈希算法在1997年由麻省理工學院提出,是一種特殊的哈希算法,目的是解決分佈式緩存的問題。

一致性哈希算法的優點:

  • 極好的應對了服務器宕機的場景。
  • 很好的支持後期服務器擴容。
  • 在引入虛擬節點後:能很好的平衡各節點的數據分佈。

因爲一致性哈希算法的優點,此算法幾乎是全部分佈式場景下使用的方案,包括mysql的分佈式、redis的分佈式等。

image.png

(2) 結果合併

image.png

昇華:引入fork-Join,提高操做速度(多線程併發重點場景,代碼中也很經常使用哦)。

  • 任務拆分
  • 多路並行操做
  • 結果合併

image.png

(3)全局惟一主鍵

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

  • 優點:簡單高效。
  • 缺點:沒法保證自增順序。

例:

水平分庫分表:一拆三場景。
主鍵分隔值:1000。
  • 表1新增一條數據,因而給表1分配1000個主鍵ID, 直到它用完。
  • 同理,表二、表3在新增數據時,也給它們分配1000個主鍵ID。直到它用完。
  • 當它們的1000個主鍵ID用完後,繼續給它們分配1000個便可。
  • 重複下去,可保證各庫表上的主鍵不重疊,惟一。

image.png

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

4.2.6 分表分庫總結

image.png

架構師角度:

  • 優先考慮緩存下降對數據庫的讀操做。
  • 再考慮讀寫分離,下降數據庫寫操做。
  • 最後開始數據拆分,切分模式:首先垂直(縱向)拆分、再次水平拆分。
    • 首先考慮按照業務垂直拆分。
    • 再考慮水平拆分:先分庫(設置數據路由規則,把數據分配到不一樣的庫中)。
  • 最後再考慮分表,單表拆分到數據1000萬之內。

我的開發角度:

  • 優先使用分表分庫框架(直接使用)。
  • 優先考慮緩存下降對數據庫的讀操做。
  • 本身垂直分表。
  • 本身水平分表。

之因此先垂直拆分才水平拆分,是由於垂直拆分後數據業務清晰並且單一,更加方便指定水平的標準。

4.3 分佈式化

分佈式化是大潮,是大規模服務器最後都要走的一步。
image.png

4.3.1 讀寫分離

設計讀寫分離的數據庫,有兩大意義:

  • 主從只負責各自的寫和讀,極大程度的緩解X鎖和S鎖的競爭。
  • 從庫可配置myisam引擎,提高查詢性能以及節約系統開銷。

說明:myisam查詢效率高於默認的innodb效率。參考:myisam和innodb的區別。

image.png

核心問題:

  • 數據的備份同步問題:參考4.4.3。
  • 讀寫比例支持動態設置:結合業務,如淘寶可設置爲20:1。

4.3.2 容災

主備倒換:提升可靠性 > 應對個別數據庫宕機場景,尤爲主庫宕機。
image.png

說明:DB2主庫宕機後,自動將主庫轉爲DB3。

核心問題:

  • 數據的備份同步問題:binlog 參考4.4.3。
  • 檢測數據庫的在線狀態:心跳機制。

4.3.3 數據備份與同步

當只有單機或者一份數據時,一但數據庫出問題,那麼總體服務將不可用,並且更嚴重的是會形成數據損害丟失不可逆。

在讀寫分離與主備倒換的場景下,核心要解決的是多個數據庫的數據同步與備份問題。

當前主流的是採用日誌備份方式(redis也相似)。

實現原理:binlog日誌備份。

image.png

說明:

  • 主庫負責寫操做,在數據變動時,會寫入binlog,同時通知各從庫。
  • 從庫收到通知後,IO線程會主動過來讀取主庫的binlog,並寫入本身的log。
  • 寫完從庫log後,通知sql線程,sql線程讀取本身的日誌,寫入從庫。

4.3.4 動態擴容

動態擴容的意義在於:隨着後期業務量增大,數據庫個數能夠經過增多的方式來應對,而相對的改造代價很小。

擴容前:

image.png

擴容後:
image.png

核心內容:

  • 在添加新庫時
    • 註冊機器與庫
    • 路由算法調整:固定哈希算法-調整模數/一致性哈希算法自然支持擴容
  • 可選的權重調整
    • 修改權重,數據插入偏向於新庫5。
    • 在各庫數量平衡時,觸發修改回原來平衡的權重,以保證後續的均衡分配。

五 架構成型

sql流向

下圖介紹sql從流入TDD到流入數據庫,期間TDDL各模塊對Sql的處理。

image.png

架構圖

image.png

下圖介紹了TDDL三層的位置以及做用。
image.png

核心能力圖

TDDL 核心能力,核心組建示意圖,其中標出了各模塊核心要解決功能,核心算法等。

image.png

參考

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
一致性哈希算法原理
http://www.javashuo.com/article/p-tzgogqcu-cr.html
TDDL初期源碼(碼雲)
https://gitee.com/justwe9891/TDDL
MyISAM與InnoDB 的區別(9個不一樣點)
http://www.javashuo.com/article/p-ggggexrz-dx.html

相關文章
相關標籤/搜索