實時資料架構體系建設指南

安全劍客發表於2020-07-07
隨著網際網路的發展進入下半場,資料的時效性對企業的精細化運營越來越重要, 商場如戰場,在每天產生的海量資料中,如何能實時有效的挖掘出有價值的資訊, 對企業的決策運營策略調整有很大幫助。

隨著網際網路的發展進入下半場,資料的時效性對企業的精細化運營越來越重要, 商場如戰場,在每天產生的海量資料中,如何能實時有效的挖掘出有價值的資訊, 對企業的決策運營策略調整有很大幫助。

此外,隨著 5G 技術的成熟、廣泛應用, 對於工業網際網路、物聯網等資料時效性要求非常高的行業,企業就更需要一套完整成熟的實時資料體系來提高自身的行業競爭力。

本文從上述現狀及實時資料需求出發,結合工業界案例、筆者的實時資料開發經驗, 梳理總結了實時資料體系建設的總體方案,本文主要分為三個部分:

第一部分主要介紹了當下在工業界比較火熱的實時計算引擎 Flink 在實時資料體系建設過程中主要的應用場景及對應解決方案;
第二部分從實時資料體系架構、實時資料模型分層、實時資料體系建設方式、流批一體實時資料架構發展等四個方面思考了實時資料體系的建設方案;
第三部分則以一個具體案例介紹如何使用 Flink SQL 完成實時資料統計類需求。

一、Flink 實時應用場景

目前看來,Flink 在實時計算領域內的主要應用場景主要可分為四類場景, 分別是實時資料同步、流式 ETL、實時資料分析和複雜事件處理,具體的業務場景和對應的解決方案可詳細研究下圖, 文字層面不再詳述。

實時資料架構體系建設指南實時資料架構體系建設指南

實時資料架構體系建設思路

二、實時資料體系架構

實時資料體系大致分為三類場景:流量類、業務類和特徵類,這三種場景各有不同。

在資料模型上,流量類是扁平化的寬表,業務數倉更多是基於正規化的建模,特徵資料是 KV 儲存;
從資料來源區分,流量數倉的資料來源一般是日誌資料,業務數倉的資料來源是業務 binlog 資料,特徵數倉的資料來源則多種多樣;
從資料量而言,流量和特徵數倉都是海量資料,每天十億級以上,而業務數倉的資料量一般每天百萬到千萬級;
從資料更新頻率而言,流量資料極少更新,則業務和特徵資料更新較多,流量資料一般關注時序和趨勢,業務資料和特徵資料關注狀態變更;
在資料準確性上,流量資料要求較低,而業務資料和特徵資料要求較高。

1、實時資料體系整體架構

實時資料架構體系建設指南實時資料架構體系建設指南

實時資料架構體系建設思路
整個實時資料體系架構分為五層,分別是接入層,儲存層,計算層、平臺層和應用層,上圖只是整體架構的概要圖,每一層具體要做的事情,接下來透過文字來詳述。

1)接入層:該層利用各種資料接入工具收集各個系統的資料,包括 binlog 日誌、埋點日誌、以及後端服務日誌,資料會被收集到 Kafka 中;這些資料不只是參與實時計算,也會參與離線計算,保證實時和離線的原始資料是統一的;

2)儲存層:該層對原始資料、清洗關聯後的明細資料進行儲存,基於統一的實時資料模型分層理念,將不同應用場景的資料分別儲存在 Kafka、HDFS、Kudu、 Clickhouse、Hbase、Redis、Mysql 等儲存引擎中,各種儲存引擎存放的具體的資料型別在實時資料模型分層部分會詳細介紹;

3)計算層:計算層主要使用 Flink、Spark、Presto 以及 ClickHouse 自帶的計算能力等四種計算引擎,Flink 計算引擎主要用於實時資料同步、 流式 ETL、關鍵系統秒級實時指標計算場景,Spark SQL 主要用於複雜多維分析的準實時指標計算需求場景,Presto 和 ClickHouse 主要滿足多維自助分析、對查詢響應時間要求不太高的場景;

4)平臺層:在平臺層主要做三個方面的工作,分別是對外提供統一查詢服務、後設資料及指標管理、資料質量及血緣;

5)應用層:以統一查詢服務對各個業務線資料場景進行支援,業務主要包括實時大屏、實時資料產品、實時 OLAP、實時特徵等。

其中,平臺層詳細工作如下:

統一查詢服務支援從底層明細資料到聚合層資料的查詢,支援以SQL化方式查詢Redis、Hbase等KV儲存中的資料;
後設資料及指標管理:主要對實時的Kafka表、Kudu表、Clickhouse表、Hive表等進行統一管理,以數倉模型中表的命名方式規範表的命名,明確每張表的欄位含義、使用方,指標管理則是儘量透過指標管理系統將所有的實時指標統一管理起來,明確計算口徑,提供給不同的業務方使用;
資料質量及血緣分析:資料質量分為平臺監控和資料監控兩個部分,血緣分析則主要是對實時資料依賴關係、實時任務的依賴關係進行分析。
平臺監控部分一是對任務執行狀態進行監控,對異常的任務進行報警並根據設定的引數對任務進行自動拉起與恢復,二是針對 Flink 任務要對 Kafka 消費處理延遲進行監控並實時報警。
資料據監控則分為兩個部分:

首先流式 ETL 是整個實時資料流轉過程中重要的一環,ETL 的過程中會關聯各種維表,實時關聯時,定時對沒有關聯上的記錄上報異常日誌到監控平臺,當數量達到一定閾值時觸發報警;
其次,部分關鍵實時指標採用了 lambda 架構,因此需要對歷史的實時指標與離線 hive 計算的資料定時做對比,提供實時資料的資料質量監控,對超過閾值的指標資料進行報警。
為了配合資料監控,需要做實時資料血緣,主要是梳理實時資料體系中資料依賴關係,以及實時任務的依賴關係,從底層ODS 到 DW 再到 DM,以及 DM 層被哪些模型用到, 將整個鏈條串聯起來,這樣做在資料/任務主動調整時可以通知關聯的下游,指標異常時藉助血緣定位問題,同時基於血緣關係的分析,我們也能評估資料的應用價值,核算資料的計算成本。

2、實時資料模型分層

實時資料架構體系建設指南實時資料架構體系建設指南

實時資料架構體系建設思路
離線數倉考慮到效率問題,一般會採取空間換時間的方式,層級劃分會比較多;實時數倉考慮到實時性問題,分層則越少越好,另外也減少了中間流程出錯的可能性,因此將其分為四層。

1)ODS 層

運算元據層,儲存原始資料,對非結構化的資料進行結構化處理,輕度清洗,幾乎不刪除原始資料。

該層的資料主要來自業務資料庫的 binlog 日誌、埋點日誌和應用程式日誌。

對於 binlog 日誌透過 canal 監聽,寫到訊息佇列 Kafka 中,對應於埋點和應用程式日誌,則透過 Filebeat 採集 nginx 和 tomcat 日誌,上報到Kafka 中。

除了儲存在 Kafka 中,同時也會對業務資料庫的 binlog 日誌透過 Flink 寫入 HDFS、Kudu 等儲存引擎,落地到 5min Hive 表,供查詢明細資料,同時也提供給離線數倉,做為其原始資料;另外,對於埋點日誌資料,由於 ODS 層是非結構化的,則沒有必要落地。

2)DWD 層

實時明細資料層,以業務過程作為建模驅動,基於每個具體的業務過程特點,構建最細粒度的明細層事實表;可以結合企業的資料使用特點,將明細事實表的某些重要維度屬性欄位做適當冗餘,也即寬表化處理。

該層的資料來源於 ODS 層,透過簡單的 Streaming ETL 後得到,對於 binlog 日誌的處理主要進行簡單的資料清洗、處理資料漂移,以及可能對多個 ODS 層的表進行 Streaming Join,對流量日誌主要是做一些通用ETL 處理,將非結構化的資料結構化,關聯通用的維度欄位。

該層的資料儲存在訊息佇列 Kafka 中,同時也會用 Flink 實時寫入 Hive 5min 表,供查詢明細資料,同時要提供給離線數倉,做為其原始資料。

3)DIM 層

公共維度層,基於維度建模理念思想,建立整個業務過程的一致性維度,降低資料計算口徑和演算法不統一風險。

DIM 層資料來源於兩部分:一部分是Flink程式實時處理ODS層資料得到,另外一部分是透過離線任務出倉得到。

DIM 層維度資料主要使用 MySQL、Hbase、Redis 三種儲存引擎,對於維表資料比較少的情況可以使用 MySQL,對於單條資料大小比較小,查詢 QPS 比較高的情況,可以使用 Redis 儲存,降低機器記憶體資源佔用,對於資料量比較大,對維表資料變化不是特別敏感的場景,可以使用HBase 儲存。

4)DM 層

①資料集市層

以資料域+業務域的理念建設公共彙總層,對於DM層比較複雜,需要綜合考慮對於資料落地的要求以及具體的查詢引擎來選擇不同的儲存方式,分為輕度彙總層和高度彙總層,同時產出,高度彙總層資料用於前端比較簡單的KV查詢, 提升查詢效能,比如實時大屏,實時報表等,資料的時效性要求為秒級,輕度彙總層Kafka中寬表實時寫入OLAP儲存引擎,用於前端產品複雜的OLAP查詢場景,滿足自助分析和產出複雜報表的需求,對資料的時效性要求可容忍到分鐘級;

②輕度彙總層

輕度彙總層由明細層透過Streaming ETL得到,主要以寬表的形式存在,業務明細彙總是由業務事實明細表和維度表join得到,流量明細彙總是由流量日誌按業務線拆分和維度表join得到。

輕度彙總層資料儲存比較多樣化,首先利用Flink實時消費DWD層Kafka中明細資料join業務過程需要的維表,實時打寬後寫入該層的Kafka中,以Json或PB格式儲存。

同時對多維業務明細彙總資料透過Flink實時寫入Kudu,用於查詢明細資料和更復雜的多維資料分析需求,對於流量資料透過Flink分別寫入HDFS和ClickHouse用於複雜的多維資料分析, 實時特徵資料則透過Flink join維表後實時寫入HDFS,用於下游的離線ETL消費。

對於落地Kudu和HDFS的寬表資料,可用Spark SQL做分鐘級的預計算,滿足業務方複雜資料分析需求,提供分鐘級延遲的資料,從而加速離線ETL過程的延遲, 另外隨著Flink SQL與Hive生態整合的不斷完善,可嘗試用Flink SQL做離線ETL和OLAP計算任務(Flink流計算基於記憶體計算的特性,和presto非常類似,這使其也可以成為一個OLAP計算引擎),用一套計算引擎解決實時離線需求,從而實現批流統一。

對於Kudu中的業務明細資料、ClickHouse中的流量明細資料,也可以滿足業務方的個性化資料分析需求,利用強大的OLAP計算引擎,實時查詢明細資料,在10s量級的響應時間內給出結果,這類需求也即是實時OLAP需求,靈活性比較高。

③高度彙總層

高度彙總層由明細資料層或輕度彙總層透過聚合計算後寫入到儲存引擎中,產出一部分實時資料指標需求,靈活性比較差。

計算引擎使用Flink Datastream API和Flink SQL,指標儲存引擎根據不同的需求,對於常見的簡單指標彙總模型可直接放在MySQL裡面,維度比較多的、寫入更新比較大的模型會放在HBase裡面, 還有一種是需要做排序、對查詢QPS、響應時間要求非常高、且不需要持久化儲存如大促活動期間線上TopN商品等直接儲存在Redis裡面。

在秒級指標需求中,需要混用Lambda和Kappa架構,大部分實時指標使用Kappa架構完成計算,少量關鍵指標(如金額相關)使用Lambda架構用批處理重新處理計算,增加一次校對過程。

總體來說 DM 層對外提供三種時效性的資料:

首先是 Flink 等實時計算引擎預計算好的秒級實時指標,這種需求對資料的時效性要求非常高,用於實時大屏、計算維度不復雜的實時報表需求。
其次是 Spark SQL 預計算的延遲在分鐘級的準實時指標, 該類指標滿足一些比較複雜但對資料時效性要求不太高的資料分析場景,可能會涉及到多個事實表的join,如銷售歸因等需求。
最後一種則是不需要預計算,ad-hoc查詢的複雜多維資料分析場景,此類需求比較個性化,靈活性比較高,如果 OLAP 計算引擎效能足夠強大,也可完全滿足秒級計算需求的場景; 對外提供的秒級實時資料和另外兩種準實時資料的比例大致為 3:7,絕大多數的業務需求都優先考慮準實時計算或 ad-hoc 方式,可以降低資源使用、提升資料準確性,以更靈活的方式滿足複雜的業務場景。

3、實時資料體系建設方式

整個實時資料體系分為兩種建設方式,即實時和準實時(它們的實現方式分別是基於流計算引擎和 ETL、OLAP 引擎,資料時效性則分別是秒級和分鐘級。

1)在排程開銷方面,準實時資料是批處理過程,因此仍然需要排程系統支援,排程頻率較高,而實時資料卻沒有排程開銷。

2)在業務靈活性方面,因為準實時資料是基於 ETL 或 OLAP 引擎實現,靈活性優於基於流計算的方式。

3)在對資料晚到的容忍度方面,因為準實時資料可以基於一個週期內的資料進行全量計算,因此對於資料晚到的容忍度也是比較高的,而實時資料使用的是增量計算,對於資料晚到的容忍度更低一些。

4)在適用場景方面,準實時資料主要用於有實時性要求但不太高、涉及多表關聯和業務變更頻繁的場景,如交易型別的實時分析,實時資料則更適用於實時性要求高、資料量大的場景,如實時特徵、流量型別實時分析等場景。

4、流批一體實時資料架構發展

從1990年 Inmon 提出資料倉儲概念到今天,大資料架構經歷了從最初的離線大資料架構、Lambda 架構、Kappa 架構以及 Flink 的火熱帶出的流批一體架構,資料架構技術不斷演進,本質是在往流批一體的方向發展,讓使用者能以最自然、最小的成本完成實時計算。

1)離線大資料架構:資料來源透過離線的方式匯入到離線數倉中,下游應用根據業務需求選擇直接讀取 DM 或加一層資料服務,比如 MySQL 或 Redis,資料儲存引擎是 HDFS/Hive,ETL 工具可以是 MapReduce  或 HiveSQL。資料倉儲從模型層面分為運算元據層 ODS、資料倉儲明細層 DWD、資料集市層 DM。

2)Lambda 架構:隨著大資料應用的發展,人們逐漸對系統的實時性提出了要求,為了計算一些實時指標,就在原來離線數倉的基礎上增加了一個實時計算的鏈路,並對資料來源做流式改造(即把資料傳送到訊息佇列),實時計算去訂閱訊息佇列,直接完成指標增量的計算,推送到下游的資料服務中去,由資料服務層完成離線&實時結果的合併。

3)Kappa 架構:Lambda 架構雖然滿足了實時的需求,但帶來了更多的開發與運維工作,其架構背景是流處理引擎還不完善,流處理的結果只作為臨時的、近似的值提供參考。後來隨著 Flink 等流處理引擎的出現,流處理技術成熟起來,這時為了解決兩套程式碼的問題,LickedIn 的 Jay Kreps 提出了 Kappa 架構。

4)流批一體架構:流批一體架構比較完美的實現方式是採用流計算 + 互動式分析雙引擎架構,在這個架構中,流計算負責的是基礎資料,而互動式分析引擎是中心,流計算引擎對資料進行實時 ETL 工作,與離線相比,降低了 ETL 過程的 latency,互動式分析引擎則自帶儲存,透過計算儲存的協同最佳化, 實現高寫入 TPS、高查詢 QPS 和低查詢 latency ,從而做到全鏈路的實時化和 SQL 化,這樣就可以用批的方式實現實時分析和按需分析,並能快速的響應業務的變化,兩者配合,實現 1 + 1 > 2 的效果;該架構對互動式分析引擎的要求非常高,也許是未來大資料庫技術發展的一個重點和方向。

為了應對業務方更復雜的多維實時資料分析需求,筆者目前在資料開發中引入 Kudu這個 OLAP 儲存引擎,對訂單等業務資料使用 Presto + Kudu 的計算方案也是在探索流批一體架構在實時資料分析領域的可行性。此外,目前比較熱的資料湖技術,如 Delta lake、Hudi 等支援在 HDFS 上進行 upsert 更新,隨著其流式寫入、SQL 引擎支援的成熟,未來可以用一套儲存引擎解決實時、離線資料需求,從而減少多引擎運維開發成本。

三、Flink SQL 實時計算 UV 指標

上一部分從宏觀層面介紹瞭如何建設實時資料體系,非常不接地氣,可能大家需要的只是一個具體的 case 來了解一下該怎麼做,那麼接下來用一個接地氣的案例來介紹如何實時計算 UV 資料。

大家都知道,在 ToC 的網際網路公司,UV 是一個很重要的指標,對於老闆、商務、運營的及時決策會產生很大的影響,筆者在電商公司,目前主要的工作就是計算 UV、銷售等各類實時資料,體驗就特別深刻, 因此就用一個簡單demo 演示如何用 Flink SQL 消費 Kafka 中的 PV 資料,實時計算出 UV 指標後寫入 Hbase。

1、Kafka 源資料解析

PV 資料來源於埋點資料經 FileBeat 上報清洗後,以 ProtoBuffer 格式寫入下游 Kafka,消費時第一步要先反序列化 PB 格式的資料為 Flink 能識別的 Row 型別,因此也就需要自定義實現 DeserializationSchema 介面,具體如下程式碼, 這裡只抽取計算用到的 PV 的 mid、事件時間 time_local,並從其解析得到 log_date 欄位:

public class PageViewDeserializationSchema implements DeserializationSchema {  
public static final Logger LOG = LoggerFactory.getLogger(PageViewDeserializationSchema.class);  
protected SimpleDateFormat dayFormatter;  
private final RowTypeInfo rowTypeInfo;  
public PageViewDeserializationSchema(RowTypeInfo rowTypeInfo){  
dayFormatter = new SimpleDateFormat("yyyyMMdd", Locale.UK);  
this.rowTypeInfo = rowTypeInfo;  
} 
 
@Override 
 
public Row deserialize(byte[] message) throws IOException {  
Row row = new Row(rowTypeInfo.getArity());  
MobilePage mobilePage = null;  
try { 
 
mobilePage = MobilePage.parseFrom(message);  
String mid = mobilePage.getMid();  
row.setField(0, mid);  
Long timeLocal = mobilePage.getTimeLocal();  
String logDate = dayFormatter.format(timeLocal);  
row.setField(1, logDate);  
row.setField(2, timeLocal);  
}catch (Exception e){  
String mobilePageError = (mobilePage != null) ? mobilePage.toString() : "";  
LOG.error("error parse bytes payload is {}, pageview error is {}", message.toString(), mobilePageError, e);  
} 
 return null;  
}
2、編寫 Flink Job 主程式

將 PV 資料解析為 Flink 的 Row 型別後,接下來就很簡單了,編寫主函式,寫 SQL 就能統計 UV 指標了,程式碼如下:

public class RealtimeUV {  
public static void main(String[] args) throws Exception {  
//step1 從properties配置檔案中解析出需要的Kakfa、Hbase配置資訊、checkpoint引數資訊  
Map config = PropertiesUtil.loadConfFromFile(args[0]);  
String topic = config.get("source.kafka.topic");  
String groupId = config.get("source.group.id");  
String sourceBootStrapServers = config.get("source.bootstrap.servers");  
String hbaseTable = config.get("hbase.table.name");  
String hbaseZkQuorum = config.get("hbase.zk.quorum");  
String hbaseZkParent = config.get("hbase.zk.parent");  
int checkPointPeriod = Integer.parseInt(config.get("checkpoint.period"));  
int checkPointTimeout = Integer.parseInt(config.get("checkpoint.timeout"));  
StreamExecutionEnvironment sEnv = StreamExecutionEnvironment.getExecutionEnvironment(); 
 
//step2 設定Checkpoint相關引數,用於Failover容錯 
 
sEnv.getConfig().registerTypeWithKryoSerializer(MobilePage.class, 
 
ProtobufSerializer.class);  
sEnv.getCheckpointConfig().setFailOnCheckpointingErrors(false);  
sEnv.getCheckpointConfig().setMaxConcurrentCheckpoints(1);  
sEnv.enableCheckpointing(checkPointPeriod,CheckpointingMode.EXACTLY_ONCE);  
sEnv.getCheckpointConfig().setCheckpointTimeout(checkPointTimeout);  
sEnv.getCheckpointConfig().enableExternalizedCheckpoints(  
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); 
 
//step3 使用Blink planner、建立TableEnvironment,並且設定狀態過期時間,避免Job OOM 
 
EnvironmentSettings environmentSettings = EnvironmentSettings.newInstance()  
.useBlinkPlanner()  
.inStreamingMode()  
.build();  
StreamTableEnvironment tEnv = StreamTableEnvironment.create(sEnv, environmentSettings); 
tEnv.getConfig().setIdleStateRetentionTime(Time.days(1), Time.days(2));  
Properties sourceProperties = new Properties(); 
 sourceProperties.setProperty("bootstrap.servers", sourceBootStrapServers);  
sourceProperties.setProperty("auto.commit.interval.ms", "3000");  
sourceProperties.setProperty("group.id", groupId); 
 
//step4 初始化KafkaTableSource的Schema資訊,筆者這裡使用register TableSource的方式將源表註冊到Flink中,而沒有用register DataStream方式,也是因為想熟悉一下如何註冊KafkaTableSource到Flink中 
 
TableSchema schema = TableSchemaUtil.getAppPageViewTableSchema();  
Optional proctimeAttribute = Optional.empty();  
List rowtimeAttributeDescriptors = Collections.emptyList();  
Map fieldMapping = new HashMap<>();  
List columnNames = new ArrayList<>(); 
 
RowTypeInfo rowTypeInfo = new RowTypeInfo(schema.getFieldTypes(), schema.getFieldNames()); 
 
columnNames.addAll(Arrays.asList(schema.getFieldNames()));  
columnNames.forEach(name -> fieldMapping.put(name, name));  
PageViewDeserializationSchema deserializationSchema = new  
PageViewDeserializationSchema(  
rowTypeInfo);  
Map specificOffsets = new HashMap<>();  
Kafka011TableSource kafkaTableSource = new Kafka011TableSource( 
 
schema, 
 
proctimeAttribute,  
rowtimeAttributeDescriptors,  
Optional.of(fieldMapping),  
topic, 
 
sourceProperties,  
deserializationSchema,  
StartupMode.EARLIEST,  
specificOffsets);  
tEnv.registerTableSource("pageview", kafkaTableSource); 
 
//step5 初始化Hbase TableSchema、寫入引數,並將其註冊到Flink中 
 
HBaseTableSchema hBaseTableSchema = new HBaseTableSchema(); 
hBaseTableSchema.setRowKey("log_date", String.class);  
hBaseTableSchema.addColumn("f", "UV", Long.class);  
HBaseOptions hBaseOptions = HBaseOptions.builder()  
.setTableName(hbaseTable)  
.setZkQuorum(hbaseZkQuorum)  
.setZkNodeParent(hbaseZkParent)  
.build(); 
 
HBaseWriteOptions hBaseWriteOptions = HBaseWriteOptions.builder() 
.setBufferFlushMaxRows(1000)  
.setBufferFlushIntervalMillis(1000)  
.build();  
HBaseUpsertTableSink hBaseSink = new HBaseUpsertTableSink(hBaseTableSchema, hBaseOptions, hBaseWriteOptions);  
tEnv.registerTableSink("uv_index", hBaseSink); 
 
//step6 實時計算當天UV指標sql, 這裡使用最簡單的group by agg,沒有使用minibatch或視窗,在大資料量最佳化時最好使用後兩種方式 
 
String uvQuery = "insert into uv_index "  
+ "select log_date,\n"  
+ "ROW(count(distinct mid) as UV)\n"  
+ "from pageview\n"  
+ "group by log_date";  
tEnv.sqlUpdate(uvQuery);  
//step7 執行Job  
sEnv.execute("UV Job");  
}  
}

以上就是一個簡單的使用 Flink SQL 統計 UV 的 case, 程式碼非常簡單,只需要理清楚如何解析 Kafka 中資料,如何初始化 Table Schema,以及如何將表註冊到 Flink中,即可使用 Flink SQL 完成各種複雜的實時資料統計類的業務需求,學習成本比API 的方式低很多。

說明一下,筆者這個 demo 是基於目前業務場景而開發的,在生產環境中可以真實執行起來,可能不能拆箱即用,你需要結合自己的業務場景自定義相應的 kafka 資料解析類。

原文來自:



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2703074/,如需轉載,請註明出處,否則將追究法律責任。

相關文章