【Flink】使用Flink实现索引数据到Elasticsearch

556 篇文章 544 订阅 ¥79.90 ¥99.00

在这里插入图片描述

1.概述

转载:使用Flink实现索引数据到Elasticsearch 建议看原文

使用Flink处理数据时,可以基于Flink提供的批式处理(Batch Processing)和流式处理(Streaming Processing)API来实现,分别能够满足不同场景下应用数据的处理。这两种模式下,输入处理都被抽象为Source Operator,包含对应输入数据的处理逻辑;输出处理都被抽象为Sink Operator,包含了对应输出数据的处理逻辑。这里,我们只关注输出的Sink Operator实现。

Flink批式处理模式,运行Flink Batch Job时作用在有界的输入数据集上,所以Job运行的时间是有时限的,一旦Job运行完成,对应的整个数据处理应用就已经结束,比如,输入是一个数据文件,或者一个Hive SQL查询对应的结果集,等等。在批式处理模式下处理数据的输出时,主要需要实现一个自定义的OutputFormat,然后基于该OutputFormat来构建一个Sink,下面看下OutputFormat接口的定义,如下所示:


@Public
public interface OutputFormat<IT> extends Serializable {
    void configure(Configuration parameters);
    void open(int taskNumber, int numTasks) throws IOException;
    void writeRecord(IT record) throws IOException;
    void close() throws IOException;
}

上面,configure()方法用来配置一个OutputFormat的一些输出参数;open()方法用来实现与外部存储系统建立连接;writeRecord()方法用来实现对Flink Batch Job处理后,将数据记录输出到外部存储系统。开发Batch Job时,通过调用DataSet的output()方法,参数值使用一个OutputFormat的具体实现即可。后面,我们会基于Elasticsearch来实现上面接口中的各个方法。

Flink流式处理模式,运行Flink Streaming Job时一般输入的数据集为流数据集,也就是说输入数据元素会持续不断地进入到Streaming Job的处理过程中,但你仍然可以使用一个HDFS数据文件作为Streaming Job的输入,即使这样,一个Flink Streaming Job启动运行后便会永远运行下去,除非有意外故障或有计划地操作使其终止。在流式处理模式下处理数据的输出时,我们需要是实现一个SinkFunction,它指定了如下将流数据处理后的结果,输出到指定的外部存储系统中,下面看下SinkFunction的接口定义,如下所示:

@Public
public interface SinkFunction<IN> extends Function, Serializable {
    @Deprecated
    default void invoke(IN value) throws Exception {}
    default void invoke(IN value, Context context) throws Exception {
        invoke(value);
    }
 
    @Public
    interface Context<T> {
        long currentProcessingTime();
        long currentWatermark();
        Long timestamp();
    }
}

通过上面接口可以看到,需要实现一个invoke()方法,实现该方法来将一个输入的IN value输出到外部存储系统中。一般情况下,对一些主流的外部存储系统,Flink实现了一下内置(社区贡献)的SinkFunction,我们只需要配置一下就可以直接使用。而且,对于Streaming Job来说,实现的SinkFunction比较丰富一些,可以减少自己开发的工作量。开发Streaming Job时,通过调用DataStream的addSink()方法,参数是一个SinkFlink的具体实现。

下面,我们分别基于批式处理模式和批式处理模式,分别使用或实现对应组件将Streaming Job和Batch Job的处理结果输出到Elasticsearch中:

基于Flink DataSteam API实现

在开发基于Flink的应用程序过程中,发现Flink Streaming API对Elasticsearch的支持还是比较好的,比如,如果想要从Kafka消费事件记录,经过处理最终将数据记录索引到Elasticsearch 5.x,可以直接在Maven的POM文件中添加如下依赖即可:


<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-connector-elasticsearch5_2.11</artifactId>
   <version>1.5.3</version>
 </dependency>

我们使用Flink Streaming API来实现将流式数据处理后,写入到Elasticsearch中。其中,输入数据源是Kafka中的某个Topic;输出处理结果到lasticsearch中,我们使用使用Transport API的方式来连接Elasticsearch,需要指定Transport地址和端口。具体实现,对应的Scala代码,如下所示:


def main(args: Array[String]): Unit = {
  // parse input arguments
  val params = ParameterTool.fromArgs(args)
 
  if (params.getNumberOfParameters < 9) {
    val cmd = getClass.getName
    println("Missing parameters!\n"
      + "Usage: " + cmd
      + " --input-topic <topic> "
      + "--es-cluster-name <es cluster name> "
      + "--es-transport-addresses <es address> "
      + "--es-port <es port> "
      + "--es-index <es index> "
      + "--es-type <es type> "
      + "--bootstrap.servers <kafka brokers> "
      + "--zookeeper.connect <zk quorum> "
      + "--group.id <some id> [--prefix <prefix>]")
    return
  }
 
  val env = StreamExecutionEnvironment.getExecutionEnvironment
 
  val kafkaConsumer = new FlinkKafkaConsumer010[String](
    params.getRequired("input-topic"),
    new SimpleStringSchema(),
    params.getProperties
  )
 
  val dataStream = env
    .addSource(kafkaConsumer)
    .filter(!_.isEmpty)
 
  val esClusterName = params.getRequired("es-cluster-name")
  val esAddresses = params.getRequired("es-transport-addresses")
  val esPort = params.getInt("es-port", 9300)
  val transportAddresses = new java.util.ArrayList[InetSocketAddress]
 
  val config = new java.util.HashMap[String, String]
  config.put("cluster.name", esClusterName)
  // This instructs the sink to emit after every element, otherwise they would be buffered
  config.put("bulk.flush.max.actions", "100")
 
  esAddresses.split(",").foreach(address => {
    transportAddresses.add(new InetSocketAddress(InetAddress.getByName(address), esPort))
  })
  val esIndex = params.getRequired("es-index")
  val esType = params.getRequired("es-type")
  val sink = new ElasticsearchSink(config, transportAddresses, new ElasticsearchSinkFunction[String] {
 
    def createIndexRequest(element: String): IndexRequest = {
      return Requests.indexRequest()
        .index(esIndex)
        .`type`(esType)
        .source(element)
    }
 
    override def process(t: String, runtimeContext: RuntimeContext, requestIndexer: RequestIndexer): Unit = {
      requestIndexer.add(createIndexRequest(t))
    }
  })
  dataStream.addSink(sink)
 
  val jobName = getClass.getSimpleName
  env.execute(jobName)
}

上面有关数据索引到Elasticsearch的处理中, 最核心的就是创建一个ElasticsearchSink,然后通过DataStream的API调用addSink()添加一个Sink,实际是一个SinkFunction的实现,可以参考Flink对应DataStream类的addSink()方法代码,如下所示:

def addSink(sinkFunction: SinkFunction[T]): DataStreamSink[T] =
  stream.addSink(sinkFunction)

基于Flink DataSet API实现

目前,Flink还没有在Batch处理模式下实现对应Elasticsearch对应的Connector,需要自己根据需要实现,所以我们基于Flink已经存在的Streaming处理模式下已经实现的Elasticsearch Connector对应的代码,经过部分修改,可以直接拿来在Batch处理模式下,将数据记录批量索引到Elasticsearch中。

我们基于Flink 1.6.1版本,以及Elasticsearch 6.3.2版本,并且使用Elasticsearch推荐的High Level REST API来实现(为了复用Flink 1.6.1中对应的Streaming处理模式下的Elasticsearch 6 Connector实现代码,我们选择使用该REST Client),需要在Maven的POM文件中添加如下依赖:

<dependency>
  <groupId>org.elasticsearch</groupId>
  <artifactId>elasticsearch</artifactId>
  <version>6.3.2</version>
</dependency>
<dependency>

  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>6.3.2</version>

</dependency>

我们实现的各个类的类图及其关系,如下图所示:

在这里插入图片描述
如果熟悉Flink Streaming处理模式下Elasticsearch对应的Connector实现,可以看到上面的很多类都在org.apache.flink.streaming.connectors.elasticsearch包里面存在,其中包括批量向Elasticsearch中索引数据(内部实现了使用BulkProcessor)。上图中引入的ElasticsearchApiCallBridge,目的是能够实现对Elasticsearch不同版本的支持,只需要根据Elasticsearch不同版本中不同Client实现,进行一些适配,上层抽象保持不变。

如果需要在Batch处理模式下批量索引数据到Elasticsearch,可以直接使用ElasticsearchOutputFormat即可实现。但是创建ElasticsearchOutputFormat,需要几个参数:

private ElasticsearchOutputFormat(
    Map<String, String> bulkRequestsConfig,
    List<HttpHost> httpHosts,
    ElasticsearchSinkFunction<T> elasticsearchSinkFunction,
    DocWriteRequestFailureHandler failureHandler,
    RestClientFactory restClientFactory) {
  super(new Elasticsearch6ApiCallBridge(httpHosts, restClientFactory),  bulkRequestsConfig, elasticsearchSinkFunction, failureHandler);
}


当然,我们可以通过代码中提供的Builder来非常方便的创建一个ElasticsearchOutputFormat。下面,我们看下我们Flink Batch Job实现逻辑。

实现ElasticsearchSinkFunction

我们需要实现ElasticsearchSinkFunction接口,实现一个能够索引数据到Elasticsearch中的功能,代码如下所示:

final ElasticsearchSinkFunction<String> elasticsearchSinkFunction = new ElasticsearchSinkFunction<String>() {
 
   @Override
   public void process(String element, RuntimeContext ctx, RequestIndexer indexer) {
     indexer.add(createIndexRequest(element, parameterTool));
   }
 
   private IndexRequest createIndexRequest(String element, ParameterTool parameterTool) {
     LOG.info("Create index req: " + element);
     JSONObject o = JSONObject.parseObject(element);
     return Requests.indexRequest()
             .index(parameterTool.getRequired("es-index"))
             .type(parameterTool.getRequired("es-type"))
             .source(o);
   }
 };

上面代码,主要是把一个将要输出的数据记录,通过RequestIndexer来实现索引到Elasticsearch中。

读取Elasticsearch配置参数

配置连接Elasticsearch的参数。从程序输入的ParameterTool中读取Elasticsearch相关的配置:

String esHttpHosts = parameterTool.getRequired("es-http-hosts");
LOG.info("Config: esHttpHosts=" + esHttpHosts);
int esHttpPort = parameterTool.getInt("es-http-port", 9200);
LOG.info("Config: esHttpPort=" + esHttpPort);
 
final List<HttpHost> httpHosts = Arrays.asList(esHttpHosts.split(","))
        .stream()
        .map(host -> new HttpHost(host, esHttpPort, "http"))
        .collect(Collectors.toList());
 
int bulkFlushMaxSizeMb = parameterTool.getInt("bulk-flush-max-size-mb", 10);
int bulkFlushIntervalMillis = parameterTool.getInt("bulk-flush-interval-millis", 10 * 1000);
int bulkFlushMaxActions = parameterTool.getInt("bulk-flush-max-actions"

创建ElasticsearchOutputFormat

创建一个我们实现的ElasticsearchOutputFormat,代码片段如下所示:


final ElasticsearchOutputFormat outputFormat = new Builder<>(httpHosts, elasticsearchSinkFunction)
        .setBulkFlushBackoff(true)
        .setBulkFlushBackoffRetries(2)
        .setBulkFlushBackoffType(ElasticsearchApiCallBridge.FlushBackoffType.EXPONENTIAL)
        .setBulkFlushMaxSizeMb(bulkFlushMaxSizeMb)
        .setBulkFlushInterval(bulkFlushIntervalMillis)
        .setBulkFlushMaxActions(bulkFlushMaxActions)
        .build();

上面很多配置项指定了向Elasticsearch中进行批量写入的行为,在ElasticsearchOutputFormat内部会进行设置并创建Elasticsearch6BulkProcessorIndexer,优化索引数据处理的性能。

实现Batch Job主控制流程

最后我们就可以构建我们的Flink Batch应用程序了,代码如下所示:

ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.readTextFile(file)
    .filter(line -> !line.isEmpty())
    .map(line -> line)
    .output(outputFormat);
 
final String jobName = ImportHDFSDataToES.class.getSimpleName();
env.execute(jobName);

我们输入的HDFS文件中,是一些已经加工好的JSON格式记录行,这里为了简单,直接将原始JSON字符串索引到Elasticsearch中,而没有进行更多其他的处理操作。

有关Flink批式处理模式下,Elasticsearch对应的OutputFormat实现的完整代码,可以参考这里:
https://github.com/shirdrn/flink-app-jobs/tree/master/src/main/java/org/shirdrn/flink/connector/batch/elasticsearch。

参考链接

https://ci.apache.org/projects/flink/flink-docs-release-1.5/dev/connectors/elasticsearch.html

https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/batch/#data-sinks

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要使用Flink数据索引Elasticsearch,你需要使用FlinkElasticsearch connector,该connector可以在Flink的官方文档中找到。 以下是实现索引数据Elasticsearch的步骤: 1. 首先,你需要创建一个Flink程序,这个程序可以连接到数据源,例如Kafka或者其他的数据源。你需要使用Flink的DataStream API来处理数据。 2. 在程序中,使用Elasticsearch connector将数据写入Elasticsearch。要使用Elasticsearch connector,你需要在pom.xml文件中添加以下依赖项: ``` <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-elasticsearch7_2.12</artifactId> <version>1.13.0</version> </dependency> ``` 3. 在程序中,使用Elasticsearch connector将数据写入Elasticsearch。以下是使用Elasticsearch connector将数据写入Elasticsearch的示例代码: ``` DataStream<Tuple2<String, Integer>> dataStream = ... //从数据源获取数据 //将数据转换为Elasticsearch需要的格式 DataStream<JSONObject> esDataStream = dataStream.map(new MapFunction<Tuple2<String, Integer>, JSONObject>() { @Override public JSONObject map(Tuple2<String, Integer> value) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("word", value.f0); jsonObject.put("count", value.f1); return jsonObject; } }); //将数据写入Elasticsearch List<HttpHost> httpHosts = new ArrayList<>(); httpHosts.add(new HttpHost("localhost", 9200, "http")); esDataStream.addSink( new ElasticsearchSink.Builder<JSONObject>(httpHosts, new ElasticsearchSinkFunction<JSONObject>() { public IndexRequest createIndexRequest(JSONObject element) { return Requests.indexRequest() .index("my-index") .type("_doc") .source(element.toJSONString(), XContentType.JSON); } @Override public void process(JSONObject element, RuntimeContext ctx, RequestIndexer indexer) { indexer.add(createIndexRequest(element)); } }).build() ); ``` 在这个例子中,我们将每个单词的计数写入Elasticsearch。要将数据写入Elasticsearch,我们需要将数据转换为JSON格式,并使用ElasticsearchSinkFunction将数据写入Elasticsearch。在ElasticsearchSinkFunction中,我们需要实现createIndexRequest方法,它将数据转换为IndexRequest对象,然后使用RequestIndexer将IndexRequest发送到Elasticsearch。 4. 启动Flink程序,并等待数据索引Elasticsearch。 这就是使用Flink数据索引Elasticsearch的步骤。注意,在实际生产环境中,你可能需要处理更复杂的数据并在Elasticsearch中建立更复杂的索引。 ### 回答2: Flink是一个开源的流处理框架,具有高效、可扩展和容错等特性。使用Flink可以将索引数据实时发送到Elasticsearch。 为了实现索引数据Elasticsearch,我们需要进行以下步骤: 1. 连接到数据源:首先,我们需要从数据源获取索引数据。可以是日志文件、消息队列或其他流式数据源。借助Flink的连接器,我们可以轻松地从这些数据源中读取数据。 2. 数据转换和处理:接下来,我们需要对获取的数据进行转换和处理。可以使用Flink的转换操作对数据进行清洗、过滤、格式化等操作,以使其适合索引Elasticsearch。 3. 将数据发送到Elasticsearch:一旦数据转换和处理完成,我们就可以使用Flink提供的Elasticsearch连接器将数据发送到Elasticsearch。连接器会自动将数据批量发送到Elasticsearch集群中的相应索引。 4. 容错和恢复:在数据处理过程中,可能会出现故障或网络中断等情况。Flink提供了容错机制,可以保证数据处理的高可用性和可靠性。如果出现故障,Flink会自动恢复并重新处理丢失的数据使用Flink实现索引数据Elasticsearch具有以下优势: 1. 实时性:Flink作为一个流处理框架,可以使索引数据几乎实时地传输到Elasticsearch,确保数据的最新性。 2. 可扩展性:Flink具有良好的扩展性,可以处理大规模的数据,并且可以根据需要动态地扩展集群规模。 3. 容错性:Flink的容错机制可以保证在发生故障时数据的安全性和可恢复性,避免数据丢失或损坏。 总结而言,使用Flink可以轻松地将索引数据实时发送到Elasticsearch,并享受其高效、可扩展和容错的优势。 ### 回答3: 使用Flink实现索引数据Elasticsearch是一个相对简单且高效的过程。Flink是一个实时流处理框架,可以通过连接到数据源,并以流式方式处理和转换数据。 首先,我们需要连接到数据源。可以通过Flink提供的API或者适配器来连接到不同类型的数据源,如Kafka、RabbitMQ等。一旦连接到数据源,我们可以使用Flink的DataStream API将数据流转换为可供索引的格式。 接下来,我们需要将转换后的数据流发送到Elasticsearch进行索引。可以使用FlinkElasticsearch连接器来实现此功能。该连接器提供了一种将数据流中的记录自动索引Elasticsearch的方式。 为了使用Elasticsearch连接器,我们需要在Flink作业中添加相应的依赖。然后,在代码中配置Elasticsearch连接和索引的相关信息,如主机地址、索引名称等。一旦配置完成,我们可以使用DataStream的addSink()方法将数据流发送到Elasticsearch。 在将数据流发送到Elasticsearch之前,可以进行一些额外的转换和处理。例如,可以对数据流进行过滤、映射或聚合操作,以便索引数据满足特定的需求。 最后,运行Flink作业并监控其运行状态。一旦作业开始运行,Flink将自动将数据流中的记录发送到Elasticsearch进行索引使用Flink实现索引数据Elasticsearch的好处是它提供了流式处理的能力,能够实时处理和索引数据。另外,Flink还提供了容错和恢复机制,以确保数据的准确性和可靠性。 总之,通过Flink实现索引数据Elasticsearch是一种快速、简单且高效的方法,可以帮助我们充分利用实时流数据并实时索引Elasticsearch中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值