Waterdrop推動Spark Structured Streaming走向生產環境

前言

StructuredStreaming是Spark 2.0以後新開放的一個模塊,相比SparkStreaming,它有一些比較突出的優點:

  • 它能做到更低的延遲;
  • 可以做實時的聚合,例如實時計算每天每個商品的銷售總額;
  • 可以做流與流之間的關聯,例如計算廣告的點擊率,需要將廣告的曝光記錄和點擊記錄關聯。

以上幾點如果使用SparkStreaming來實現可能會比較麻煩或者說是很難實現,但是使用 Structured Streaming 實現起來會比較輕鬆。

如何使用StructuredStreaming

可能你沒有詳細研究過StructuredStreaming,但是發現StructuredStreaming能很好的解決你的需求,如何快速利用StructuredStreaming來解決你的需求?目前社區有一款工具Waterdrop,項目地址:https://github.com/InterestingLab/waterdrop ,
可以高效低成本的幫助你利用StructuredStreaming來完成你的需求。

waterdrop

Waterdrop是一個非常易用,高性能,能夠應對海量數據的實時數據處理產品,它構建在Spark之上。Waterdrop擁有着非常豐富的插件,支持從Kafka、HDFS、Kudu中讀取數據,進行各種各樣的數據處理,並將結果寫入ClickHouse、Elasticsearch或者Kafka中

準備工作

首先我們需要安裝Waterdrop,安裝十分簡單,無需配置系統環境變量

  1. 準備Spark環境
  2. 安裝Waterdrop
  3. 配置Waterdrop
    以下是簡易步驟,具體安裝可以參照Quick Start
cd /usr/local
wget https://archive.apache.org/dist/spark/spark-2.2.0/spark-2.2.0-bin-hadoop2.7.tgz
tar -xvf https://archive.apache.org/dist/spark/spark-2.2.0/spark-2.2.0-bin-hadoop2.7.tgz
wget https://github.com/InterestingLab/waterdrop/releases/download/v1.3.0/waterdrop-1.3.0.zip
unzip waterdrop-1.3.0.zip
cd waterdrop-1.3.0

vim config/waterdrop-env.sh
# 指定Spark安裝路徑
SPARK_HOME=${SPARK_HOME:-/usr/local/spark-2.2.0-bin-hadoop2.7}

Waterdrop Pipeline

我們僅需要編寫一個Waterdrop Pipeline的配置文件即可完成數據的導入。

配置文件包括四個部分,分別是Spark、Input、filter和Output。

Spark

這一部分是Spark的相關配置,主要配置Spark執行時所需的資源大小。

spark {
  spark.app.name = "Waterdrop"
  spark.executor.instances = 2
  spark.executor.cores = 1
  spark.executor.memory = "1g"
}

Input

下面是一個從kafka讀取數據的例子

kafkaStream {
    topics = "waterdrop"
    consumer.bootstrap.servers = "localhost:9092"
    schema = "{\"name\":\"string\",\"age\":\"integer\",\"addrs\":{\"country\":\"string\",\"city\":\"string\"}}"
}

通過上面的配置就可以讀取kafka裏的數據了 ,topics是要訂閱的kafka的topic,同時訂閱多個topic可以以逗號隔開,consumer.bootstrap.servers就是Kafka的服務器列表,schema是可選項,因爲StructuredStreaming從kafka讀取到的值(官方固定字段value)是binary類型的,詳見http://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html
但是如果你確定你kafka裏的數據是json字符串的話,你可以指定schema,input插件將按照你指定的schema解析

Filter

下面是一個簡單的filter例子

filter{
    sql{
        table_name = "student"
        sql = "select name,age from student"
    }
}

table_name是註冊成的臨時表名,以便於在下面的sql使用

Output

處理好的數據往外輸出,假設我們的輸出也是kafka

output{
    kafka {
        topic = "waterdrop"
        producer.bootstrap.servers = "localhost:9092"
        streaming_output_mode = "update"
        checkpointLocation = "/your/path"
    }
}

topic 是你要輸出的topic,producer.bootstrap.servers是kafka集羣列表,streaming_output_mode是StructuredStreaming的一個輸出模式參數,有三種類型append|update|complete,具體使用參見文檔 output-modes.

checkpointLocation是Structured Streaming的checkpoint路徑,如果配置了這個參數,這個目錄會存儲程序的運行信息,比如程序退出再啓動的話會接着上次的offset進行消費。

場景分析

以上就是一個簡單的例子,接下來我們就來介紹的稍微複雜一些的業務場景

場景一:實時聚合場景

假設現在有一個商城,上面有10種商品,現在需要實時求每天每種商品的銷售額,甚至是求每種商品的購買人數(不要求十分精確)。
這麼做的巨大的優勢就是海量數據可以在實時處理的時候,完成聚合,再也不需要先將數據寫入數據倉庫,再跑離線的定時任務進行聚合,
操作起來還是很方便的。

kafka的數據如下

{"good_id":"abc","price":300,"user_id":123456,"time":1553216320}

那我們該怎麼利用waterdrop來完成這個需求呢,當然還是只需要配置就好了。

#spark裏的配置根據業務需求配置
spark {
  spark.app.name = "Waterdrop"
  spark.executor.instances = 2
  spark.executor.cores = 1
  spark.executor.memory = "1g"
}

#配置input
input {
    kafkaStream {
        topics = "good_topic"
        consumer.bootstrap.servers = "localhost:9092"
        schema = "{\"good_id\":\"string\",\"price\":\"integer\",\"user_id\":\"Long\",\"time\":\"Long\"}"
    }
}

#配置filter    
filter {
    
    #在程序做聚合的時候,內部會去存儲程序從啓動開始的聚合狀態,久而久之會導致OOM,如果設置了watermark,程序自動的會去清理watermark之外的狀態
    #這裏表示使用ts字段設置watermark,界限爲1天

    Watermark {
        time_field = "time"
        time_type = "UNIX"              #UNIX表示時間字段爲10爲的時間戳,還有其他的類型詳細可以查看插件文檔
        time_pattern = "yyyy-MM-dd"     #這裏之所以要把ts對其到天是因爲求每天的銷售額,如果是求每小時的銷售額可以對其到小時`yyyy-MM-dd HH`
        delay_threshold = "1 day"
        watermark_field = "ts"          #設置watermark之後會新增一個字段,`ts`就是這個字段的名字
    }
    
    #之所以要group by ts是要讓watermark生效,approx_count_distinct是一個估值,並不是精確的count_distinct
    sql {
        table_name = "good_table_2"
        sql = "select good_id,sum(price) total,	approx_count_distinct(user_id) person from good_table_2 group by ts,good_id"
    }
}

#接下來我們選擇將結果實時輸出到Kafka
output{
    kafka {
        topic = "waterdrop"
        producer.bootstrap.servers = "localhost:9092"
        streaming_output_mode = "update"
        checkpointLocation = "/your/path"
    }
}

如上配置完成,啓動waterdrop,就可以獲取你想要的結果了。

場景二:多個流關聯場景

假設你在某個平臺投放了廣告,現在要實時計算出每個廣告的CTR(點擊率),數據分別來自兩個topic,一個是廣告曝光日誌,一個是廣告點擊日誌,
此時我們就需要把兩個流數據關聯到一起做計算,而Waterdrop 最近也支持了此功能,讓我們一起看一下該怎麼做:

點擊topic數據格式

{"ad_id":"abc","click_time":1553216320,"user_id":12345}

曝光topic數據格式

{"ad_id":"abc","show_time":1553216220,"user_id":12345}
#spark裏的配置根據業務需求配置
spark {
  spark.app.name = "Waterdrop"
  spark.executor.instances = 2
  spark.executor.cores = 1
  spark.executor.memory = "1g"
}

#配置input
input {
    
    kafkaStream {
        topics = "click_topic"
        consumer.bootstrap.servers = "localhost:9092"
        schema = "{\"ad_id\":\"string\",\"user_id\":\"Long\",\"click_time\":\"Long\"}"
        table_name = "click_table"
    }
    
    kafkaStream {
        topics = "show_topic"
        consumer.bootstrap.servers = "localhost:9092"
        schema = "{\"ad_id\":\"string\",\"user_id\":\"Long\",\"show_time\":\"Long\"}"
        table_name = "show_table"
    }
}

filter {
    
    #左關聯右表必須設置watermark
    #右關左右表必須設置watermark
    #http://spark.apache.org/docs/latest/structured-streaming-programming-guide.html#inner-joins-with-optional-watermarking
    Watermark {
              source_table_name = "click_table" #這裏可以指定爲某個臨時表添加watermark,不指定的話就是爲input中的第一個
              time_field = "time"
              time_type = "UNIX"               
              delay_threshold = "3 hours"
              watermark_field = "ts" 
              result_table_name = "click_table_watermark" #添加完watermark之後可以註冊成臨時表,方便後續在sql中使用
    }
    
    Watermark {
                source_table_name = "show_table" 
                time_field = "time"
                time_type = "UNIX"               
                delay_threshold = "2 hours"
                watermark_field = "ts" 
                result_table_name = "show_table_watermark" 
     }
    
    
    sql {
        table_name = "show_table_watermark"
        sql = "select a.ad_id,count(b.user_id)/count(a.user_id) ctr from show_table_watermark as a left join click_table_watermark as b on a.ad_id = b.ad_id and a.user_id = b.user_id "
    }
    
}

#接下來我們選擇將結果實時輸出到Kafka
output {
    kafka {
        topic = "waterdrop"
        producer.bootstrap.servers = "localhost:9092"
        streaming_output_mode = "append" #流關聯只支持append模式
        checkpointLocation = "/your/path"
    }
}

通過配置,到這裏流關聯的案例也完成了。

結語

通過配置能很快的利用StructuredStreaming做實時數據處理,但是還是需要對StructuredStreaming的一些概念瞭解,比如其中的watermark機制,還有程序的輸出模式。

最後,waterdrop當然還支持spark streaming和spark 批處理。
如果你對這兩個也感興趣的話,可以閱讀我們以前發佈的文章

如何快速地將Hive中的數據導入ClickHouse
優秀的數據工程師,怎麼用Spark在TiDB上做OLAP分析
如何使用Spark快速將數據寫入Elasticsearch

希望瞭解 Waterdrop 和 HBase, ClickHouse、Elasticsearch、Kafka、MySQL 等數據源結合使用的更多功能和案例,可以直接進入項目主頁 https://github.com/InterestingLab/waterdrop 或者聯繫項目負責人:

Garyelephan 微信: garyelephant

kid-xiong 微信: why20131019

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