實(shí)時(shí)數(shù)倉建設(shè)目的
解決傳統(tǒng)數(shù)倉的問題
實(shí)時(shí)數(shù)倉是一個(gè)很容易讓人產(chǎn)生混淆的概念。實(shí)時(shí)數(shù)倉本身似乎和把 PPT 黑色的背景變得更白一樣,從傳統(tǒng)的經(jīng)驗(yàn)來講,我們認(rèn)為數(shù)倉有一個(gè)很重要的功能,即能夠記錄歷史。通常,數(shù)倉都是希望從業(yè)務(wù)上線的第一天開始有數(shù)據(jù),然后一直記錄到現(xiàn)在。
但實(shí)時(shí)處理技術(shù),又是強(qiáng)調(diào)當(dāng)前處理狀態(tài)的一門技術(shù),所以我們認(rèn)為這兩個(gè)相對(duì)對(duì)立的方案重疊在一起的時(shí)候,它注定不是用來解決一個(gè)比較廣泛?jiǎn)栴}的一種方案。于是,我們把實(shí)時(shí)數(shù)倉建設(shè)的目的定位為解決由于傳統(tǒng)數(shù)據(jù)倉庫數(shù)據(jù)時(shí)效性低解決不了的問題。
由于這個(gè)特點(diǎn),我們給定了兩個(gè)原則:
- 傳統(tǒng)數(shù)倉能解決的問題,實(shí)時(shí)數(shù)倉就不解決了。比如上個(gè)月的一些歷史的統(tǒng)計(jì),這些數(shù)據(jù)是不會(huì)用實(shí)時(shí)數(shù)倉來建設(shè)的。
- 問題本身就不太適合用數(shù)倉來解決,也不用實(shí)時(shí)數(shù)倉解決。比如業(yè)務(wù)性很強(qiáng)的需求,或者是對(duì)時(shí)效性要求特別高的需求。這些需求我們也不建議通過實(shí)時(shí)數(shù)倉這種方式來進(jìn)行解決。
當(dāng)然為了讓我們整個(gè)系統(tǒng)看起來像是一個(gè)數(shù)倉,我們還是給自己提了一些要求的。這個(gè)要求其實(shí)跟我們建立離線數(shù)倉的要求是一樣的,首先實(shí)時(shí)的數(shù)倉是需要面向主題的,然后具有集成性,并且保證相對(duì)穩(wěn)定。
離線數(shù)倉和實(shí)時(shí)數(shù)倉的區(qū)別在于離線數(shù)據(jù)倉庫是一個(gè)保存歷史累積的數(shù)據(jù),而我們?cè)诮ㄔO(shè)實(shí)時(shí)數(shù)倉的時(shí)候,我們只保留上一次批處理到當(dāng)前的數(shù)據(jù)。這個(gè)說法非常的拗口,但是實(shí)際上操作起來還是蠻輕松的。
通常來講解決方案是保留大概三天的數(shù)據(jù),因?yàn)楸A羧斓臄?shù)據(jù)的話,可以穩(wěn)定地保證兩天完整的數(shù)據(jù),這樣就能保證,在批處理流程還沒有處理完昨天的數(shù)據(jù)的這段間隙,依然能夠提供一個(gè)完整的數(shù)據(jù)服務(wù)。
實(shí)時(shí)數(shù)倉的應(yīng)用場(chǎng)景

- 實(shí)時(shí) OLAP 分析
OLAP 分析本身就非常適合用數(shù)倉去解決的一類問題,我們通過實(shí)時(shí)數(shù)倉的擴(kuò)展,把數(shù)倉的時(shí)效性能力進(jìn)行提升。甚至可能在分析層面上都不用再做太多改造,就可以使原有的 OLAP 分析工具具有分析實(shí)時(shí)數(shù)據(jù)的能力。
- 實(shí)時(shí)數(shù)據(jù)看板
這種場(chǎng)景比較容易接受,比如天貓雙11的實(shí)時(shí)大屏滾動(dòng)展示核心數(shù)據(jù)的變化。實(shí)際上對(duì)于美團(tuán)來講,不光有促銷上的業(yè)務(wù),還有一些主要的門店業(yè)務(wù)。對(duì)于門店的老板而言,他們可能在日常的每一天中也會(huì)很關(guān)心自己當(dāng)天各個(gè)業(yè)務(wù)線上的銷售額。
- 實(shí)時(shí)特征
實(shí)時(shí)特征指通過匯總指標(biāo)的運(yùn)算來對(duì)商戶或者用戶標(biāo)記上一些特征。比如多次購買商品的用戶后臺(tái)會(huì)判定為優(yōu)質(zhì)用戶。另外,商戶銷售額高,后臺(tái)會(huì)認(rèn)為該商戶商的熱度更高。然后,在做實(shí)時(shí)精準(zhǔn)運(yùn)營(yíng)動(dòng)作時(shí)可能會(huì)優(yōu)先考慮類似的門店或者商戶。
- 實(shí)時(shí)業(yè)務(wù)監(jiān)控
美團(tuán)點(diǎn)評(píng)也會(huì)對(duì)一些核心業(yè)務(wù)指標(biāo)進(jìn)行監(jiān)控,比如說當(dāng)線上出現(xiàn)一些問題的時(shí)候,可能會(huì)導(dǎo)致某些業(yè)務(wù)指標(biāo)下降,我們可以通過監(jiān)控盡早發(fā)現(xiàn)這些問題,進(jìn)而來減少損失。
如何建設(shè)實(shí)時(shí)數(shù)倉
實(shí)時(shí)數(shù)倉概念映射
我們通過離線數(shù)倉開發(fā)和實(shí)時(shí)數(shù)倉開發(fā)的對(duì)應(yīng)關(guān)系表,幫助大家快速清晰的理解實(shí)時(shí)數(shù)倉的一些概念。

- 編程方式
離線開發(fā)最常見的方案就是采用 Hive SQL 進(jìn)行開發(fā),然后加上一些擴(kuò)展的 udf 。映射到實(shí)時(shí)數(shù)倉里來,我們會(huì)使用 Flink SQL ,同樣也是配合 udf 來進(jìn)行開發(fā)。
- 作業(yè)執(zhí)行層面
離線處理的執(zhí)行層面一般是 MapReduce 或者 Spark Job ,對(duì)應(yīng)到實(shí)時(shí)數(shù)倉就是一個(gè)持續(xù)不斷運(yùn)行的 Flink Streaming 的程序。
- 數(shù)倉對(duì)象層面
離線數(shù)倉實(shí)際上就是在使用 Hive 表。對(duì)于實(shí)時(shí)數(shù)倉來講,我們對(duì)表的抽象是使用 Stream Table 來進(jìn)行抽象。
- 物理存儲(chǔ)
離線數(shù)倉,我們多數(shù)情況下會(huì)使用 HDFS 進(jìn)行存儲(chǔ)。實(shí)時(shí)數(shù)倉,我們更多的時(shí)候會(huì)采用像 Kafka 這樣的消息隊(duì)列來進(jìn)行數(shù)據(jù)的存儲(chǔ)。
實(shí)時(shí)數(shù)倉的整體架構(gòu)
在此之前我們做過一次分享,是關(guān)于為什么選擇 Flink 來做實(shí)時(shí)數(shù)倉,其中重點(diǎn)介紹了技術(shù)組件選型的原因和思路,具體內(nèi)容參考《美團(tuán)點(diǎn)評(píng)基于 Flink 的實(shí)時(shí)數(shù)倉建設(shè)實(shí)踐》。本文分享的主要內(nèi)容是圍繞數(shù)據(jù)本身來進(jìn)行的,下面是我們目前的實(shí)時(shí)數(shù)倉的數(shù)據(jù)架構(gòu)圖。

從數(shù)據(jù)架構(gòu)圖來看,實(shí)時(shí)數(shù)倉的數(shù)據(jù)架構(gòu)會(huì)跟離線數(shù)倉有很多類似的地方。比如分層結(jié)構(gòu);比如說 ODS 層,明細(xì)層、匯總層,乃至應(yīng)用層,它們命名的模式可能都是一樣的。盡管如此,實(shí)時(shí)數(shù)倉和離線數(shù)倉還是有很多的區(qū)別的。
跟離線數(shù)倉主要不一樣的地方,就是實(shí)時(shí)數(shù)倉的層次更少一些。
以我們目前建設(shè)離線數(shù)倉的經(jīng)驗(yàn)來看,數(shù)倉的第二層遠(yuǎn)遠(yuǎn)不止這么簡(jiǎn)單,一般都會(huì)有一些輕度匯總層這樣的概念,其實(shí)第二層會(huì)包含很多層。另外一個(gè)就是應(yīng)用層,以往建設(shè)數(shù)倉的時(shí)候,應(yīng)用層其實(shí)是在倉庫內(nèi)部的。在應(yīng)用層建設(shè)好后,會(huì)建同步任務(wù),把數(shù)據(jù)同步到應(yīng)用系統(tǒng)的數(shù)據(jù)庫里。
在實(shí)時(shí)數(shù)倉里面,所謂 App 層的應(yīng)用表,實(shí)際上就已經(jīng)在應(yīng)用系統(tǒng)的數(shù)據(jù)庫里了。上圖,雖然畫了 APP 層,但它其實(shí)并不算是數(shù)倉里的表,這些數(shù)據(jù)本質(zhì)上已經(jīng)存過去了。
為什么主題層次要少一些?是因?yàn)樵趯?shí)時(shí)處理數(shù)據(jù)的時(shí)候,每建一個(gè)層次,數(shù)據(jù)必然會(huì)產(chǎn)生一定的延遲。
為什么匯總層也會(huì)盡量少建?是因?yàn)樵趨R總統(tǒng)計(jì)的時(shí)候,往往為了容忍一部分?jǐn)?shù)據(jù)的延遲,可能會(huì)人為的制造一些延遲來保證數(shù)據(jù)的準(zhǔn)確。
舉例,統(tǒng)計(jì)事件中的數(shù)據(jù)時(shí),可能會(huì)等到 10:00:05 或者 10:00:10再統(tǒng)計(jì),確保 10:00 前的數(shù)據(jù)已經(jīng)全部接受到位了,再進(jìn)行統(tǒng)計(jì)。所以,匯總層的層次太多的話,就會(huì)更大的加重人為造成的數(shù)據(jù)延遲。
建議盡量減少層次,特別是匯總層一定要減少,最好不要超過兩層。明細(xì)層可能多一點(diǎn)層次還好,會(huì)有這種系統(tǒng)明細(xì)的設(shè)計(jì)概念。
第二個(gè)比較大的不同點(diǎn)就是在于數(shù)據(jù)源的存儲(chǔ)。
在建設(shè)離線數(shù)倉的時(shí)候,可能整個(gè)數(shù)倉都全部是建立在 Hive 表上,都是跑在 Hadoop 上。但是,在建設(shè)實(shí)時(shí)數(shù)倉的時(shí)候,同一份表,我們甚至可能會(huì)使用不同的方式進(jìn)行存儲(chǔ)。
比如常見的情況下,可能絕大多數(shù)的明細(xì)數(shù)據(jù)或者匯總數(shù)據(jù)都會(huì)存在 Kafka 里面,但是像維度數(shù)據(jù),可能會(huì)存在像 Tair 或者 HBase 這樣的 kv 存儲(chǔ)的系統(tǒng)中,實(shí)際上可能匯總數(shù)據(jù)也會(huì)存進(jìn)去,具體原因后面詳細(xì)分析。除了整體結(jié)構(gòu),我們也分享一下每一層建設(shè)的要點(diǎn)。
■ ODS 層的建設(shè)
數(shù)據(jù)來源盡可能統(tǒng)一
利用分區(qū)保證數(shù)據(jù)局部有序

首先第一個(gè)建設(shè)要點(diǎn)就是 ODS 層,其實(shí) ODS 層建設(shè)可能跟倉庫不一定有必然的關(guān)系,只要使用 Flink 開發(fā)程序,就必然都要有實(shí)時(shí)的數(shù)據(jù)源。目前主要的實(shí)時(shí)數(shù)據(jù)源是消息隊(duì)列,如 Kafka。而我們目前接觸到的數(shù)據(jù)源,主要還是以 binlog、流量日志和系統(tǒng)日志為主。
這里面我主要想講兩點(diǎn):
首先第一個(gè)建設(shè)要點(diǎn)就是 ODS層,其實(shí)ODS層建設(shè)可能跟這個(gè)倉庫不一定有必然的關(guān)系,只要你使用這個(gè)flink開發(fā)程序,你必然都要有這種實(shí)時(shí)的數(shù)據(jù)源。目前的主要的實(shí)時(shí)數(shù)據(jù)源就是消息隊(duì)列,如kafka。我們目前接觸到的數(shù)據(jù)源,主要還是以binlog、流量日志和系統(tǒng)日志為主。
這里面我主要想講兩點(diǎn),一個(gè)這么多數(shù)據(jù)源我怎么選?我們認(rèn)為以數(shù)倉的經(jīng)驗(yàn)來看:
首先就是數(shù)據(jù)源的來源盡可能要統(tǒng)一。這個(gè)統(tǒng)一有兩層含義:
- 第一個(gè)統(tǒng)一就是實(shí)時(shí)的數(shù)據(jù)源本身要跟自己統(tǒng)一,比如你選擇從某個(gè)系統(tǒng)接入某一種數(shù)據(jù),要么都從binlog來接,要么都從系統(tǒng)日志來接,最好不要混著接。在不知道數(shù)據(jù)生產(chǎn)的流程的情況下,一部分通過binlog接入一部分通過系統(tǒng)日志接入,容易出現(xiàn)數(shù)據(jù)亂序的問題。
- 第二個(gè)統(tǒng)一是指實(shí)時(shí)和離線的統(tǒng)一,這個(gè)統(tǒng)一可能更重要一點(diǎn)。雖然我們是建設(shè)實(shí)時(shí)數(shù)倉,但是本質(zhì)上還是數(shù)倉,作為一個(gè)團(tuán)隊(duì)來講,倉庫里的指標(biāo)的計(jì)算邏輯和數(shù)據(jù)來源應(yīng)該完全一致,不能讓使用數(shù)據(jù)的人產(chǎn)生誤解。如果一個(gè)數(shù)據(jù)兩個(gè)團(tuán)隊(duì)都能為你提供,我們建議選擇跟離線同學(xué)一致的數(shù)據(jù)來源。包括我們公司本身也在做一些保證離線和實(shí)時(shí)采用的數(shù)據(jù)源一致的工作。
第二個(gè)要點(diǎn)就是數(shù)據(jù)亂序的問題,我們?cè)诓杉瘮?shù)據(jù)的時(shí)候會(huì)有一個(gè)比較大的問題,可能同一條數(shù)據(jù),由于分區(qū)的存在,這條數(shù)據(jù)先發(fā)生的狀態(tài)后消費(fèi)到,后發(fā)生的狀態(tài)先消費(fèi)到。我們?cè)诮鉀Q這一問題的時(shí)候采用的是美團(tuán)內(nèi)部的一個(gè)數(shù)據(jù)組件。
其實(shí),保證數(shù)據(jù)有序的主要思路就是利用 kafka 的分區(qū)來保證數(shù)據(jù)在分區(qū)內(nèi)的局部有序。至于具體如何操作,可以參考《美團(tuán)點(diǎn)評(píng)基于 Flink 的實(shí)時(shí)數(shù)倉建設(shè)實(shí)踐》。這是我們美團(tuán)數(shù)據(jù)同步部門做的一套方案,可以提供非常豐富的策略來保證同一條數(shù)據(jù)是按照生產(chǎn)順序進(jìn)行保序消費(fèi)的,實(shí)現(xiàn)在源頭解決數(shù)據(jù)亂序的問題。
■ DW 層的建設(shè)
解決原始數(shù)據(jù)中數(shù)據(jù)存在噪聲、不完整和數(shù)據(jù)形式不統(tǒng)一的情況。形成規(guī)范,統(tǒng)一的數(shù)據(jù)源。如果可能的話盡可能和離線保持一致。
明細(xì)層的建設(shè)思路其實(shí)跟離線數(shù)倉的基本一致,主要在于如何解決 ODS 層的數(shù)據(jù)可能存在的數(shù)據(jù)噪聲、不完整和形式不統(tǒng)一的問題,讓它在倉庫內(nèi)是一套滿足規(guī)范的統(tǒng)一的數(shù)據(jù)源。我們的建議是如果有可能的話,最好入什么倉怎么入倉,這個(gè)過程和離線保持一致。
尤其是一些數(shù)據(jù)來源比較統(tǒng)一,但是開發(fā)的邏輯經(jīng)常變化的系統(tǒng),這種情況下,我們可能采用的其實(shí)是一套基于配置的入倉規(guī)則??赡茈x線的同學(xué)有一套入倉的系統(tǒng),他們配置好規(guī)則就知道哪些數(shù)據(jù)表上數(shù)據(jù)要進(jìn)入實(shí)時(shí)數(shù)倉,以及要錄入哪些字段,然后實(shí)時(shí)和離線是采用同一套配置進(jìn)行入倉,這樣就可以保證我們的離線數(shù)倉和實(shí)時(shí)數(shù)倉在 DW 層長(zhǎng)期保持一個(gè)一致的狀態(tài)。
實(shí)際上建設(shè) DW 層其實(shí)主要的工作主要是以下4部分。

唯一標(biāo)紅的就是模型的規(guī)范化,其實(shí)模型的規(guī)范化,是一個(gè)老生常談的問題,可能每個(gè)團(tuán)隊(duì)在建設(shè)數(shù)倉之前,都會(huì)先把自己的規(guī)范化寫出來。但實(shí)際的結(jié)果是我們會(huì)看到其實(shí)并不是每一個(gè)團(tuán)隊(duì)最終都能把規(guī)范落地。
在實(shí)時(shí)的數(shù)倉建設(shè)當(dāng)中,我們要特別強(qiáng)調(diào)模型的規(guī)范化,是因?yàn)閷?shí)施數(shù)倉有一個(gè)特點(diǎn),就是本身實(shí)時(shí)作業(yè)是一個(gè)7×24 小時(shí)調(diào)度的狀態(tài),所以當(dāng)修改一個(gè)字段的時(shí)候,可能要付出的運(yùn)維代價(jià)會(huì)很高。在離線數(shù)倉中,可能改了某一個(gè)表,只要一天之內(nèi)把下游的作業(yè)也改了,就不會(huì)出什么問題。但是實(shí)時(shí)數(shù)倉就不一樣了,只要改了上游的表結(jié)構(gòu),下游作業(yè)必須是能夠正確解析上游數(shù)據(jù)的情況下才可以。
另外使用像 kafka 這樣的系統(tǒng),它本身并不是結(jié)構(gòu)化的存儲(chǔ),沒有元數(shù)據(jù)的概念,也不可能像改表一樣,直接把之前不規(guī)范的表名、表類型改規(guī)范。要在事后進(jìn)行規(guī)范代價(jià)會(huì)很大。所以建議一定要在建設(shè)之初就盡快把這些模型的規(guī)范化落地,避免后續(xù)要投入非常大的代價(jià)進(jìn)行治理。
- 重復(fù)數(shù)據(jù)處理
除了數(shù)據(jù)本身我們會(huì)在每條數(shù)據(jù)上額外補(bǔ)充一些信息,應(yīng)對(duì)實(shí)時(shí)數(shù)據(jù)生產(chǎn)環(huán)節(jié)的一些常見問題

- 唯一鍵和主鍵
我們會(huì)給每一條數(shù)據(jù)都補(bǔ)充一個(gè)唯一鍵和一個(gè)主鍵,這兩個(gè)是一對(duì)的,唯一鍵就是標(biāo)識(shí)是唯一一條數(shù)據(jù)的,主鍵是標(biāo)記為一行數(shù)據(jù)。一行數(shù)據(jù)可能變化很多次,但是主鍵是一樣的,每一次變化都是其一次唯一的變化,所以會(huì)有一個(gè)唯一鍵。唯一鍵主要解決的是數(shù)據(jù)重復(fù)問題,從分層來講,數(shù)據(jù)是從我們倉庫以外進(jìn)行生產(chǎn)的,所以很難保證我們倉庫以外的數(shù)據(jù)是不會(huì)重復(fù)的。
可能有些人交付數(shù)據(jù)給也會(huì)告知數(shù)據(jù)可能會(huì)有重復(fù)。生成唯一鍵的意思是指我們需要保證 DW 層的數(shù)據(jù)能夠有一個(gè)標(biāo)識(shí),來解決可能由于上游產(chǎn)生的重復(fù)數(shù)據(jù)導(dǎo)致的計(jì)算重復(fù)問題。生成主鍵,其實(shí)最主要在于主鍵在 kafka 進(jìn)行分區(qū)操作,跟之前接 ODS 保證分區(qū)有序的原理是一樣的,通過主鍵,在 kafka 里進(jìn)行分區(qū)之后,消費(fèi)數(shù)據(jù)的時(shí)候就可以保證單條數(shù)據(jù)的消費(fèi)是有序的。
- 版本和批次
版本和批次這兩個(gè)其實(shí)又是一組。當(dāng)然這個(gè)內(nèi)容名字可以隨便起,最重要的是它的邏輯。
首先,版本。版本的概念就是對(duì)應(yīng)的表結(jié)構(gòu),也就是 schema 一個(gè)版本的數(shù)據(jù)。由于在處理實(shí)時(shí)數(shù)據(jù)的時(shí)候,下游的腳本依賴表上一次的 schema 進(jìn)行開發(fā)的。當(dāng)數(shù)據(jù)表結(jié)構(gòu)發(fā)生變化的時(shí)候,就可能出現(xiàn)兩種情況:第一種情況,可能新加或者刪減的字段并沒有用到,其實(shí)完全不用感知,不用做任何操作就可以了。另外一種情況,需要用到變動(dòng)的字段。此時(shí)會(huì)產(chǎn)生一個(gè)問題,在 Kafka 的表中,就相當(dāng)于有兩種不同的表結(jié)構(gòu)的數(shù)據(jù)。這時(shí)候其實(shí)需要一個(gè)標(biāo)記版本的內(nèi)容來告訴我們,消費(fèi)的這條數(shù)據(jù)到底應(yīng)該用什么樣的表結(jié)構(gòu)來進(jìn)行處理,所以要加一個(gè)像版本這樣的概念。
第二,批次。批次實(shí)際上是一個(gè)更不常見的場(chǎng)景,有些時(shí)候可能會(huì)發(fā)生數(shù)據(jù)重導(dǎo),它跟重啟不太一樣,重啟作業(yè)可能就是改一改,然后接著上一次消費(fèi)的位置啟動(dòng)。而重導(dǎo)的話,數(shù)據(jù)消費(fèi)的位置會(huì)發(fā)生變化。
比如,今天的數(shù)據(jù)算錯(cuò)了,領(lǐng)導(dǎo)很著急讓我改,然后我需要把今天的數(shù)據(jù)重算,可能把數(shù)據(jù)程序修改好之后,還要設(shè)定程序,比如從今天的凌晨開始重新跑。這個(gè)時(shí)候由于整個(gè)數(shù)據(jù)程序是一個(gè) 7x24 小時(shí)的在線狀態(tài),其實(shí)原先的數(shù)據(jù)程序不能停,等重導(dǎo)的程序追上新的數(shù)據(jù)之后,才能把原來的程序停掉,最后使用重導(dǎo)的數(shù)據(jù)來更新結(jié)果層的數(shù)據(jù)。
在這種情況下,必然會(huì)短暫的存在兩套數(shù)據(jù)。這兩套數(shù)據(jù)想要進(jìn)行區(qū)分的時(shí)候,就要通過批次來區(qū)分。其實(shí)就是所有的作業(yè)只消費(fèi)指定批次的數(shù)據(jù),當(dāng)重導(dǎo)作業(yè)產(chǎn)生的時(shí)候,只有消費(fèi)重導(dǎo)批次的作業(yè)才會(huì)消費(fèi)這些重導(dǎo)的數(shù)據(jù),然后數(shù)據(jù)追上之后,只要把原來批次的作業(yè)都停掉就可以了,這樣就可以解決一個(gè)數(shù)據(jù)重導(dǎo)的問題。
■ 維度數(shù)據(jù)建設(shè)
其次就是維度數(shù)據(jù),我們的明細(xì)層里面包括了維度數(shù)據(jù)。關(guān)于維度的數(shù)據(jù)的處理,實(shí)際上是先把維度數(shù)據(jù)分成了兩大類采用不同的方案來進(jìn)行處理。
- 變化頻率低的維度
第一類數(shù)據(jù)就是一些變化頻率比較低的數(shù)據(jù),這些數(shù)據(jù)其實(shí)可能是一些基本上是不會(huì)變的數(shù)據(jù)。比如說,一些地理的維度信息、節(jié)假日信息和一些固定代碼的轉(zhuǎn)換。

這些數(shù)據(jù)實(shí)際上我們采用的方法就是直接可以通過離線倉庫里面會(huì)有對(duì)應(yīng)的維表,然后通過一個(gè)同步作業(yè)把它加載到緩存中來進(jìn)行訪問。還有一些維度數(shù)據(jù)創(chuàng)建得會(huì)很快,可能會(huì)不斷有新的數(shù)據(jù)創(chuàng)建出來,但是一旦創(chuàng)建出來,其實(shí)也就不再會(huì)變了。
比如說,美團(tuán)上開了一家新的門店,門店所在的城市名字等這些固定的屬性,其實(shí)可能很長(zhǎng)時(shí)間都不會(huì)變,取最新的那一條數(shù)據(jù)就可以了。這種情況下,我們會(huì)通過公司內(nèi)部的一些公共服務(wù),直接去訪問當(dāng)前最新的數(shù)據(jù)。最終,我們會(huì)包一個(gè)維度服務(wù)的這樣一個(gè)概念來對(duì)用戶進(jìn)行屏蔽,具體是從哪里查詢相關(guān)細(xì)節(jié),通過維度服務(wù)即可關(guān)聯(lián)具體的維度信息。
- 變化頻率高的維度
第二類是一些變化頻率較高的數(shù)據(jù)。比如常見的病人心腦科的狀態(tài)變動(dòng),或者某一個(gè)商品的價(jià)格等。這些東西往往是會(huì)隨著時(shí)間變化比較頻繁,比較快。而對(duì)于這類數(shù)據(jù),我們的處理方案就稍微復(fù)雜一點(diǎn)。首先對(duì)于像價(jià)格這樣變化比較頻繁的這種維度數(shù)據(jù),會(huì)監(jiān)聽它的變化。比如說,把價(jià)格想象成維度,我們會(huì)監(jiān)聽維度價(jià)格變化的消息,然后構(gòu)建一張價(jià)格變換的拉鏈表。

一旦建立了維度拉鏈表,當(dāng)一條數(shù)據(jù)來的時(shí)候,就可以知道,在這個(gè)數(shù)據(jù)某一時(shí)刻對(duì)應(yīng)的準(zhǔn)確的維度是多少,避免了由于維度快速的變化導(dǎo)致關(guān)聯(lián)錯(cuò)維度的問題。
另一類如新老客這維度,于我們而言其實(shí)是一種衍生維度,因?yàn)樗旧聿⒉皇蔷S度的計(jì)算方式,是用該用戶是否下過單來計(jì)算出來的,所以它其實(shí)是用訂單數(shù)據(jù)來算出來的一個(gè)維度。
所以類似訂單數(shù)的維度,我們會(huì)在 DW 層建立一些衍生維度的計(jì)算模型,然后這些計(jì)算模型輸出的其實(shí)也是拉鏈表,記錄下一個(gè)用戶每天這種新老客的變化程度,或者可能是一個(gè)優(yōu)質(zhì)用戶的變化的過程。由于建立拉鏈表本身也要關(guān)聯(lián)維度,所以可以通過之前分組 key 的方式來保障不亂序,這樣還是將其當(dāng)做一個(gè)不變的維度來進(jìn)行關(guān)聯(lián)。
通過這種方式來建立拉鏈表相對(duì)麻煩,所以實(shí)際上建議利用一些外部組件的功能。實(shí)際操作的時(shí)候,我們使用的是 Hbase。HBase 本身支持?jǐn)?shù)據(jù)多版本的,而且它能記錄數(shù)據(jù)更新的時(shí)間戳,取數(shù)據(jù)的時(shí)候,甚至可以用這個(gè)時(shí)間戳來做索引。
所以實(shí)際上只要把數(shù)據(jù)存到 HBase 里,再配合上 mini-versions ,就可以保證數(shù)據(jù)不會(huì)超時(shí)死掉。上面也提到過,整個(gè)實(shí)時(shí)數(shù)倉有一個(gè)大原則,不處理離線數(shù)倉能處理的過程。相當(dāng)于處理的過程,只需要處理三天以內(nèi)的數(shù)據(jù),所以還可以通過配置 TTL 來保證 HBase 里的這些維度可以盡早的被淘汰掉。因?yàn)楹芏嗵煲郧暗木S度,實(shí)際上也不會(huì)再關(guān)聯(lián)了,這樣就保證維度數(shù)據(jù)不會(huì)無限制的增長(zhǎng),導(dǎo)致存儲(chǔ)爆炸。
■ 維度數(shù)據(jù)使用
處理維度數(shù)據(jù)之后,這個(gè)維度數(shù)據(jù)怎么用?

第一種方案,也是最簡(jiǎn)單的方案,就是使用 UDTF 關(guān)聯(lián)。其實(shí)就是寫一個(gè) UDTF 去查詢上面提到的維度服務(wù),具體來講就是用 LATERAL TABLE 關(guān)鍵詞來進(jìn)行關(guān)聯(lián),內(nèi)外關(guān)聯(lián)都是支持的。
另外一種方案就是通過解析 SQL ,識(shí)別出關(guān)聯(lián)的維表以及維表中的字段,把它原本的查詢進(jìn)行一次轉(zhuǎn)化為原表.flatmap (維表),最后把整個(gè)操作的結(jié)果轉(zhuǎn)換成一張新的表來完成關(guān)聯(lián)操作。
但是這個(gè)操作要求使用者有很多周邊的系統(tǒng)來進(jìn)行配合,首先需要能解析 SQL ,同時(shí)還能識(shí)別文本,記住所有維表的信息,最后還要可以執(zhí)行 SQL 轉(zhuǎn)化,所以這套方案適合一些已經(jīng)有成熟的基于 Flink SQL 的 SQL開發(fā)框架的系統(tǒng)來使用。如果只是單純的寫封裝的代碼,建議還是使用 UDTF 的方式來進(jìn)行關(guān)聯(lián)會(huì)非常的簡(jiǎn)單,而且效果也是一樣的。
■ 匯總層的建設(shè)
在建設(shè)實(shí)時(shí)數(shù)倉的匯總層的時(shí)候,跟離線的方案其實(shí)會(huì)有很多一樣的地方。

第一點(diǎn)是對(duì)于一些共性指標(biāo)的加工,比如說 pv、uv、交易額這些運(yùn)算,我們會(huì)在匯總層進(jìn)行統(tǒng)一的運(yùn)算。另外,在各個(gè)腳本中多次運(yùn)算,不僅浪費(fèi)算力,同時(shí)也有可能會(huì)算錯(cuò),需要確保關(guān)于指標(biāo)的口徑是統(tǒng)一在一個(gè)固定的模型里面的。本身 Flink SQL 已經(jīng)其實(shí)支持了非常多的計(jì)算方法,包括這些 count distinct 等都支持。
值得注意的一點(diǎn)是,它在使用 count distinct 的時(shí)候,他會(huì)默認(rèn)把所有的要去重的數(shù)據(jù)存在一個(gè) state 里面,所以當(dāng)去重的基數(shù)比較大的時(shí)候,可能會(huì)吃掉非常多的內(nèi)存,導(dǎo)致程序崩潰。這個(gè)時(shí)候其實(shí)是可以考慮使用一些非精確系統(tǒng)的算法,比如說 BloomFilter 非精確去重、 HyperLogLog 超低內(nèi)存去重方案,這些方案可以極大的減少內(nèi)存的使用。
第二點(diǎn)就是 Flink 比較有特色的一個(gè)點(diǎn),就是 Flink 內(nèi)置非常多的這種時(shí)間窗口。Flink SQL 里面有翻滾窗口、滑動(dòng)窗口以及會(huì)話窗口,這些窗口在寫離線 SQL 的時(shí)候是很難寫出來的,所以可以開發(fā)出一些更加專注的模型,甚至可以使用一些在離線開發(fā)當(dāng)中比較少使用的一些比較小的時(shí)間窗口。
比如說,計(jì)算最近10分鐘的數(shù)據(jù),這樣的窗口可以幫助我們建設(shè)一些基于時(shí)間趨勢(shì)圖的應(yīng)用。但是這里面要注意一點(diǎn),就是一旦使用了這個(gè)時(shí)間窗口,要配置對(duì)應(yīng)的 TTL 參數(shù),這樣可以減少內(nèi)存的使用,提高程序的運(yùn)行效率。另外,如果 TTL 不夠滿足窗口的話,也有可能會(huì)導(dǎo)致數(shù)據(jù)計(jì)算的錯(cuò)誤。
第三點(diǎn),在匯總層進(jìn)行多維的主題匯總,因?yàn)閷?shí)時(shí)倉庫本身是面向主題的,可能每一個(gè)主題會(huì)關(guān)心的維度都不一樣,所以我們會(huì)在不同的主題下,按照這個(gè)主題關(guān)心的維度對(duì)數(shù)據(jù)進(jìn)行一些匯總,最后來算之前說過的那些匯總指標(biāo)。但是這里有一個(gè)問題,如果不使用時(shí)間窗口的話,直接使用 group by ,它會(huì)導(dǎo)致生產(chǎn)出來的數(shù)據(jù)是一個(gè) retract 流,默認(rèn)的 kafka 的 sink 它是只支持 append 模式,所以在這里要進(jìn)行一個(gè)轉(zhuǎn)化。
如果想把這個(gè)數(shù)據(jù)寫入 kafka 的話,需要做一次轉(zhuǎn)化,一般的轉(zhuǎn)化方案實(shí)際上是把撤回流里的 false 的過程去掉,把 true 的過程保存起來,轉(zhuǎn)化成一個(gè) append stream ,然后就可以寫入到 kafka 里了。
第四點(diǎn),在匯總層會(huì)做一個(gè)比較重要的工作,就是衍生維度的加工。如果衍生維度加工的時(shí)候可以利用 HBase 存儲(chǔ),HBase 的版本機(jī)制可以幫助你更加輕松地來構(gòu)建一個(gè)這種衍生維度的拉鏈表,可以幫助你準(zhǔn)確的 get 到一個(gè)實(shí)時(shí)數(shù)據(jù)當(dāng)時(shí)的準(zhǔn)確的維度。
倉庫質(zhì)量保證
經(jīng)過上面的環(huán)節(jié),如果你已經(jīng)建立好了一個(gè)倉庫,你會(huì)發(fā)現(xiàn)想保證倉庫的正常的運(yùn)行或者是保證它高質(zhì)量的運(yùn)行,其實(shí)是一個(gè)非常麻煩的過程,它要比一線的操作復(fù)雜得多,所以我們?cè)诮ㄔO(shè)完倉庫之后,需要建設(shè)很多的周邊系統(tǒng)來提高我們的生產(chǎn)效率。
下面介紹一下我們目前使用的一些工具鏈系統(tǒng),工具鏈系統(tǒng)的功能結(jié)構(gòu)圖如下圖。

首先,工具鏈系統(tǒng)包括一個(gè)實(shí)時(shí)計(jì)算平臺(tái),主要的功能是統(tǒng)一提交作業(yè)和一些資源分配以及監(jiān)控告警,但是實(shí)際上無論是否開發(fā)數(shù)倉,大概都需要這樣的一個(gè)工具,這是開發(fā) Flink 的基本工具。
對(duì)于我們來講,跟數(shù)倉相關(guān)的主要工具有兩塊:
- 系統(tǒng)管理模塊,這個(gè)模塊實(shí)際上是我們的實(shí)時(shí)和離線是一起使用的。其中知識(shí)庫管理模塊,主要是用來記錄模型中表和字段的一些信息,另外就是一些工單的解決方法也會(huì)維護(hù)進(jìn)去。Flink 管理主要是用來管理一些我們公司自己開發(fā)的一些 Flink 相關(guān)的系統(tǒng)組件。
- 重點(diǎn)其實(shí)還是我們整個(gè)用來開發(fā)實(shí)時(shí)數(shù)倉 ETL 的一個(gè)開發(fā)工具。主要是如下幾點(diǎn):
-
- SQL 及 UDF 管理,管理 SQL 腳本和 UDF,以及對(duì) UDF 進(jìn)行配置。
- 任務(wù)日志查看和任務(wù)監(jiān)控。
- 調(diào)度管理,主要是管理任務(wù)的重導(dǎo)和重傳。
- 數(shù)據(jù)資產(chǎn)管理,管理實(shí)時(shí)和離線的元數(shù)據(jù),以及任務(wù)依賴信息。
其實(shí)整個(gè)這條工具鏈,每個(gè)工具都有它自己特定的用場(chǎng)場(chǎng)景,下面重點(diǎn)講解其中兩個(gè)。
元數(shù)據(jù)與血緣管理
■ 元數(shù)據(jù)管理
我們?cè)?Flink SQL 的開發(fā)過程中,每一個(gè)任務(wù)都要重新把元數(shù)據(jù)重新寫一遍。因?yàn)?kafka 以及很多的緩存組件,如 Tair、redis 都不支持元數(shù)據(jù)的管理,所以我們一定要盡早建設(shè)元數(shù)據(jù)管理系統(tǒng)。
■ 血緣管理
血緣其實(shí)對(duì)于實(shí)時(shí)數(shù)倉來講比較重要,在上文中也提到過,在實(shí)時(shí)的作業(yè)的運(yùn)維過程當(dāng)中,一旦對(duì)自己的作業(yè)進(jìn)行了修改,必須保證下游都是能夠準(zhǔn)確的解析新數(shù)據(jù)的這樣一個(gè)情況。如果是依賴于這種人腦去記憶,比如說誰用我的銷售表或者口頭通知這種方式來講的話,效率會(huì)非常的低,所以一定要建立一套就是血緣的管理機(jī)制。要知道到底是誰用了生產(chǎn)的表,然后上游用了誰的,方便大家再進(jìn)行修改的時(shí)候進(jìn)行周知,保證我們整個(gè)實(shí)時(shí)數(shù)倉的穩(wěn)定。

元數(shù)據(jù)和血緣管理系統(tǒng),最簡(jiǎn)單的實(shí)現(xiàn)方式大概分為以下三點(diǎn):
- 通過元數(shù)據(jù)服務(wù)生成 Catalog
首先通過元數(shù)據(jù)系統(tǒng),把元數(shù)據(jù)系統(tǒng)里的元數(shù)據(jù)信息加載到程序中來,然后生成 Flink Catalog 。這樣就可以知道當(dāng)前作業(yè)可以消費(fèi)哪些表,使用哪些表。
- 解析 DDL 語句創(chuàng)建更新表
當(dāng)作業(yè)進(jìn)行一系列操作,最終要輸出某張表的時(shí)候,解析作業(yè)里面關(guān)于輸出部分的 DDL 代碼,創(chuàng)建出新的元數(shù)據(jù)信息寫入到元數(shù)據(jù)系統(tǒng)。
- 作業(yè)信息和運(yùn)行狀態(tài)寫入元數(shù)據(jù)
作業(yè)本身的元數(shù)據(jù)信息以及它的運(yùn)行狀態(tài)也會(huì)同步到元數(shù)據(jù)系統(tǒng)里面來,讓這些信息來幫助我們建立血緣關(guān)系。
最終的系統(tǒng)可以通過數(shù)據(jù)庫來存儲(chǔ)這些信息,如果你設(shè)計(jì)的系統(tǒng)沒那么復(fù)雜,也可以使用文件來進(jìn)行存儲(chǔ)。重點(diǎn)是需要盡快建立一套這樣的系統(tǒng),不然在后續(xù)的開發(fā)和運(yùn)維過程當(dāng)中都會(huì)非常的痛苦。
數(shù)據(jù)質(zhì)量驗(yàn)證
將實(shí)時(shí)數(shù)據(jù)寫入 Hive,使用離線數(shù)據(jù)持續(xù)驗(yàn)證實(shí)時(shí)數(shù)據(jù)的準(zhǔn)確性。
當(dāng)建設(shè)完一個(gè)數(shù)倉之后,尤其是第一次建立之后,一定會(huì)非常懷疑自己數(shù)據(jù)到底準(zhǔn)不準(zhǔn)。在此之前的驗(yàn)證方式就是通過寫程序去倉庫里去查,然后來看數(shù)據(jù)對(duì)不對(duì)。在后續(xù)的建設(shè)過程中我們發(fā)現(xiàn)每天這樣人為去對(duì)比太累了。
我們就采取了一個(gè)方案,把中間層的表寫到 Hive 里面去,然后利用離線數(shù)據(jù)豐富的質(zhì)量驗(yàn)證工具去對(duì)比離線和實(shí)時(shí)同一模型的數(shù)據(jù)差異,最后根據(jù)設(shè)定的閾值進(jìn)行監(jiān)控報(bào)警。這個(gè)方案雖然并不能及時(shí)的發(fā)現(xiàn)實(shí)時(shí)數(shù)據(jù)的問題,但是可以幫助你在上線前了解實(shí)時(shí)模型的準(zhǔn)確程度。然后進(jìn)行任務(wù)的改造,不斷提高數(shù)據(jù)的準(zhǔn)確率。另外這個(gè)方案還可以檢驗(yàn)離線數(shù)據(jù)的準(zhǔn)確性。
作者:黃偉倫 美團(tuán)研發(fā)工程師