基于HBase做Storm 實(shí)時(shí)計(jì)算指標(biāo)存儲(chǔ)
Hi,大家好!我叫祝海林,微信號(hào)叫祝威廉,本來微博也想叫祝威廉的,可惜被人占了,于是改名叫祝威廉二世。然后總感覺哪里不對(duì)。目前在樂視云數(shù)據(jù)部門里從事實(shí)時(shí)計(jì)算,數(shù)據(jù)平臺(tái)、搜索和推薦等多個(gè)方向。曾從事基礎(chǔ)框架,搜索研發(fā)四年,大數(shù)據(jù)平臺(tái)架構(gòu)、推薦三年多,個(gè)人時(shí)間現(xiàn)專注于集群自動(dòng)化部署,服務(wù)管理,資源自動(dòng)化調(diào)度等方向。
這次探討的主題是:
基于 HBase 做 Storm 實(shí)時(shí)計(jì)算指標(biāo)存儲(chǔ)
HBase 實(shí)時(shí)指標(biāo)存儲(chǔ)是我入職樂視云后對(duì)原有的實(shí)時(shí)系統(tǒng)改造的
一部分。部分分享內(nèi)容其實(shí)還處于實(shí)施階段。架構(gòu)方案設(shè)計(jì)的話應(yīng)該是仁者見仁智者見智,也會(huì)有很多考慮不周的地方,歡迎大家批評(píng)指正。說不定大家聽完分享后好的提議我們會(huì)用到工程上,也為后面的實(shí)際課程做好準(zhǔn)備。
HBase 存儲(chǔ)設(shè)計(jì)
Storm 結(jié)果如何存儲(chǔ)到 HBase
HBase 寫入性能優(yōu)化
與傳統(tǒng)方案 (Redis/MySQL) 對(duì)比
樂視云內(nèi)部用 Storm 做 CDN,點(diǎn)播,直播流量的計(jì)算,同時(shí)還有慢速比,卡頓比等統(tǒng)計(jì)指標(biāo)。相應(yīng)的指標(biāo)會(huì)由指標(biāo)名稱,業(yè)務(wù)類型,客戶,地域,ISP 等多個(gè)維度組成。指標(biāo)計(jì)算一個(gè)比較大的問題是 Key 的集合很大。
舉個(gè)例子,假設(shè)我們有客戶 10w,計(jì)算指標(biāo)假設(shè) 100 個(gè),5 個(gè) ISP,30 個(gè)地域,這樣就有億級(jí)以上的 Key 了,我們還要統(tǒng)計(jì)分鐘級(jí)別,小時(shí)級(jí)別,天級(jí)別,月級(jí)別。所以寫入量和存儲(chǔ)量都不小。
如果采用 Redis/Memcached 寫入速度是沒有問題的,畢竟完全的內(nèi)存操作。但是 key 集合太大,其實(shí)壓力也蠻大的,我去的時(shí)候因?yàn)榧恿酥笜?biāo),結(jié)果導(dǎo)致 Memcache 被寫爆了,所以緊急做了擴(kuò)容。
首先是 Redis 查起來的太麻煩。客戶端為了某個(gè)查詢,需要匯總成千上萬個(gè) Key。。。業(yè)務(wù)方表示很蛋疼,我們也表示很蛋疼
其次,內(nèi)存是有限的,只能存當(dāng)天的。以前的數(shù)據(jù)需要轉(zhuǎn)存。
第三,你還是繞不過持久化存儲(chǔ),于是引入 MySQL,現(xiàn)在是每天一張表。那 Redis 導(dǎo)入到 MySQL 本身就麻煩。所以工作量多了,查詢也麻煩,查一個(gè)月半年的數(shù)據(jù)就吐血了。
鑒于以上原因,我們就想著有沒有更合適的方案。
我們首先就想到了 HBase,因?yàn)?HBase 還是具有蠻強(qiáng)悍的寫入性功能以及優(yōu)秀的可擴(kuò)展性。而事實(shí)上經(jīng)過調(diào)研,我們發(fā)現(xiàn) HBase 還是非常適合指標(biāo)查詢的,可以有效的通過列來減少 key 的數(shù)量。
舉個(gè)例子,我現(xiàn)在想繪制某一個(gè)視頻昨天每一分鐘的播放量的曲線圖。如果是 Redis,你很可能需要查詢 1440 個(gè) Key。如果是 HBase,只要一條記錄就搞定。
我們現(xiàn)在上圖:
這里,我們一行可以追蹤某個(gè)指標(biāo)一天的情況。如果加再加個(gè)維度,無非增加一條記錄。而如果是 redis,可能就多了一倍,也就是 2880 個(gè) key 了。
假設(shè)該視頻是 A,已經(jīng)在線上 100 天了。我們會(huì)記錄這個(gè)視頻所有的 1 分鐘播放數(shù),用 Redis 可能有 100*1440 個(gè) key,但是 HBase只要獲取 100 條記錄就可以找出來,我們把時(shí)間粒度轉(zhuǎn)化為了 hbase 的列,從而減少行 (Key)。
我們知道 HBase 是可以多列族,多 Column,Schemaless 的。所以這里,我們建了一個(gè)列族,在該列族上,直接建了 1440 個(gè) Column。Column 的數(shù)目和時(shí)間粒度有關(guān)。如果是一分鐘粒度,會(huì)有 1440 個(gè),如果是五分鐘粒度的會(huì)有 288 個(gè),如果是小時(shí)粒度的,會(huì)有 24 個(gè)。不同的粒度,我們會(huì)建不同的表。
寫入的時(shí)候,我們可以定位到 rowkey,以及對(duì)應(yīng)的 column,這里一般不會(huì)存在并發(fā)寫。當(dāng)然 HBase 的 increment 已經(jīng)解決了并發(fā)問題,但是會(huì)造成一定的性能影響。
查詢的時(shí)候,可根據(jù)天的區(qū)間查出一條相應(yīng)的記錄。我們是直接把記錄都取出來,Column 只是一個(gè) Int/Long 類型,所以 1440 個(gè) Column 數(shù)據(jù)也不算大。
Storm 計(jì)算這一塊,還有一個(gè)比較有意思的地方。假設(shè) A 指標(biāo)是五分鐘粒度的,也就是說我們會(huì)存儲(chǔ) A 指標(biāo)每個(gè)五分鐘的值。但是在實(shí)際做存儲(chǔ)的時(shí)候,他并不是五分鐘結(jié)束后就往 HBase 里存儲(chǔ),而是每隔(幾秒/或者一定條數(shù)后)就 increment 到 HBase 中,然后清除重新計(jì)數(shù)。
這里其實(shí)我要強(qiáng)調(diào)的是,到 HBase 并不是覆蓋某個(gè) Rowkey 特定的 Cloumn 值,而是在它原有的基礎(chǔ)上,做加法。這樣做可以防止時(shí)間周期比較長的指標(biāo),其累計(jì)值不會(huì)因?yàn)橛型負(fù)洚?dāng)?shù)袅硕鴣G失數(shù)據(jù)(其實(shí)還是會(huì)丟的,但可能損失的計(jì)數(shù)比較少而已)。
丟數(shù)據(jù)比如你 kill-9 了。
大家可以想象一下,如果我計(jì)算一個(gè)五分鐘的指標(biāo),到第三分鐘掛掉了,此時(shí)累計(jì)值是 1000,接著拓?fù)渲貑⒘?,五分鐘還沒完,剩下的兩分鐘它會(huì)接著累計(jì),此時(shí)是 500。如果是覆蓋寫,就會(huì)得到不正確的結(jié)果,實(shí)際上整個(gè)完整的計(jì)數(shù)是 1500。
防止拓?fù)洚?dāng)?shù)舨⒉皇沁@樣設(shè)計(jì)的主要原因,還有一點(diǎn)是計(jì)算延時(shí)了,比如某個(gè)數(shù)據(jù)片段因?yàn)槟硞€(gè)原因,延時(shí)了十分鐘才到 Storm 實(shí)時(shí)計(jì)算集群,這個(gè)時(shí)候新得到的值還可以加回去,如果是覆蓋,數(shù)據(jù)就錯(cuò)誤了。
所以 HBase 存儲(chǔ)這塊就變成做加法操作而不僅僅是簡單的更新了。目前 HBase 添加了計(jì)數(shù)的功能 (Incrment),但是我發(fā)現(xiàn)跨行,沒有批量更新的的接口。
而 HBase 的 Client 也是非常的奇特,比如 HTablePool 竟然是對(duì)象池而不是鏈接池,多個(gè) HTable 對(duì)象是共享一個(gè) Connection 鏈接的。當(dāng)然,這里 HTable 的 Connection 會(huì)比較復(fù)雜,因?yàn)橐B Zookeeper 還有各個(gè) Region。
又沒有批量接口,一個(gè) Client 只能有一個(gè) Connection 鏈接,所以導(dǎo)致客戶端的寫入量死活上不去。16 臺(tái) 32G,24 核的服務(wù)器,我做了預(yù)分區(qū) (60個(gè)左右),用了四十個(gè)進(jìn)程,300 個(gè)左右的線程去寫,也就只能寫到 60000/s 而已。
但實(shí)際并發(fā)應(yīng)該是只有 40 左右的。300 個(gè)線程并沒有起到太多作用。
還有就是,HBase 的 incrementColumnValue 的性能確實(shí)不高。至少和批量 Put 差距很大。
但在我們的測試中,還是比較平穩(wěn)的,整個(gè)寫入狀態(tài)。抖動(dòng)不大。
這里要強(qiáng)調(diào)一點(diǎn),HBase 看場景,在我們這個(gè)場景下是預(yù)分區(qū)是非常重要的。否則一開始都集中在一臺(tái)機(jī)器的一個(gè) Regin 上寫,估計(jì)很快寫的進(jìn)程就都堵住了。上線就會(huì)掛。
所以我事先收集了幾天的 key,然后預(yù)先根據(jù) key 的分布做了分區(qū)。我測試過,在我們的集群上,到了 60 個(gè)分區(qū)就是一個(gè)瓶頸,再加分區(qū)已經(jīng)不能提升寫入量。
寫入我們也做了些優(yōu)化,因?yàn)閷懙木€程和 Storm 是混用的(其實(shí)就是 Storm 在寫)。我們不能堵住了 Storm。
當(dāng)用戶提交了N條記錄進(jìn)行更新操作,我會(huì)做如下操作:
將N條分成10份,每份N/10條。
每個(gè)JVM實(shí)例會(huì)構(gòu)建一個(gè)擁有10個(gè)線程的線程池。
線程池中的每個(gè)線程都會(huì)維護(hù)一個(gè)Connection(通過ThreadLocal完成)。
線程會(huì)對(duì)自己的這N/10條數(shù)據(jù)順序進(jìn)行incrementColumnValue。
做這個(gè)優(yōu)化的原因是我上面提到的,HTable 的連接池是共享 Connnection 的。我們這里是為了讓每個(gè)線程都有一個(gè) Connection。具體分成多少份(我這里采用的是 10),是需要根據(jù) CPU 來考量的。我們的服務(wù)器 CPU 并不是很多。值不是越大越好。如果太大,比如我起了 40 個(gè)虛擬機(jī)。每個(gè)虛擬機(jī) 10 個(gè)線程,那么會(huì)有 400 個(gè)到 Zookeeper 和 HBase 的連接。值設(shè)置的過大,會(huì)對(duì) Zookeeper 有一定的壓力。
這種方案我測試的結(jié)果是:
吞吐量上去了。在 1500w 左右的測試數(shù)據(jù)中,原有的方式大概平均只有 3w/s 左右的寫入量。 通過新的方式,大概可以提高到 5.4w/s,只要 4 分鐘左右就能完成 1500w 條數(shù)據(jù)的寫入。
峰值略微提升了一些。之前大約 6.1w/s,現(xiàn)在可以達(dá)到 6.6w/s。
因?yàn)槲矣猛患荷系?Spark 模擬的提交,所以可能會(huì)對(duì) HBase 的寫入有一點(diǎn)影響,如果想要繼續(xù)提升寫入性能,只能重寫 HBase 這塊客戶端的代碼。
我們總結(jié)下上面的內(nèi)容:
Redis/Mysql 存儲(chǔ)方案存在的一些缺點(diǎn)。
HBase 表結(jié)構(gòu)設(shè)計(jì),充分李永樂 HBase 自身的特點(diǎn),有效的減少Key的數(shù)量,提高查詢效率。
Storm 寫入方案,用以保證出現(xiàn)數(shù)據(jù)延時(shí)或者 Storm 拓?fù)洚?dāng)?shù)艉蟛粫?huì)導(dǎo)致數(shù)據(jù)不可用。
我們?cè)倏纯凑麄€(gè)存儲(chǔ)體系完整的拓?fù)鋱D。
第五個(gè)圓圈是為了在實(shí)時(shí)計(jì)算出錯(cuò)時(shí),通過 Spark/MR 進(jìn)行數(shù)據(jù)恢復(fù)。
第二個(gè)圓圈和第四個(gè)圓圈是為了做維度復(fù)制,比如我計(jì)算了五分鐘的值,這些值其實(shí)可以自動(dòng)疊加到對(duì)應(yīng)的小時(shí)和天上。我們稱為分裂程序
第三個(gè)圓圈就是對(duì)外吐出數(shù)據(jù)了,由我們的統(tǒng)一查詢引擎對(duì)外提供支持查詢支持了。
我們對(duì)查詢做一個(gè)推演。如果我要給用戶繪制流量的一個(gè)月曲線圖。曲線的最小粒度是小時(shí),小時(shí)的值是取 12 個(gè)五分鐘里最高的值,我們看看需要取多少條記錄完成這個(gè)查詢。
我們需要取 31 條五分鐘的記錄,每條記錄有 288 個(gè)點(diǎn),對(duì)這 288 個(gè)點(diǎn)分成 24 份(具體就是把分鐘去掉 groupBy 一下),求出每份里的最大值(每組 SortBy 一下),這樣就得到了 24 個(gè)值。
我取過兩天的,整個(gè) HTTP 響應(yīng)時(shí)間可以控制 50ms 左右(本機(jī)測試)。
上面的整體架構(gòu)中,分裂程序是為了緩解實(shí)時(shí)寫入 HBase 的壓力,同時(shí)我們還利用 MR/Spark 做為恢復(fù)機(jī)制,如果實(shí)時(shí)計(jì)算產(chǎn)生問題,我們可以在小時(shí)內(nèi)完成恢復(fù)操作,比如日志的收集程序、分揀程序、以及格式化程序。格式化程序處理完之后是 kafka,Storm 對(duì)接的是 Kafka 和 HBase。
上面就是今天分享的內(nèi)容了。
感謝大家。