愛奇藝基礎(chǔ)數(shù)據(jù)平臺演進
隨著公司業(yè)務(wù)發(fā)展,除了視頻基礎(chǔ)數(shù)據(jù),還逐步對接了 UGC 視頻、全網(wǎng)影視資料、資源位、直播、游戲、文學(xué)、電商等公司大部分業(yè)務(wù)方的基礎(chǔ)數(shù)據(jù),支持海量業(yè)務(wù)數(shù)據(jù)的存儲、分發(fā)、在線查詢、離線分析等服務(wù)。?目前已有近百張數(shù)據(jù)表,總數(shù)據(jù)量數(shù)十億,數(shù)據(jù)日增長百萬級,日消息量千萬級,覆蓋公司幾十個業(yè)務(wù)團隊。
本文將從愛奇藝數(shù)據(jù)平臺在實際業(yè)務(wù)中解決HBase高可用、消息服務(wù)高可用以及平臺整體服務(wù)水平擴展能力等方面,敘述愛奇藝的探索和實踐。
01
? ?服務(wù)能力
愛奇藝基礎(chǔ)數(shù)據(jù)平臺主要提供以下能力:
02
? ?整體架構(gòu)
- 接入層:提供HTTP、RPC協(xié)議訪問,提供統(tǒng)一的消息監(jiān)聽和離線掃描SDK
- 統(tǒng)一管理平臺:為對接業(yè)務(wù)提供便捷的開發(fā)工具,表定義查看、數(shù)據(jù)量、消息量、修改記錄,實時查詢等功能;同時也包含一站式的字段定義管理系統(tǒng),可高效便捷地對業(yè)務(wù)表字段進行調(diào)整。
- 服務(wù)治理:對于數(shù)據(jù)的訪問都有精細化的權(quán)限控制、流量控制等功能
03
? ?服務(wù)流程
首先需要通過統(tǒng)一管理平臺,定義好表及其的字段類型結(jié)構(gòu),隨后會發(fā)布基于Protobuf的數(shù)據(jù)定義包,通過這個包來使用分發(fā)平臺中存儲的數(shù)據(jù)。
生產(chǎn)業(yè)務(wù)通過ID服務(wù)、寫入服務(wù)將數(shù)據(jù)寫入平臺,平臺會先將數(shù)據(jù)入庫HBase,后會發(fā)送一個更新通知消息,下游業(yè)務(wù)通過訂閱消息獲取到具體變更的ID及字段信息,再通過讀取服務(wù)獲取該ID的最新數(shù)據(jù)。
平臺內(nèi)部也基于消息將本次變動的內(nèi)容記錄于HBase,方便業(yè)務(wù)排查定位問題,尤其是數(shù)據(jù)結(jié)果不對時,業(yè)務(wù)可以很快通過這個變更記錄查詢到是哪個業(yè)務(wù)在什么時間具體IP地址改動的數(shù)據(jù),在實際工作場景中使用頻率較高。
對于消息合并服務(wù)主要是對寫入觸發(fā)的消息進行相同ID寫入的合并,減少發(fā)出的消息量,降低下游訂閱業(yè)務(wù)的處理壓力。我們針對消息區(qū)分了優(yōu)先級,不同的優(yōu)先級有不同的合并窗口時間,例如:直播等業(yè)務(wù)對時效性相對敏感,消息合并則窗口期更短。
04
? ??服務(wù)方案
??
4.1 ID服務(wù)的高可用
ID服務(wù)使用2個MySQL集群,其中一個MySQL示例只生成單數(shù)ID,另外一個MySQL生成雙數(shù)ID,這樣可以做到其中一個MySQL不可用時,另外一個MySQL可以正常提供服務(wù)。
4.2? 消息分發(fā)
平臺本身存儲了很多不同業(yè)務(wù)的數(shù)據(jù)表(例如:視頻、直播、圖書等),業(yè)務(wù)可以根據(jù)自己的業(yè)務(wù)需要可以訂閱單個或多個不同的業(yè)務(wù)表消息并做一定規(guī)則的過濾,而這種場景屬于比較普遍的,所以由平臺本身實現(xiàn)比較合理,不用每個業(yè)務(wù)都做一遍?;谶@種背景下我們最初使用了ActiveMQ的VirtualTopic做了大類的區(qū)分,但一段時間后我們發(fā)現(xiàn)這種方式并不夠靈活,無法控制的更精細。為此我們自研了一個ActiveMQ插件來滿足相對精細的消息分發(fā)控制,整體結(jié)構(gòu)如下圖:
通過管理平臺我們將規(guī)則通過一個特殊的Topic推送到插件,插件本身會監(jiān)聽這個Topic消息,將規(guī)則保存在內(nèi)存中并持久化,插件會在每一條消息發(fā)送之前對消息進行一個路由,根據(jù)訂閱規(guī)則匹配發(fā)送到1個或多個隊列中,原理類似AOP機制。
05
? ??問題及解決方案
5.1? HBase讀取性能差的問題
由于本身平臺業(yè)務(wù)場景決定,一次寫入對應(yīng)N次讀取,所以在極端場景下,線上偶爾發(fā)生過HBase某個RegionServer宕機的情況,進而造成大量的超時情況。
目前我們的主要解決的思路就是加緩存,讀多寫少就是緩存的主要場景。在數(shù)據(jù)庫選型上,我們在Redis、CouchBase、MongoDB上進行了調(diào)研,最終選擇了MongoDB,主要的原因是Redis和CouchBase在容量上不滿足業(yè)務(wù)需求。在我們對MongoDB的壓測中,性能方面也在可接受范圍內(nèi)。
緩存方案如下:
寫入服務(wù)每次請求都會生成一個唯一的SessionID,我們將這個ID作為數(shù)據(jù)的版本號,緩存是否失效使用這個版本號來判斷。每次寫入時更新緩存以及讀取時緩存失效時更新緩存都為異步,主要是為了降低延時,以及避免緩存更新失敗導(dǎo)致寫入失敗。
為了保證緩存和HBase的一致性,每次請求都要讀取HBase中存儲的版本號,這也對HBase造成了較大的壓力,為了解決這個問題,我們將HBase中SessionId設(shè)為單獨列族,并設(shè)置IN_MEMORY => ‘true’來優(yōu)化。? ? ??
5.2? HBase可用性
由于全部數(shù)據(jù)都存儲在HBase,所以提升HBase本身的可用性就尤為重要,目前單集群內(nèi)的單節(jié)點故障,HBase本身的機制是可以保證的。但是如果整個集群故障或者集群所在機房出現(xiàn)了故障,如何能保證服務(wù)可用?
經(jīng)過調(diào)研目前開源版本HBase還沒有相對完善的跨機房部署方案,例如單個機房故障情況下不影響服務(wù)正常使用。
我們在結(jié)合服務(wù)特性的情況下設(shè)計的HBase同城主備高可用方案,如下:
Mongo作為寫入緩存,保存WAL(WriteAhead Log)Mongo三機房部署,高可用。
Synchronizer服務(wù)將WAL寫入主HBase,異步服務(wù)。
主HBase與備用HBase建立數(shù)據(jù)同步。
寫入流程:Write服務(wù)只寫入Mongo,由Synchronizer同步服務(wù)將數(shù)據(jù)同步到主HBase。
讀取流程:同時讀取Mongo和HBase,將WAL最新數(shù)據(jù)與HBase數(shù)據(jù)合并得到最新數(shù)據(jù)返回,讀取服務(wù)使用Hystrix進行熔斷,如果主HBase宕了,Mongo中數(shù)據(jù)與備用HBase集群仍然可以合并出最新數(shù)據(jù)返回。
Mongo中WAL設(shè)置TTL,時間大于主庫到從庫的同步延遲。
目前該方案已經(jīng)在生產(chǎn)環(huán)境經(jīng)歷了2次故障,并且故障對讀寫無影響,上下游業(yè)務(wù)無感知。
5.3? ActiveMQ碰到的問題
- 單個慢消費情況下寫入消息性能嚴重下降,直接影響業(yè)務(wù)方正常生產(chǎn)。集群無法輕易水平擴展,易產(chǎn)生瓶頸,無法通過增加機器方式提高集群的生產(chǎn)和消費能力。
- 解決方案
對于第一個問題我們的處理這種問題的方式也比較簡單粗暴,通過之前開發(fā)的插件對每一個隊列進行閾值控制,超過一定閾值則不再繼續(xù)發(fā)送消息并通知業(yè)務(wù)及時消費消息,這對業(yè)務(wù)本身是有一定影響的,對業(yè)務(wù)不友好,治標(biāo)不治本。
在經(jīng)歷了較多次線上問題后,我們決定考慮其他消息中間件,在調(diào)研了市面上主流的消息中間件后我們將Kafka和RocketMQ作為備選,在選型的時候我們主要考慮幾個因素,可用性、可靠性、水平擴展能力,在這3項中兩個中間件都滿足需求。
還有一個需要考慮的因素就是消息過濾或分發(fā),因為存量隊列都有訂閱規(guī)則,考慮業(yè)務(wù)遷移成本問題,這個訂閱規(guī)則實現(xiàn)還是由平臺實現(xiàn),對比發(fā)現(xiàn)RocketMQ支持在服務(wù)器端過濾,這個特點吸引了我們,在經(jīng)過功能驗證后,該功能滿足需求,最終選型RocketMQ。
部署方案如下:
單集群同城3機房部署,主從部署在不同機房,保證單節(jié)點宕機、單機房不可用消息發(fā)送和消息消費不受影響,并且消息消費的時效性也不受影響。
我們還開發(fā)了基于RocketMQ客戶端的SDK,過濾規(guī)則都存儲在配置服務(wù),由SDK負責(zé)將訂閱規(guī)則推送到FilterServer,業(yè)務(wù)可以更簡單的遷移到RocketMQ,消息過濾在集群端,所以效率更高,可以減少不必要的消息投遞到客戶端。上線后,徹底解決了之前ActiveMQ的問題。
5.4? 擴展讀能力
隨著業(yè)務(wù)的不斷發(fā)展,對接的業(yè)務(wù)會越來越多,現(xiàn)有讀寫邏輯相對復(fù)雜,讀取能力并不能完全達到水平擴展的能力。為了可以更好支撐未來的業(yè)務(wù)發(fā)展,需要進一步提升讀取能力,使服務(wù)讀取能力完全做到水平擴展。在數(shù)據(jù)庫層面,通常有讀寫分離,也就是正常的讀請求操作主庫,其余純讀取的請求使用從庫來解決這種問題,但是由于業(yè)務(wù)場景限制,很多業(yè)務(wù)都是通過訂閱以及獲取最新數(shù)據(jù)的方式來同步平臺數(shù)據(jù)經(jīng)過一定的業(yè)務(wù)處理、抽取、加工成最終業(yè)務(wù)想要的數(shù)據(jù),所以單純將所有用戶的讀取請求都轉(zhuǎn)為讀從庫顯然并不合適,而且這些請求中還有一部分讀請求是寫入業(yè)務(wù)的先讀后寫,但是這種方案也給了我們啟發(fā)。
由于平臺業(yè)務(wù)較復(fù)雜,無法單純在數(shù)據(jù)庫層面做讀寫分離,所以就新增一個業(yè)務(wù)層面的從庫,通過業(yè)務(wù)服務(wù)同步主庫數(shù)據(jù),這樣下游業(yè)務(wù)可以通過從庫同步或者單純讀取數(shù)據(jù),而且從庫可以增加多個。
服務(wù)方案如下:
新服務(wù)SlaveRead(從庫)可通過消息 讀取的方式從主庫同步最新數(shù)據(jù),更新后發(fā)出消息,提供給下游服務(wù)使用,業(yè)務(wù)可以以相同的方式在從庫同步數(shù)據(jù)或者單純的讀取請求。
從庫與從庫之間可以建立同步關(guān)系,這樣整體同步的壓力就不會壓在主庫上,避免從庫多進而增加主庫壓力,最終實現(xiàn)了平臺能力可水平擴展。
06
? ??總結(jié)
總體來說愛奇藝基礎(chǔ)數(shù)據(jù)平臺通過在技術(shù)和服務(wù)方案上的不斷改進解決業(yè)務(wù)實際碰到的問題,在RocketMQ、HBase上也積累了一些實戰(zhàn)經(jīng)驗。未來將繼續(xù)探索提高平臺整體服務(wù)能力、服務(wù)穩(wěn)定性、性能等方面的技術(shù)及方案。