B站萬億級數據庫選型與架構設計實踐
一、業務場景
在開始講解之前,我先為大家介紹一下B站的業務場景。B站的業務大體上可以分為以下幾類:
1、點播類業務
點播類業務就是大家經??吹囊曨l以及稿件之類相關的業務,這類數據使用場景的特點有:
- 數據一致性要求較高
- 耗時敏感
- 流量大
- 可用性要求高
2、直播類業務
直播類業務對應B站的S12、跨晚、拜年祭等,有以下幾個特點:
- 數據一致性要求較高
- 熱點數據,如S12的主播房間
- 平時流量中等,大型直播流量會呈現爆炸性增長
- 可用性要求高
3、游戲類業務
- 數據一致性要求較高
- 耗時敏感
- 流量大
- 可用性要求高
4、電商類業務
如B站本身的會員購,這類業務的要求如下:
- 數據一致性要求較高
- 熱點數據,集中在秒殺場景及熱門番劇
- 平時流量中等,熱門番劇及商品會呈現爆炸性增長
- 可用性要求高
5、支付類業務
- 數據一致性要求極高
- 可用性要求高
- 流量不大
二、架構演進
介紹完B站的業務場景之后,接下來是B站整體數據庫的架構演進歷史。
1、1.0階段
1.0階段對于所有互聯網公司而言,其實都有類似的架構——簡單的主從,所有流量集中在一個主庫上。另外,與以前使用的商業數據庫類似場景——單實例多庫。這種架構在公司剛起步的時候是比較方便的,便于業務的快速迭代,但是隨著流量的增長,會出現以下幾個問題:
1)單機的性能瓶頸
服務器的CPU、內存、存儲的限制我們不可能一直垂直升級,從而出現了我們第一個架構演進的小版本——讀寫分離和一主多從,此場景有兩個核心要求:
- 數據一致性要求較低
- 數據敏感度要求較低
滿足以上兩個要求的場景可以很好地規避因MySQL主從復制存在的延遲所帶來的問題,同時又可以滿足業務快速增長帶來的流量壓力。
2)各業務互相影響
隨著業務的發展,各個業務之間的互相影響推動了我們架構的第二個小版本出現——按照業務庫進行遷移拆分。
基于讀寫分離和業務庫維度的拆分還是無法避免各個功能模塊的互相影響。在這種情況下,架構1.0階段的第三個小版本應運而生——基于業務的功能維度進行拆分,將一個X庫拆分為n個庫,拆分完之后分布在不同的實例。在每個不同的實例下,我們會有不同數量的從庫支撐業務的流量增長,以滿足大部分場景的業務需求?,F在B站也有很多業務采用類似的架構,通過進行垂直業務拆分滿足我們的業務增長。
2、2.0階段
架構2.0階段——水平拆分。成熟、穩定、定制的Proxy是水平拆分的利器,而一個符合要求的Proxy是需要時間進行打磨。為滿足業務的快速發展,我們選擇在業務層實現,也就是我們在代碼層實現路由,雖然配置時會比較繁瑣,但能夠滿足大部分業務場景,很多互聯網公司也有類似的階段。在業務側進行水平拆分之后,我們其實面臨著一個新的問題——跨實例查詢。
3、3.0階段
1)第一個階段
進入B站演進的3.0階段,我們引入了TiDB,將之前業務層面的分片數據通過TiDB本身的DTS同步到TiDB集群,從而滿足了大部分業務的查詢需求。同時我們在部分場景的業務下直接嘗試使用TiDB。
引入TiDB之后,基于B站的特點,我們對其進行了本地化定制。由于TiDB Server是無狀態的,而官方對于如何路由到每個節點也沒有一個通用的解決方案。因此我們結合 B站的基礎平臺能力,將TiDB Server全部在PaaS上進行容器化,同時把我們的服務發現能力和TiDB Server進行整合,并對相應語言的SDK進行改造,從而實現了TiDB Server的負載均衡,解決了TiDB Server本身的瓶頸,如:故障切換、業務快速感知節點變化、連接數等。
2)第二個階段
到了3.0的第二個階段,我們已經把Proxy打磨為一個很成熟的產品,同時為滿足支撐了異地多活的場景,我們還定制了DTS,把我們數據庫的部署從同城多活直接做到了異地多活,也就是兩地三中心的架構。
首先,在DTS方面,我們也基于B站本身技術棧的特點做了大量的定制,與其它公司開源的組件有部分不同。例如沖突檢測,我們提供了多種可選擇的規則,包括基于特定字段的以及全字段匹配的,同時對于沖突字段數據的R數據處理,我們一般會有兩種途徑:
- 一是直接沖突的場景,將不重要的數據直接打入到我們的隊列里,也就是我們公司之前的Data Bus里;
- 二是業務方可以基于打到Data Bus里的數據做沖突數據處理,通過DTS提供一個接口將數據回寫到特定的機房,因為當需要把數據重新寫入數據庫時,也是需要做防回環的處理,所以我們DTS上提供一個接口供業務使用。
其次,在主從切換的時候,由于兩地三中心要保證數據可以進行來回切換,切換期間雖然是全局進行,但是一些邊緣場景下仍然會存在數據沖突的問題。所以我們也提供了一個在主從切換下數據沖突以及相關信息的打撈隊列,實現二次處理的功能,這也是我們中間 DTS提供的一個能力。
Proxy的能力與各家主流的功能是類似的,都能夠支持讀寫分離、分庫分表、限流、黑白名單等。對于Proxy的部署,我們采取了兩種方案:
- 一是集中式部署,類似于大家常說的網關模式,能夠便捷地進行統一的限流及資源的調控;
- 二是Sidecar模式,應用層在使用方面比較簡單,直接配置本地IP即可,但是同時已帶來其他問題,如全局的管控(限流、連接等)、成本等。
三、架構設計
接下來為大家介紹的是B站對于不同數據量的場景的架構設計理念。
1、大型直播活動
整體概括起來有以下四種類型:
1)高并發寫入
高并發寫入考驗的是主庫的寫入能力和從庫的復制能力。
2)高并發查詢
高并發查詢一般都會引入緩存的能力,緩存主要涉及以下幾種:
- 分布式緩存主要解決容量的問題;
- Local Cache在應用層提供能力,在應用本地可以緩存部分數據,但是數據可能存在不及時的問題;
- 多級緩存能夠緩解爆炸性增長的流量帶來的壓力。
3)實時排序
實時排序最直觀的場景就是觀眾在直播間刷禮物的時候展示出來的名次,為保證時效性以及順序,我們一般會采用Redis有序集合。
4)預期外突發流量
預期外突發流量對于我們而言考驗的主要是應用層的快速擴容以及如何對流量進行削峰,同時保證數據庫比較平穩地寫入,也就是異步寫入的場景。今年最明顯的預期外突發流量場景是佩洛西事件,比我們平常的流量大了將近5倍。
2、電商大促
整體上歸納下來有以下幾個特點:
1)秒殺場景
秒殺場景主要涉及合適的選型和請求最簡化?;诠镜幕ㄟM行定制,才可以實現最好的性能和體驗。
2)訂單
訂單有很明顯的冷熱數據特征。一般情況下,我們的訂單會被進行一年前、兩年前以及實時訂單的不同拆分。這塊對于數據庫而言考驗的是數據的歸檔及查詢能力。
3)庫存
庫存與秒殺場景存在一定關聯,但并不是完全相關。秒殺場景會涉及到庫存,但是庫存在平常也會一直使用,因此兩者不能進行強掛鉤。
庫存場景主要在于保證減庫存的準確性,以及減少用戶端在訪問時可能存在的沖突,另外是一致性的問題,也就是在秒殺和減庫存時不能出現超賣的情況,避免對商家造成虧本。
4)流量削峰
流量削峰與大型直播賽事遇到的突發流量是不同的,因為這一部分流量是我們已知的,已經預估好會有多少流量,因此我們一般會進行隊列處理以及做分層。
前面介紹了大型直播賽事和電商大促兩個典型場景,我們做了一部分數據庫架構設計以及與應用端的聯動。下面介紹我們真正進行數據庫架構設計時,需要考慮哪些關鍵點。
3、數據
首先需要考慮數據,按照我們數據類型的使用場景,我們可以將數據分為以下三種:
1)配置型
配置型類似于我們的數據字典以及一些權限配置,特點包括:
- 量小
- 幾乎無事務依賴
- 讀多寫少
如果需要對配置型數據進行高并發訪問,只需要加緩存即可,不需要做過多處理。
2)日志型
日志型數據包括交易流水、訂單狀態等,我認為日志型數據也可以稱為流水型數據,特點包括:
- 量大:無法避免,因為我們需要記錄中間各部狀態;
- 無事務依賴:我們后續進行的更多是查詢而很少更改;
- 寫多讀少:讀的比例一般是寫的幾十分之一。
3)狀態型
- 數據量:與業務有關,狀態型數據可以理解為我們的訂單,以及直播場景里刷禮物的扣減情況;
- 事務強依賴:必須保證用戶下單成功之后的庫存扣減,以及用戶給主播打賞之后平臺的扣減和主播收到的禮物;
- 讀多寫多:與用戶的進程掛鉤,寫和讀的場景都比較多。
綜上所述,對于數據一般通過數據量、事務和讀寫請求三個維度進行判斷,從而對數據進行規整和梳理,對比上述我列出的三種數據類型,可以得出數據的特定類型。有了數據類型之后,我們就可以考慮進行下一個階段,即業務對數據庫的要求。
4、業務
業務對數據庫選型的要求相對而言比較多,包括事務、性能、擴縮容、高可用、遷移。
1)事務
對事務的要求需要基于數據類型進行判斷。
2)性能
一些業務對耗時比較敏感,也就是性能要求比較高,要求必須在多少毫秒以內將數據結果反饋回來。那么在進行數據庫選型時,我們需要考慮該數據庫能否承載這么高的性能反應。
3)擴縮容
如果業務要上一個新業務,要考慮滿足一年至兩年的增長的需求,因此數據庫的擴縮容能力非常重要。如果之前申請的數據量比較大,但是業務發展沒有達到預期,那么數據庫需要縮容,所以這一方面對于數據庫選型也是有要求的。
4)高可用
高可用需要進行取舍,如果要保證數據的強一致性,以及性能的穩定性,必須舍棄一部分東西,具體要與業務溝通和協調,從而保證實際效果符合業務要求。
5)遷移
遷移不僅是業務代碼的改造,從A類數據庫遷到B類數據庫還需要考慮數據庫的遷移成本,以及能否支撐同構和異構。對于業務而言,業務更多考慮的是遷移帶來的業務改造成本,一般業務會比較喜歡協議無變更、基礎操作語法不變的平滑遷移。
5、數據庫
數據庫我們要考慮的關鍵點有:
1)事務
如果你想要強事務依賴,可以用傳統型數據庫,以及現有的NewSQL,比如TiDB、OceanBase等。如果不考慮事務,數據庫選擇會更多,比如Redis、MongoDB,主要取決于具體的使用場景和數據庫要承擔的能力。
2)性能
每一種數據庫的性能不同,以關系型數據庫和非關系型數據庫為例,MongoDB和MySQL兩者的性能差別是很大的,依然取決于數據庫要承擔的能力。
3)擴縮容、高可用
擴縮容和高可用不需要進行過多的解釋,因為高可用是DBA選擇數據庫的硬性要求。
4)遷移
這一部分的遷移與業務的遷移存在差異,業務的遷移主要考慮業務改造成本,數據庫的遷移需要考慮以下三點:
- 數據是否一致
- 數據遷移時是否有增量
- 數據遷移會對業務產生什么影響
如果業務允許直接一刀切,那么方案則比較簡單;如果業務要求無損,那么如何評估方案也是需要大家進行考量的。
5)備份/還原
如果可能出現數據需要恢復的場景,則必須考慮備份/還原的能力。我們一般會更傾向于做物理備份,因為物理備份還原比較快,但是一些數據庫沒有提供物理備份的能力,如MongoDB。Redis我們也不會做持續化的備份,因為會導致性能的嚴重下降。
6)容災
容災是第一部分B站數據庫架構演進我們提到的兩地三中心和同城多活需要具備的一個能力。
7)穩定性
數據庫的要求是能夠平穩地對外提供服務,因此穩定性非常關鍵。
8)成本
我們不可能為了保證性能無限地往數據庫里加機器,因為成本會很高。同時需要考慮開源數據庫和商業數據庫的選擇,在一定程度上商業數據庫的性能比同等規格的開源數據庫更好,但是需要考慮維護成本和二次定制化能力的成本。
9)定制化
商業數據庫有時不會讓我們做更多的定制化開發,但是這會給我們的上下游依賴帶來一個問題,因為大部分場景我們會依賴于類似MySQL的binlog,下游的刷緩存能力以及大數據的實時數倉能力都需要依靠binlog去往下游,也就是CDC能力。那么這一方面也是數據庫選型需要進行評估的重要能力。
6、策略
1)多維度綜合考慮
數據庫架構選型并不是從一個維度考慮的,每個數據庫有自身的使用場景和特點,因此數據庫架構選型需要從多個維度綜合考慮,包括數據的維度、業務的真實訴求、DBA團隊能提供的數據庫能力,以及公司對于數據庫的支撐能力,主要是公司其他團隊如開發和平臺類支撐。
2)滿足未來三到五年需求
數據庫架構例如擴縮容能力,必須滿足未來3~5年的需求,而不是頻繁地迭代和更新,否則對業務而言是有損的。
3)穩定為主
數據庫需要平穩運行,而不是天天宕機,因此數據庫架構選型需要以穩定為主。
上圖的右側是目前B站的數據庫團隊使用的數據庫占比,可以看出:
- Redis、MySQL分別占比第一和第二;
- 占比第三的是MC,因MC無高可用,這一方面需要從業務層進行設計,如MC異常后的回源能力;
- 其他數據庫相對數量較少。
總體來說,B站的數據庫特點是Redis和MySQL為主,其它數據庫主要是基于我們的使用場景進行選擇和提供。
四、穩定性
今天主要是想向大家介紹B站萬億級數據庫選型與架構設計實踐,所以需要考慮數據庫如何提供穩定性能力。
1、高可用
在提供穩定性方面,主要是如何保證數據庫高可用。BRM是我們基于B站的業務特點自研的MySQL高可用組件,在該架構上我們提供了兩個功能節點Leader和Follower,能夠對集群內的所有節點進行管理和探活。不管在哪個節點進行注冊,我們都可以將其注冊到整個集群。因為內部有一個網關會把所有請求轉發到主節點,同時再分發到剩下的Follower節點上。
Leader和Follower都參與投票決策,用以規避因網絡抖動問題導致BRM誤判數據庫不可用,然后由Leader節點根據投票結果判斷該節點到底是否宕機。
整體概括起來,我們自研的BRM會有以下六個核心功能:
- 多節點部署:解決MHA單點風險;?
- 支持跨機房:跨機房部署解決因網絡異常引起的誤切風險;
- 支持權重:B站的數據庫有單機房、同城多活和異地多活,如何保證切到想要的節點上。通過對不同節點設置權重,實現類似MongoDB一樣的基于權重的選主能力;
- 多節點投票決策:通過多個BRM節點對同一個實例探測,滿足多數節點一致才判定實例不可用;?
- 專線抖動誤切預防:通過多機房多節點部署我們可以預防因專線抖動導致的主節點誤切,也可以避免跨機房專線異常造成的誤判;
- 熔斷機制:如果出現機房宕機的情況,我們可以先切一部分,查看故障發生的原因,確認沒有問題之后再把熔斷機制放開。
2、預警
保證系統的平穩運行,也涉及到預警的能力。對于數據庫的預警,真正比較具有可預測性和可觀察性的是慢查詢。數據庫的CPU和IO之類的也可以作為參考,但是會存在一定的誤判,所以我們的方案是針對慢查詢,并且做了一套慢查詢預警體系。
首先對于DB層的慢查詢,我們做了流式的采集上報和實時分析。在實時分析之后,可能會存在誤報的情況,因為如果集群在常態情況下,每天固定某個時刻都會出現比如100條慢查詢,那么此時是否該報,其實這本身是一個業務某個時間點的特定行為,不會影響整體行為,所以需要將其屏蔽。針對這一方面,我們引入多次線性回歸,通過多次線性回歸實現了對偶發性的抖動的過濾,不同業務級別環比倍數、持續性增長(未到閾值倍數,但持續增長或存在)慢查詢的預警,并且基于規則引擎實現自定義處理。
3、Proxy
通過對Proxy的大量使用,我們可以實現針對某個數據庫、某個服務、某類SQL指紋進行攔截、限流、熔斷,以阻止某些異常流量打崩數據的場景,也可以做比較輕松狀態下的讀寫分離。
我們也可以做多機房路由,將機動架構下的數據流量轉發到主庫,同時能夠動態發現拓撲結構的變化,新增或刪除從庫以及節點的變化都比較易于發現。
同時我們可以去做更精細化的Sidecar模式,從而減少業務技術與能力,通過Sidecar模式使用Proxy,可以滿足大家在大量場景下的能力。
4、多活
多活是為了保證在一個機房掛掉之后,我們可以有另外的機房支撐這一方面的能力,我前面講到的Proxy、BRM以及DTS等都是用于滿足多活的訴求。通過多活我們可以保證最大能力的冗災,同時對用戶的影響達到最小,當一個機房掛掉之后,影響的用戶可能只有一部分,快速將用戶全部導流到另外一個機房可以為用戶提供平穩的使用體驗。
五、效率
最后是自動化效率的問題,不管是TiDB這種原生的分布式數據庫還是我們基于Proxy和業務層自研的分布式數據庫能力,同時比如Redis這種超大規模集群,我們現在經常會超過Redis本身的上限,因gossip通信機制,如果節點數量過大會導致節點間的心跳請求將帶寬占滿,所以我們的自動化如何提供效率?以下是自動化運維演進的方向:
當前我們仍然處于自動化運維的階段,自動化平臺能力的核心有四個方面,分別是資源管理研發自助、運維操作和風險管理。
自動化運維平臺
1、資源管理
資源管理簡單理解就是資源如何進行分配,有多個維度:
- 主機管理;
- Operator管理:無論是否上k8s都要提供Operator的管理能力;
- 資源池管理:涉及到如何提高機器使用度的問題;
- 資源報表:涉及到賬單能力,通過賬單可以明確告訴業務哪個地方使用不合理,哪個成本可以節省,以及哪個架構可以調整。
2、研發自助
日常情況下,研發有很多事情需要做,例如查詢、導入、加字段以及健康檢查等。資源申請指的是我們辦了一些比較簡單的常規業務,他們可以基于我們前面講到的策略進行匹配后選擇數據庫。到DBA審核的時候,我們會評估他們寫入的內容是否合理,保證不會出現由于架構設計失敗引發重構的問題。
3、運維操作
集群管理、實例管理和數據管理是一些比較日常的運維操作,整體上由平臺化進行支撐,大部分可以通過自動化解決,不需要人工進行管理。
4、風險管理
風險管理包括監控與告警、健康度報表以及接入信息脫敏和存儲信息脫敏。B站涉及到電商和支付方面,需要對一些數據和用戶信息進行大量的脫敏,通過數據掃描保證數據的合規。
以上就是我們自動化平臺的能力。