分佈式數據處理框架:Apache Beam

1簡介

大數據時代中,數據從簡單的批處理,擴展到實時處理、流處理。起初的MapReduce處理模式早已獨木難支。此外,大數據處理技術也是百花齊放,如 HBase、Hive、Kafka、Spark、Flink 等,對開發者而言,想要將其全部熟練運用幾乎是一項不可能完成的任務。此時,Google在2016年2月宣佈將大數據流水線產品(Google DataFlow)貢獻給 Apache 基金會孵化,2017年1月Apache 對外宣佈開源 Apache Beam,2017年5月迎來了它的第一個穩定版本2.0.0。

Apache Beam的主要目標是統一批處理和流處理的編程範式,爲亂序無限的大數據集處理提供簡單靈活,功能豐富以及表達能力強大的SDK,但Apache Beam不涉及具體執行引擎的實現,Apache Beam希望基於Beam開發的數據處理程序可以執行在任意的分佈式計算引擎上。

2Apache Beam架構

2.1基礎架構

Apache Beam將數據處理分成三層Beam Model、Pipeline和Beam Runners組成。如下圖2-1所示,Model是Beam 的模型或叫數據來源的IO,它是由多種數據源或倉庫的IO組成,數據源支持批處理和流處理。Pipeline是Beam的管道,所有的批處理或流處理都要通過這個管道把數據傳輸到後端的計算平臺。這個管道現在是唯一的。數據源可以切換多種,計算平臺或處理平臺也支持多種。需要注意的是,管道只有一條,它的作用是連接數據和 Runners平臺(計算引擎)。Runners是大數據計算或處理平臺,目前支持Apache Flink、Apache Spark、Direct Pipeline 和 Google Clound Dataflow 四種。其中 Apache Flink 和 Apache Spark 同時支持本地和雲端。Direct Pipeline 僅支持本地,Google Clound Dataflow 僅支持雲端。後續還將接入更多大數據計算平臺。


圖2-1 數據處理架構圖

2.2Beam Model

Beam Model便是Beam的編程範式,即Beam SDK的靈魂。首先,瞭解下Beam Model相關問題域的一些基本概念。

l  數據源

分佈式數據處理的數據來源類型通常有兩類,有界的數據集和無界的數據流。有界的數據集,比如一個HDFS中的文件,一個Hive表等,數據特點是已持久化、大小固定、不常變動。而無界的數據流,比如Kafka流出來的數據流,其特點是動態、無邊界、無法全部持久化。Beam框架設計時便需兼顧這兩種數據源數據處理進行考慮,即批處理和流處理。

l  時間

分佈式框架的時間處理有兩種,一種是全量計算,另一種是部分增量計算。批處理任務通常進行全量的數據計算,較少關注數據的時間屬性,但是對於流處理任務來說,由於數據流是無情無盡的,無法進行全量的計算,通常是對某個窗口中得數據進行計算,對於大部分的流處理任務來說,按照時間進行窗口劃分,可能是最常見的需求。

l  亂序

對於流處理的數據流來說,數據的到達順序可能並不嚴格按照Event-Time的時間順序。若是按照 Process Time 定義時間窗口,便不存在亂序問題,因爲都是關閉當前窗口後才進行下一個窗口操作,需要等待,所以執行都是有序的。而對於Event Time定義的時間窗口,則可能存在時間靠前的消息在時間靠後的消息後到達的情況,這在分佈式的數據源中可能非常常見的棘手問題。

Beam Model 處理的目標數據是無界的時間亂序數據流,不考慮時間順序或有界的數據集可看做是無界亂序數據流的一個特例。Beam Model 從下面四個維度歸納了用戶在進行數據處理的時候需要考慮的問題:

  • What。如何對數據進行計算?例如,機器學習中訓練學習模型可以用 Sum或者Join 等。在Beam SDK中由Pipeline中的操作符指定。
  • Where。數據在什麼範圍中計算?例如,基於Process-Time 的時間窗口、基於Event-Time的時間窗口、滑動窗口等等。在Beam SDK中由Pipeline 的窗口指定。
  • When。何時輸出計算結果?例如,在1小時的Event-Time時間窗口中,每隔1分鐘將當前窗口計算結果輸出。在Beam SDK中由Pipeline的Watermark和觸發器指定。
  • How。遲到數據如何處理?例如,將遲到數據計算增量結果輸出,或是將遲到數據計算結果和窗口內數據計算結果合併成全量結果輸出。在Beam SDK中由Accumulation指定。

Beam Model將“WWWH”四個維度抽象出來組成了Beam SDK,用戶在基於Beam SDK構建數據處理業務邏輯時,每一步只需要根據業務需求按照這四個維度調用具體的API,即可生成分佈式數據處理Pipeline,並提交到具體的Runners執行引擎上執行。Apache Beam目前支持的API接口是由Java與Python語言實現的,其他語言版本的API正在開發之中。下表2-1是目前Beam 2.0的SDKs支持數據源IO 。

數據源IO

描述

Amqp

高級消息隊列協議

Cassandra

Cassandra是一個NoSQL列族(column family)實現,使用由Amazon Dynamo引入的架構方面的特性來支持Big Table數據模型。

Elasticesarch

一個實時的分佈式搜索引擎

Google-cloud-platform

谷歌雲 IO

Hadoop-file-system

操作 Hadoop 文件系統的 IO

Hadoop-hbase

操作 Hadoop 上的 Hbase 的接口 IO

Hcatalog

Hcatalog 是 Apache 開源的對於表和底層數據管理統一服務平臺

Jdbc

連接各種數據庫的數據庫連接器

Jms

Java 消息服務(Java Message Service,簡稱 JMS)是用於訪問企業消息系統的開發商中立的 API。企業消息系統可以協助應用軟件通過網絡進行消息交互。JMS 在其中扮演的角色與 JDBC 很相似,正如 JDBC 提供了一套用於訪問各種不同關係數據庫的公共 API,JMS 也提供了獨立於特定廠商的企業消息系統訪問方式

Kafka

處理流數據的輕量級大數據消息系統,或叫消息總線

Kinesis

對接亞馬遜的服務,可以構建用於處理或分析流數據的自定義應用程序,以滿足特定需求

Mongodb

MongoDB 是一個基於分佈式文件存儲的數據庫

Mqtt

IBM 開發的一個即時通訊協議

Solr

亞實時的分佈式搜索引擎技術

xml

一種數據格式

表2-1 Beam 2.0的SDKs支持數據源IO

3簡單實戰演習

3.1 環境

A.     下載安裝JDK 7或更新的版本。

B.      下載maven並配置。

C.      開發環境 Eclipse(個人習慣)。

3.2 Join操作示例

3.2.1Join案例

將用戶信息(用戶賬戶+用戶名)文本數據,與訂單信息(用戶賬戶+訂單名+訂單詳情)文本數據,通過用戶賬戶字段取並集操作。

3.2.2 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>william-lib</groupId>
	<artifactId>wordcount</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>wordcount</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
		</dependency>
		<dependency>
			<groupId>org.apache.beam</groupId>
			<artifactId>beam-sdks-java-core</artifactId>
			<version>2.0.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.beam</groupId>
			<artifactId>beam-runners-direct-java</artifactId>
			<version>2.0.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.beam</groupId>
			<artifactId>beam-sdks-java-io-jdbc</artifactId>
			<version>2.0.0</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.38</version>
		</dependency>

	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
					<encoding>UTF8</encoding>
					<fork>fasle</fork>
					<meminitial>1024m</meminitial>
					<maxmem>2024m</maxmem>
				</configuration>
			</plugin>


			<!-- The configuration of maven-jar-plugin -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<!-- The configuration of the plugin -->
				<configuration>
					<!-- Configuration of the archiver -->
					<archive>
						<!-- 生成的jar中,不要包含pom.xml和pom.properties這兩個文件 -->
						<addMavenDescriptor>false</addMavenDescriptor>
						<!-- Manifest specific configuration -->
						<manifest>
							<!-- 是否要把第三方jar放到manifest的classpath中 -->
							<addClasspath>true</addClasspath>
							<!-- 生成的manifest中classpath的前綴,因爲要把第三方jar放到lib目錄下,所以classpath的前綴是lib/ -->
							<classpathPrefix>./</classpathPrefix>
							<!-- 應用的main class -->
							<mainClass>william_lib.wordcount</mainClass>
						</manifest>
					</archive>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>


3.2.3TestJoin.java

package william_lib.wordcount;

import org.apache.beam.runners.direct.DirectRunner;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.io.TextIO;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.MapElements;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.SimpleFunction;
import org.apache.beam.sdk.transforms.join.CoGbkResult;
import org.apache.beam.sdk.transforms.join.CoGroupByKey;
import org.apache.beam.sdk.transforms.join.KeyedPCollectionTuple;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.TupleTag;

/**
 *
 * <pre>
 * 2個本地數據集,以指定字段做join操作
 * </pre>
 *
 * @author William_JM
 * @date 2017年12月13日
 *
 */
public class TestJoin {
  @SuppressWarnings("serial")
  public static void main(String[] args) {
    // 管道工廠創建需求管道
    PipelineOptions options = PipelineOptionsFactory.create();
    // 顯式指定PipelineRunner:DirectRunner(Local模式)
    options.setRunner(DirectRunner.class);
    // 裝置管道
    Pipeline pipeline = Pipeline.create(options);
    // 讀取用戶信息數據集
    final PCollection<KV<String, String>> userInfoCollection =
        pipeline.apply(TextIO.read().from(args[0])).apply("userInfoCollection",
            MapElements.via(new SimpleFunction<String, KV<String, String>>() {
              @Override
              public KV<String, String> apply(String input) {
                // line format example:account|username
                String[] values = input.split("\\|");
                return KV.of(values[0], values[1]);
              }
            }));
    // 業務訂單數據集
    final PCollection<KV<String, String>> orderCollection =
        pipeline.apply(TextIO.read().from(args[1])).apply("orderCollection",
            MapElements.via(new SimpleFunction<String, KV<String, String>>() {
              @Override
              public KV<String, String> apply(String input) {
                // line format example: orderId|orderName|description
                String[] values = input.split("\\|");
                return KV.of(values[0], values[1]);
              }

            }));
    final TupleTag<String> userInfoTag = new TupleTag<String>();
    final TupleTag<String> orderTag = new TupleTag<String>();
    // 通過 beam提供CoGroupByKey實現對2組關係數據集(key/value)的join操作
    // beam SDK爲確保數據類型一致,強制數據集壓入KeyedPCollectionTuple
    final PCollection<KV<String, CoGbkResult>> cogrouppedCollection =
        KeyedPCollectionTuple.of(userInfoTag, userInfoCollection).and(orderTag, orderCollection)
            .apply(CoGroupByKey.<String>create());
    final PCollection<KV<String, String>> finalResultCollection = cogrouppedCollection.apply(
        "finalResultCollection", ParDo.of(new DoFn<KV<String, CoGbkResult>, KV<String, String>>() {
          @ProcessElement
          public void processElement(ProcessContext pc) {
            KV<String, CoGbkResult> e = pc.element();
            String account = e.getKey();
            String username = e.getValue().getOnly(userInfoTag);
            for (String order : pc.element().getValue().getAll(orderTag)) {
              pc.output(KV.of(account, username + "\t" + order));
            }
          }
        }));
    // 結果保存數據集
    PCollection<String> formattedResults = finalResultCollection.apply("formattedResults",
        ParDo.of(new DoFn<KV<String, String>, String>() {
          @ProcessElement
          public void processElement(ProcessContext c) {
            c.output(c.element().getKey() + " : " + c.element().getValue());
          }
        }));
    formattedResults.apply(TextIO.write().to("joinedResults"));
    pipeline.run().waitUntilFinish();
  }
}


如上是將用戶信息數據集與訂單信息數據集做join操作實戰單碼。

1、 管道工廠生產定製屬性管道(Pipeline)——指定數據處理Runner爲本地模式。

PipelineOptions options = PipelineOptionsFactory.create();

options.setRunner(DirectRunner.class);

2、 組裝數據引流管道

Pipeline pipeline = Pipeline.create(options);

3、 注入用戶數據集——這是beam最重要的Model模塊,就是指定數據的來源,及數據的結構。本例是有界固定大小的文本文件。

final PCollection<KV<String, String>> userInfoCollection= pipeline.apply

(TextIO.read().from(args[0])).apply("userInfoCollection",MapElements.via(newSimpleFunction<String, KV<String, String>>(){…...}));

final PCollection<KV<String, String>>orderCollection = pipeline.apply

(TextIO.read().from(args[1])).apply("orderCollection",MapElements.via(newSimpleFunction<String, KV<String, String>>() {……}));

4、 join處理——beam是通過Transforms範式,在管道中操作數據,用戶需以方法(函數)的形式提供處理邏輯對象(也就是“用戶代碼”)。本例中使用的是beam SDKs提供的通用方法CoGroupByKey實現對2組關係數據集(key/value)的join操作。

final PCollection<KV<String,CoGbkResult>>cogrouppedCollection = KeyedPCollectionTuple.of(userInfoTag,userInfoCollection).and(orderTag,orderCollection).apply(CoGroupByKey.<String>create());

5、 結果數據保存結構樣式——封裝數據處理結果爲自定義對象

PCollection<String> formattedResults = finalResultCollection.apply("forma

ttedResults",ParDo.of(new DoFn<KV<String,String>, String>(){…}));

6、 指定結果保存路徑

formattedResults.apply(TextIO.write().to("joinedResults"));

7、送入管道,分配計算引擎執行

pipeline.run().waitUntilFinish();

3.2.4部署運行

因爲Windows上的Beam2.0.0不支持本地路徑,故需要打包部署到Linux 上。

1、打jar包

2、準備待合併文本數據userInfo.txt和order.txt。文本內容如下:


3、執行指令:java -jar testJoin.jaruserInfo.txt order.txt。

執行結果如下圖:




4Apache Beam應用場景

Google Cloud、阿里巴巴、百度等巨頭公司都在使用Beam,Apache Beam 中文社區正在集成一些工作中的Runners和SDKs IO,包括人工智能、機器學習和時序數據庫等一些功能。以下爲應用場景的幾個例子:

1、 Beam可以用於ETL Job任務

Beam的數據可以通過SDKs的IO接入,通過管道可以用後面的Runners 做清洗。

2、Beam數據倉庫快速切換、跨倉庫

由於Beam的數據源是多樣IO,所以用Beam以快速切換任何數據倉庫。

3、Beam計算處理平臺切換、跨平臺

Runners目前提供了4種可以切換的常用平臺,隨着Beam的強大應該會有更多的平臺提供給大家使用。

5總結

1、Apache Beam的Beam Model將無限亂序數據流的數據處理抽象成“WWWH”四個維度,非常清晰與合理;

2、Beam Model統一了對無限數據流和有限數據集的處理模式,且明確了編程範式,擴大了流處理系統可應用的業務範圍,例如,Event-Time/Session窗口的支持,亂序數據的處理支持等。

3、Apache Beam集成了很多數據模型的一個統一化平臺,它爲大數據開發工程師頻繁換數據源或多數據源、多計算框架提供了集成統一框架平臺。

4、Apache Beam 主要針對理想並行的數據處理任務,並通過把數據集拆分多個子數據集,讓每個子數據集能夠被單獨處理,從而實現整體數據集的並行化處理。

5、Apache Beam也可以對數據源中數據讀取,自定義業務規則,對數據質量進行稽覈統計,保存數據質量信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章