本文系美圖互聯網技術沙龍第 11 期嘉賓分享內容,公衆號後臺回覆「naix」獲取 PPT。
大數據技術和應用系統目前已經在各個行業中發揮着巨大的做用,各類各樣的開源技術也給大數據從業人員帶來了很大的便利。Bitmap 做爲一種大數據需求下產生的計算體系,有着計算速度快、信息密度高、支持海量數據等衆多優點。java
美圖擁有海量用戶數據,天天都有大量數據計算任務。而 Bitmap 技術能大幅度減小計算的開銷,節省數據存儲的成本,儘管有很多公司作過 Bitmap 的相關嘗試,可是到目前爲止尚未一個相對成熟的分佈式 Bitmap 開源應用,所以美圖研發了本身的分佈式 Bitmap 系統,應用於美圖各個場景下的相關數據計算任務。node
Bitmap 做爲被各類框架普遍引用的一門技術,它的原理其實很簡單。mysql
bit 即比特,而 Bitmap 則是經過 bit 位來標識某個元素對應的值(支持 0、1 兩種狀態),簡單而言,Bitmap 自己就是一個 bit 數組。redis
舉個簡單的例子,假設有 10 個用戶(ID 分別爲 1~10),某天 一、三、五、七、八、9 登陸系統,如何簡單的表示用戶的登陸狀態呢?只須要找到用戶對應的位,並置 1 便可。算法
更多地,若是須要查看某用戶當天是否登陸系統,僅需查看該用戶 ID 位對應的值是否爲1。而且,經過統計 Bitmap 中 1 的個數即可知道登陸系統的用戶總數。Bitmap 已支持的運算操做(如 AND、OR、ANDNOT 等)可使維度交叉等計算更加便捷。sql
高性能數據庫
Bitmap 在其主戰場的計算性能至關驚人。在美圖,早期的統計主要基於 Hive。以美圖系某 APP 數據爲基準進行了簡單的留存計算(即統計新增用戶在第二天依然活躍的用戶數量),經過 Hive(採用 left out join)耗時 139 秒左右,而 Bitmap(交集計算)僅需 226 毫秒,Hive 的耗時是 Bitmap 的 617 倍左右。以下圖所示,其中,Hive 基於 4 節點的 Hadoop 集羣,而 Bitmap 僅使用單節點單進程。數組
存儲空間小緩存
因爲 Bitmap 是經過 bit 位來標識狀態,數據高度壓縮故佔用存儲空間極小。假設有 10 億活躍設備 ID(數值類型),若使用常規的 Int 數組存儲大概需 3.72G,而 Bitmap 僅需 110M 左右。固然,若要進行去重、排序等操做,存儲空間的節省帶來的性能紅利(如內存消耗等)也很是可觀。服務器
美圖公司擁有衆多 APP,如美圖秀秀、美顏相機、美拍、美妝相機、潮自拍等。你們熟知的美圖秀秀和美顏相機都擁有千萬級別的日活,歷史累積的用戶量更達幾十億。
大部分主要平常統計功能均基於 Bitmap,如新增、活躍、留存、升級、回訪等。 同時,咱們還支持時間粒度(好比天、周、月甚至年)及 APP、渠道、版本、地區等多種維度,以及各維度的交叉計算等。
Bitmap 原理簡單,可是僅經過 Bitmap 服務來支撐海量數據和需求比想象中更復雜。從 2015 年至今,從單機版到分佈式,從單 APP 到各類各樣 APP 的接入,從少許指標的「少許」數據到目前的海量數據和需求,咱們在 Bitmap 的實踐過程當中也遇到了很多的挑戰,其中較典型的有:
百 T 級 Bitmap 索引。這是單個節點難以維護的量級,一般狀況下須要藉助外部存儲或自研一套分佈式數據存儲來解決;
序列化和反序列化問題。雖然 Bitmap 存儲空間佔用小、計算快,但使用外部存儲時,對於大型 Bitmap 單個文件經壓縮後仍可達幾百兆甚至更多,存在很是大的優化空間。另外,存儲及查詢反序列化數據也是很是耗時的;
如何在分佈式 Bitmap 存儲上比較好的去作多維度的交叉計算,以及如何在高併發的查詢場景作到快速的響應
Naix,即美圖 Bitmap 服務的最終形態,是美圖自主研發的通用分佈式 Bitmap 服務。爲了使 Naix 適用於各類場景,咱們在設計時都儘量讓相關組件和結構通用化。
Naix 的名字來源於 Dota,在美圖數據技術團隊有各類「Dota 系列」的項目,如 Kunkka、Puck、Arachnia 等。將分佈式 Bitmap 稱做 Naix 的緣由十分簡單,其諧音 Next 寓意着下一代 Bitmap。
整個 Naix 系統以下圖所示主要分爲三層:外部調用層、系統核心節點層、依賴的外部存儲層。
外部調用層
外部調用層分爲 generator 和 tcp client。generator 是負責生成 Bitmap 的工具,原始數據、常規數據一般存儲在 HDFS 或者其餘存儲介質中,須要經過 generator 節點將對應的文本數據或其餘數據轉化成 Bitmap 相關的數據,而後再同步到系統中。tcp client 主要負責客戶端應用與分佈式系統的交互。
核心節點層
核心節點層主要包含三種:
Master 節點,即 Naix 的核心,主要是對集羣進行相關的管理和維護,如添加 Bitmap、節點管理等操做;
Transport 節點是查詢操做的中間節點,在接收到查詢相關的請求後,由 Transport 對其進行分發;
Data Nodes(Naix中最核心的數據存儲節點),咱們採用 Paldb 做爲 Bitmap 的基礎數據存儲。
依賴的外部存儲層
Naix 對於外部存儲有輕量級的、依賴,其中 mysql 主要用於對元數據進行管理,並維護調度中間狀態、數據存儲等,而 redis 更多的是做爲計算過程當中的緩存。
index group
如上圖所示 index group 是 Naix 最基本的數據結構,相似於常規數據庫中的 DataBase,主要用於隔離各類不一樣的業務。好比在美圖的業務中,美圖秀秀和美拍就是兩個不一樣的 index group。每一個 index group 中能夠有多個i ndex,index 相似於常規數據庫中的 table,如新增和活躍的 Bitmap 就屬於兩個不一樣的 index。
index
在每一個 index 中,都有一個固化的時間屬性。因爲 Bitmap 數據可能涉及不一樣的時間週期,經過格式化的時間方式將不一樣時間週期的數據放入同一個 index。對應時間段內的 index 涉及多個維度,如版本、渠道、地區等,每一個維度涉及不一樣的維度值(如 v1.0.0、v1.2.0 等),而咱們所指的 Bitmap 文件則是針對具體的維度值而言的。
數據信息字典管理
Bitmap 用於標識某個用戶或元素的狀態一般是指 ID,但在現實業務應用中每每並不是如此。若是須要對 imei、idfa 進行統計,則須要將設備標識經過數據字典的映射轉化爲 ID 後再生成 Bitmap 並完成相關統計。同時,爲了方便數據的維護和使用,咱們對維度、維度值也作了字典映射管理。
Naix genertor
對於 Bitmap 原始數據一般指是相似於 Mysql 記錄數據、HDFS 文本文件等,而 Naix generator 的做用是將原始數據轉化成 Bitmap 相關數據並同步到 Naix 系統,generator 以插件的方式支持各類不一樣場景的 Bitmap 生成,再由業務方基於插件開發各自的業務邏輯。
simple plugin 是最簡單的方式,也是咱們最先使用的插件。在美圖,大部分的數據都是 HDFS 的原始數據,經過 Hive Client 過濾相關數據處處理服務器,再經過插件轉換成 Bitmap 數據。
因爲美圖數據量大、業務複雜,在以前的某個階段,天天的數據生成須要消耗近 3 小時。若是中間出現問題再重跑,勢必會影響其餘的統計業務而形成嚴重後果。所以咱們研發了 mapreduce plugin,指望經過分發自身的優點,加快數據生成的速度。
實踐證實,使用 mapreduce plugin 最終可將接近 3 小時的 generate 過程壓縮至 8 分鐘左右(基於 4 節點的測試集羣)。基於 mapreduce 的特色,在業務和數據量持續增長的狀況下咱們也能夠經過節點擴容或者 map、reduce 數量調整很容易的保持持續快的 generate 速度。
第三個插件是 bitmap to bitmap plugin,針對各類時間週期的 Bitmap 數據,用戶能夠經過咱們提供的 plugin 進行配置,在系統中按期地根據 bitmap 生成 bitmap。相似周、月、年這樣的 Bitmap,該插件能夠經過原生的 Bitmap 生成周期性的 Bitmap(例如經過天生成周、經過周生成月等),用戶僅需提交生成計劃,最終在系統中便會按期自動生成 Bitmap 數據結果。
分片
如何將海量數據存儲到分佈式系統中?一般狀況下,常規的分佈式 Bitmap 都是依賴於一些相似 hbase 之類的外度存儲或者按照業務切割去作分佈式的存儲,在計算過程當中數據的查找以及數據的拷貝都是極大的瓶頸。通過各類嘗試,最終咱們採起分片的方式,即經過固定的寬度對全部的 Bitmap 作分片;同一分片、相同副本序號的數據存儲至相同節點,不一樣分片的數據可能會被存放在相同或者不一樣的節點。
分片的設計帶來了很多的好處:
百 T 級別的數據分佈式存儲問題迎刃而解;
並行計算:Bitmap 結構十分特殊,基本上的 Bitmap 操做均可以按分片並行計算,再彙總整合。對於巨大的 bitmap 數據,也可按此方式提高速度;
數據 copy 問題:一般狀況下,在未分片前大部分 Bitmap 實踐會按照業務將數據分開,但當數據量大時,單個業務的數據在單節點也沒法存儲。當涉及到跨業務的計算時,必然須要進行數據 copy。但進行分片後,自然就將這些計算按照分片分發到不一樣節點獨自進行計算,避免了數據 copy;
序列化和反序列化的問題:一般會出如今大型 Bitmap 中,但分片後全部 Bitmap 大小基本可控,便再也不有序列化和發序列化的問題;
跨越 int 屏障:一般 Bitmap 實現僅支持 int 範圍,而隨着美圖業務的發展,其用戶增加很快便會超過 int 範圍。採起數據分片的方式,經過分片內的 id 位移,能夠輕易地將分片進行橫向疊加從而支持到 long 的長度。
replication
replication 是常規的分佈式系統極其重要的特性,防止因爲機器宕機、磁盤損壞等緣由致使的數據丟失。在 Naix 中,replication 支持 index group level。
如圖所示,用深藍色標識主分片,淺藍色和藍綠色標識剩下的副本分片。經過兩個不一樣 replication 數量設置的 index group,以及兩個 index 內部對應的兩個 index,在圖中咱們能夠看到對應同一個 replication 下標的同一個分片,都會被存儲在相同數據節點。而對於同一個分片的不一樣副本則必然是存儲在不一樣節點中。
空間和文件碎片相關的優化
空間和文件碎片的優化是 Bitmap 實踐中嘗試最多的一部分。Bitmap 基於 Long 數組或其餘數字型數組實現,其數據每每過於密集或稀疏,有很大的優化空間。大部分Bitmap的壓縮算法相似對齊壓縮,經過壓縮節省空間並減小計算量,在美圖 Bitmap 中,早期使用 ewah(採用相似 RLE 的方式),後續切換爲 RoaringBitmap,這也是目前 Spark、Hive、Kylin、Druid 等框架經常使用的 Bitmap 壓縮方式中。對 ewah 和 RoaringBitmap 進行性能對比,在咱們真實業務場景中測試,空間節省了 67.3%,數據耗時節省了 58%。在 query 方面,實際場景測試略有提高,但不及空間和 generate 耗時性能提高明顯。
早期 Bitmap 採用文件存儲,在讀取時咱們進行了相似 mmap 的優化。但平常業務中存在不少細粒度維度拆分的Bitmap(好比某個渠道的新增用戶較少),而處理分片操做會將這些小型 Bitmap 拆分得更小。小文件對於操做系統自己的 block 和 inode 利用率極低,嘗試經過優化存儲方案來解決小文件碎片的問題。主要調研瞭如下幾個方案:
Redis
Redis 自己支持 bitset 操做,但其實現效果達不到指望。假設進行簡單的 Bitmap 數據 kv 存儲,以 200T 的數據容量爲例,每臺機器爲 256G,保留一個副本備份,大概須要 160 臺服務器,隨着業務量的增加,成本也會逐步遞增;
HBase
在美圖大數據,HBase 的使用場景很是多。若採用 HBase 存儲 Bitmap 數據,在讀寫性能上優化空間不大,且對 HBase 的依賴太重,很難達到預期的效果;
RocksDB
RocksDB 目前在業界的使用較爲廣泛。經測試,RocksDB 在開啓壓縮的場景下,CPU 和磁盤佔用會因爲壓縮致使不穩定;而在不開啓壓縮的場景下,RocksDB 在數據量上漲時性能衰減嚴重,同時多DB的使用上性能並無提高;
PalDB
PalDB 是 linkedin 開發的只讀 kv 存儲,在官方測試性能是 RocksDB 和 LevelDB 的 8 倍左右,當數據量達某個量級。PalDB 的性能甚至比 java 內置的 HashSet、HashMap 性能更優。PalDB 自己的設計有利有弊,其設計致使使用場景受限,但已基本知足 Naix 的使用場景。PalDB 源碼量少,咱們也基於具體使用場景進行了簡單調整。經測試,最終存儲空間節省 13%,query 耗時在實際併發場景中,使用 PalDB 會有 60%以上的提高。generator 耗時略長,但因爲增長了 mr 的 generator 方式故此影響可忽略;
Naix query
在 Naix 系統中,總體的交互是在咱們自研的 rpc 服務基礎上實現的(rpc 服務基於 Netty,採用 TCP 協議)。在 rpc 底層和 Naix 業務端協議都採用了 ProtoBuf。
分佈式計算的過程包括節點、分片、數據存儲等,而針對查詢場景,咱們該如何找到相關數據?如何進行計算呢?
在 Naix 中,當 client 發起查詢請求到 transport 節點,transport 節點經過查詢條件選擇最優的節點分發請求。在對應的節點中根據請求條件肯定分片的選擇,每一個節點找到對應分片後進行計算,將計算完成的結果結點進行聚合並返回 client,相似於 fork-join 疊加 fork-join 的計算過程。
Naix 系統以通用的方式支持查詢:支持操做符 ∩ ∪ - ( ) 組合表達式;用戶根據使用場景選擇所需的查詢 Tuple 和操做符組裝查詢表達式便可,固然,咱們也封裝了查詢表達式的組裝轉換工具因爲 Naix 支持的查詢自己與業務無關,用戶能夠經過組裝表達式作各類查詢操做。
舉幾個簡單的例子:
最簡單的設備或用戶定位,好比查詢某個是不是某天的新增用戶或者活躍用戶;
多維度的組合分析,好比查看某天美拍 vivo 這個渠道新增用戶在次日的留存狀況;
多維度的局部組合交叉分析(數據分析常見場景),好比統計某天美拍在百度、vivo 渠道對應的 v6.0 和 v8.0 版本的活躍用戶數,這就涉及兩個渠道和兩個版本交叉共 4 個組合的查詢。這種操做一般用於數據分析。包括前面兩種,這些簡單的查詢操做平均響應僅需幾毫秒;
多維度的全交叉計算,相似於須要知道某天美拍中的渠道和版本全部信息作交叉,產出這麼大量級的數據結果。相似操做的性能主要看查詢計算的維度數量以及涉及的數據量,一般是在秒到分鐘級的響應。
爲了將 Naix 系統推廣到更多的公司業務甚至是外部場景,咱們也還在不斷的完善和優化,目前正在作如下幾個嘗試:
早期咱們更多的是集中精力進行系統研發,保證可以知足需求。目前也在不斷的豐富運維工具,指望可以更方便運維來維護和管理 Naix;
嘗試各類各樣的計算優化,力求將 query 性能再提高一個臺階;
sql query 也在咱們的規劃內,由於 sql 是被你們更普遍接受的方式,但願能使用相似這種通用的 query 表達方式下降各類不一樣使用方的學習成本。