二、在線特征存取技術(shù)
本節(jié)介紹一些在線特征系統(tǒng)上常用的存取技術(shù)點(diǎn),以豐富我們的武器庫(kù)。主要內(nèi)容也并非詳細(xì)的系統(tǒng)設(shè)計(jì),而是一些常見(jiàn)問(wèn)題的通用技術(shù)解決方案。但如上節(jié)所說(shuō),如何根據(jù)策略需求,利用合適的技術(shù),制定對(duì)應(yīng)的方案,才是各位架構(gòu)師的核心價(jià)值所在。
2.1 數(shù)據(jù)分層
特征總數(shù)據(jù)量達(dá)到TB級(jí)后,單一的存儲(chǔ)介質(zhì)已經(jīng)很難支撐完整的業(yè)務(wù)需求了。高性能的在線服務(wù)內(nèi)存或緩存在數(shù)據(jù)量上成了杯水車(chē)薪,分布式KV存儲(chǔ)能提供更大的存儲(chǔ)空間但在某些場(chǎng)景又不夠快。開(kāi)源的分布式KV存儲(chǔ)或緩存方案很多,比如我們用到的就有Redis/Memcache,HBase,Tair等,這些開(kāi)源方案有大量的貢獻(xiàn)者在為它們的功能、性能做出不斷努力,本文就不更多著墨了。
對(duì)構(gòu)建一個(gè)在線特征系統(tǒng)而言,實(shí)際上我們需要理解的是我們的特征數(shù)據(jù)是怎樣的。有的數(shù)據(jù)非常熱,我們通過(guò)內(nèi)存副本或者是緩存能夠以極小的內(nèi)存代價(jià)覆蓋大量的請(qǐng)求。有的數(shù)據(jù)不熱,但是一旦訪問(wèn)要求穩(wěn)定而快速的響應(yīng)速度,這時(shí)基于全內(nèi)存的分布式存儲(chǔ)方案就是不錯(cuò)的選擇。對(duì)于數(shù)據(jù)量級(jí)非常大,或者增長(zhǎng)非??斓臄?shù)據(jù),我們需要選擇有磁盤(pán)兜底的存儲(chǔ)方案——其中又要根據(jù)各類不同的讀寫(xiě)分布,來(lái)選擇存儲(chǔ)技術(shù)。
當(dāng)業(yè)務(wù)發(fā)展到一定層次后,單一的特征類型將很難覆蓋所有的業(yè)務(wù)需求。所以在存儲(chǔ)方案選型上,需要根據(jù)特征類型進(jìn)行數(shù)據(jù)分層。分層之后,不同的存儲(chǔ)引擎統(tǒng)一對(duì)策略服務(wù)提供特征數(shù)據(jù),這是保持系統(tǒng)性能和功能兼得的最佳實(shí)踐。
2.2 數(shù)據(jù)壓縮
海量的離線特征加載到線上系統(tǒng)并在系統(tǒng)間流轉(zhuǎn),對(duì)內(nèi)存、網(wǎng)絡(luò)帶寬等資源都是不小的開(kāi)銷。數(shù)據(jù)壓縮是典型的以時(shí)間換空間的例子,往往能夠成倍減少空間占用,對(duì)于線上珍貴的內(nèi)存、帶寬資源來(lái)說(shuō)是莫大的福音。數(shù)據(jù)壓縮本質(zhì)思想是減少信息冗余,針對(duì)特征系統(tǒng)這個(gè)應(yīng)用場(chǎng)景,我們積累了一些實(shí)踐經(jīng)驗(yàn)與大家分享。
2.2.1 存儲(chǔ)格式
特征數(shù)據(jù)簡(jiǎn)單來(lái)說(shuō)即特征名與特征值。以用戶畫(huà)像為例,一個(gè)用戶有年齡、性別、愛(ài)好等特征。存儲(chǔ)這樣的特征數(shù)據(jù)通常來(lái)說(shuō)有下面幾種方式:
JSON格式,完整保留特征名-特征值對(duì),以JSON字符串的形式表示。
元數(shù)據(jù)抽取,如Hive一樣,特征名(元數(shù)據(jù))單獨(dú)保存,特征數(shù)據(jù)以String格式的特征值列表表示。
元數(shù)據(jù)固化,同樣將元數(shù)據(jù)單獨(dú)保存,但是采用強(qiáng)類型定義每個(gè)特征,如Integer、Double等而非統(tǒng)一的String類型。
三種格式各有優(yōu)劣:
JSON格式的優(yōu)點(diǎn)在特征數(shù)量可以是變長(zhǎng)的。以用戶畫(huà)像為例,A用戶可能有年齡、性別標(biāo)簽。B用戶可以有籍貫、愛(ài)好標(biāo)簽。不同用戶標(biāo)簽種類可以差別很大,都能便捷的存儲(chǔ)。但缺點(diǎn)是每組特征都要存儲(chǔ)特征名,當(dāng)特征種類同構(gòu)性很高時(shí),會(huì)包含大量冗余信息。
元數(shù)據(jù)抽取的特點(diǎn)與JSON格式相反,它只保留特征值本身,特征名作為元數(shù)據(jù)單獨(dú)存放,這樣減少了冗余特征名的存儲(chǔ),但缺點(diǎn)是數(shù)據(jù)格式必須是同構(gòu)的,而且如果需要增刪特征,需要更改元數(shù)據(jù)后刷新整個(gè)數(shù)據(jù)集。
元數(shù)據(jù)固化的優(yōu)點(diǎn)與元數(shù)據(jù)抽取相同,而且更加節(jié)省空間。然而其存取過(guò)程需要實(shí)現(xiàn)專有序列化,實(shí)現(xiàn)難度和讀寫(xiě)速度都有成本。
特征系統(tǒng)中,一批特征數(shù)據(jù)通常來(lái)說(shuō)是完全同構(gòu)的,同時(shí)為了應(yīng)對(duì)高并發(fā)下的批量請(qǐng)求,我們?cè)趯?shí)踐中采用了元數(shù)據(jù)抽取作為存儲(chǔ)方案,相比JSON格式,有2~10倍的空間節(jié)約(具體比例取決于特征名的長(zhǎng)度、特征個(gè)數(shù)以及特征值的類型)。
2.2.2 字節(jié)壓縮
提到數(shù)據(jù)壓縮,很容易就會(huì)想到利用無(wú)損字節(jié)壓縮算法。無(wú)損壓縮的主要思路是將頻繁出現(xiàn)的模式(Pattern)用較短的字節(jié)碼表示。考慮到在線特征系統(tǒng)的讀寫(xiě)模式是一次全量寫(xiě)入,多次逐條讀取,因此壓縮需要針對(duì)單條數(shù)據(jù),而非全局壓縮。目前主流的Java實(shí)現(xiàn)的短文本壓縮算法有Gzip、Snappy、Deflate、LZ4等,我們做了兩組實(shí)驗(yàn),主要從單條平均壓縮速度、單條平均解壓速度、壓縮率三個(gè)指標(biāo)來(lái)對(duì)比以上各個(gè)算法。
數(shù)據(jù)集:我們選取了2份線上真實(shí)的特征數(shù)據(jù)集,分別取10萬(wàn)條特征記錄。記錄為純文本格式,平均長(zhǎng)度為300~400字符(600~800字節(jié))。
壓縮算法:Deflate算法有1~9個(gè)壓縮級(jí)別,級(jí)別越高,壓縮比越大,操作所需要的時(shí)間也越長(zhǎng)。而LZ4算法有兩個(gè)壓縮級(jí)別,我們用0,1表示。除此之外,LZ4有不同的實(shí)現(xiàn)版本:JNI、Java Unsafe、Java Safe,詳細(xì)區(qū)別參考 https://github.com/lz4/lz4-java ,這里不做過(guò)多解釋。