2019-02-28-Flink(5)——sink 介紹與實踐 概念 實戰 總結

本文轉自個人微信公衆號,原文鏈接。本博客評論系統需要梯子,大家關注下公衆號方便交流。

本文基於Apache Flink 1.7。

結合上一篇文章,Source 是 Flink 程序的輸入,Sink 就是 Flink 程序處理完Source後數據的輸出,比如將輸出寫到文件、sockets、外部系統、或者僅僅是顯示(在大數據生態中,很多類似的,比如Flume裏也是對應的Source/Channel/Sink),Flink 提供了多種數據輸出方式,下面逐一介紹。

概念

Flink 預定義 Sinks

  • 基於文件的:如 writeAsText()writeAsCsv()writeUsingOutputFormatFileOutputFormat
  • 寫到socket: writeToSocket
  • 用於顯示的:printprintToErr
  • 自定義Sink: addSink

對於write* 來說,主要用於測試程序,Flink 沒有實現這些方法的檢查點機制,也就沒有 exactly-once 支持。所以,爲了保證 exactly-once ,需要使用 flink-connector-filesystem,同時,自定義的addSink 也可以支持。

Connectors

connectors 用於給接入第三方數據提供接口,現在支持的connectors 包括:

  • Apache Kafka
  • Apache Cassandra
  • Elasticsearch
  • Hadoop FileSystem
  • RabbitMQ
  • Apache NiFi

另外,通過 Apache Bahir,可以支持Apache ActiveMQ、Apache Flume、Redis、Akka之類的Sink。

容錯

爲了保證端到端的 exactly-once,Sink 需要實現checkpoint 機制,下圖(圖片來自於官網)所示的Sink 實現了這點。


.

實戰

Elasticsearch Connector

下面我們將使用 Elasticsearch Connector 作爲Sink 爲例示範Sink的使用。Elasticsearch Connector 提供了at least once 語義支持,at lease once 支持需要用到Flink的checkpoint 機制。

要使用Elasticsearch Connector 需要根據Elasticsearch 版本添加依賴,如下圖所示(圖片來自官網)。

在這裏,我們使用的Elasticsearch 版本是5.6.9,Scala 版本2.11。

添加如下依賴:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-elasticsearch5_2.11</artifactId>
    <version>${flink.version}</version>
</dependency>

先看ElasticsearchSink 源碼,我們需要定義 ElasticsearchSinkFunction<T> 以及可選的 ActionRequestFailureHandler,ActionRequestFailureHandler 用來處理失敗的請求。

public class ElasticsearchSink<T> extends ElasticsearchSinkBase<T, TransportClient> {
    private static final long serialVersionUID = 1L;

    public ElasticsearchSink(Map<String, String> userConfig, List<InetSocketAddress> transportAddresses, ElasticsearchSinkFunction<T> elasticsearchSinkFunction) {
        this(userConfig, transportAddresses, elasticsearchSinkFunction, new NoOpFailureHandler());
    }

    public ElasticsearchSink(Map<String, String> userConfig, List<InetSocketAddress> transportAddresses, ElasticsearchSinkFunction<T> elasticsearchSinkFunction, ActionRequestFailureHandler failureHandler) {
        super(new Elasticsearch5ApiCallBridge(transportAddresses), userConfig, elasticsearchSinkFunction, failureHandler);
    }
}

下面看完整的例子:

package learn.sourcesAndsinks

import java.net.{InetAddress, InetSocketAddress}
import java.util

import org.apache.flink.api.common.functions.RuntimeContext
import org.apache.flink.streaming.connectors.elasticsearch.{ElasticsearchSinkFunction, RequestIndexer}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment, _}
import org.apache.flink.streaming.connectors.elasticsearch.util.IgnoringFailureHandler
import org.apache.flink.streaming.connectors.elasticsearch5.ElasticsearchSink
import org.elasticsearch.action.index.IndexRequest
import org.elasticsearch.client.Requests

object BasicSinks {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

    // 定義stream
    val stream: DataStream[String] = env.fromCollection(List("aaa", "bbb", "ccc"))

    // Elasticsearch 相關配置,ES 用 docker 起的,所以cluster.name 是默認的docker-cluster
    val config = new util.HashMap[String, String]()
    config.put("cluster.name", "docker-cluster")
    config.put("bulk.flush.max.actions", "1")
    val transportAddress = new util.ArrayList[InetSocketAddress]()
    transportAddress.add(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 9300))

    stream.addSink(new ElasticsearchSink(
      config,
      transportAddress,
      new ElasticsearchSinkFunction[String] {
        def createIndexRequest(element: String): IndexRequest = {
          val json = new util.HashMap[String, String]()
          json.put("data", element)

          return Requests.indexRequest()
            .index("my-index")
            .`type`("my-type")
            .source(json)
        }
        def process(element: String, ctx: RuntimeContext, indexer: RequestIndexer) = {
          indexer.add(createIndexRequest(element))
        }
      },
      // 忽略錯誤,示例用,不建議用於生產環境
      new IgnoringFailureHandler()
      ))

    env.execute()
  }
}

如下圖所示,是上面程序的結果。[圖片上傳失敗...(image-7c3e6e-1552137799207)]

上面實現了一個基礎的Elasticsearch Sink,爲了保證數據完整性,需要添加一些重試策略,這些主要跟 Elasticsearch 相關。

ES flush 相關配置

bulk.flush.max.actions

bulk.flush.max.size.mb

bulk.flush.interval.ms

ES 錯誤重試配置

bulk.flush.backoff.enable

bulk.flush.backoff.type

bulk.flush.backoff.delay

bulk.flush.backoff.retries

如果在此基礎上還需要處理Elasticsearch 的報錯,可以自己實現ActionRequestFailureHandler 方法。

總結

本文主要以 Flink Elasticsearch Connector 爲例講了Flink 裏的Sink,後面會對Source 和 Sink 進行源碼解讀。

看到這裏,請掃描下方二維碼關注我,Happy Friday !

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