使用Flink实现索引数据到Elasticsearch
使用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接口的定义,如下所示:
|
1
2
3
4
5
6
7
|
@Publicpublic 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的接口定义,如下所示:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
@Publicpublic 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文件中添加如下依赖即可:
|
1
2
3
4
5
|
<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代码,如下所示:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
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()方法代码,如下所示:
|
1
2
|
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文件中添加如下依赖:
|
01
02
03
04
05
06
07
08
09
10
|
<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,需要几个参数:
|
1
2
3
4
5
6
7
8
|
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中的功能,代码如下所示:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
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相关的配置:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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", 1); |
- 创建ElasticsearchOutputFormat
创建一个我们实现的ElasticsearchOutputFormat,代码片段如下所示:
|
1
2
3
4
5
6
7
8
|
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应用程序了,代码如下所示:
|
1
2
3
4
5
6
7
8
|
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
本文基于署名-非商业性使用-相同方式共享 4.0许可协议发布,欢迎转载、使用、重新发布,但务必保留文章署名时延军(包含链接:http://shiyanjun.cn),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。
使用Flink实现索引数据到Elasticsearch的更多相关文章
- Flink 之 写入数据到 ElasticSearch
前面 FLink 的文章中我们已经介绍了说 Flink 已经有很多自带的 Connector. 1.<从0到1学习Flink>—— Data Source 介绍 2.<从0到1学习F ...
- 《从0到1学习Flink》—— Flink 写入数据到 ElasticSearch
前言 前面 FLink 的文章中我们已经介绍了说 Flink 已经有很多自带的 Connector. 1.<从0到1学习Flink>-- Data Source 介绍 2.<从0到1 ...
- ES 18 - (底层原理) Elasticsearch写入索引数据的过程 以及优化写入过程
目录 1 Lucene操作document的流程 1.1 添加document的流程 1.2 删除document的流程 2 优化写入流程 - 实现近实时搜索 2.1 流程的改进思路 2.2 设置re ...
- 第六篇 elasticsearch express 删除索引数据
express 框架删除elasticsearch索引数据 1.在elasticsearch.js文件下添加 function deleteDocument(id) { return elasticC ...
- elasticsearch备份与恢复4_使用ES-Hadoop将ES中的索引数据写入HDFS中
背景知识见链接:elasticsearch备份与恢复3_使用ES-Hadoop将HDFS数据写入Elasticsearch中 项目参考<Elasticsearch集成Hadoop最佳实践> ...
- elasticsearch 索引数据多了怎么办,如何调优,部署 ?
面试官:想了解大数据量的运维能力. 解答:索引数据的规划,应在前期做好规划,正所谓"设计先行,编码在后", 这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户 ...
- elasticsearch 索引数据多了怎么办,如何调优,部署 ?
解答:索引数据的规划,应在前期做好规划,正所谓"设计先行,编码在后", 这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户 检索或者其他业务受到影响. 如何调优 ...
- 第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中
第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中 前面我们讲到的elasticsearch( ...
- 使用Logstash同步数据至Elasticsearch,Spring Boot中集成Elasticsearch实现搜索
安装logstash.同步数据至ElasticSearch 为什么使用logstash来同步,CSDN上有一篇文章简要的分析了以下几种同步工具的优缺点:https://blog.csdn.net/la ...
随机推荐
- Idea软件中隐藏忽略.idea,.iml等文件
参考链接,https://blog.csdn.net/fanrenxiang/article/details/80533950 ignore files and folders;增加隐藏的文件类型:
- Numpy库的下载与安装总结
今天在做Plotly的散点图时,需要Numpy 这个库的使用 没有安装Numpy这个库的时候,报错一般是下图这样:ModuleNotFoundError: No module named 'numpy ...
- C# 一般处理程序ashx接收服务端post过来json数据
这个和前端js的接收方式有点不一样,前端接收用request.form["xxx"]即可
- c/c++ 重载运算符 类型转换运算符
重载运算符 类型转换运算符 问题:能不能把一个类型A的对象a,转换成另一个类型B的对象b呢?? 是可以的.这就必须要用类型A的类型转换运算符(conversion operator) 下面的opera ...
- 爬虫之Requests&beautifulsoup
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模拟程序或者蠕 ...
- Python基础——1基础
1.基础 输出 print(‘把子肉爱上热干面’,‘哈哈’) # ‘,’输出为空格 输人 name = input(‘提示的内容’) /浮点除法 %.6f //地板除法 整除 % 取余 pyt ...
- HTML---标签的分类 | display | visibility
一.HTML标签的分类和转换 1.1,三类HTML标签 1.2,三类HTML标签的特点 1.3,三类标签的转换--display:none隐藏于visibility不同之处 二.HTML某些标签--不 ...
- spring boot 2.0 WebMvcConfigurerAdapter过时解决方法
第一种: @Configuration public class WebAppConfig implements WebMvcConfigurer{ @Bean public HandlerInter ...
- 用 C# 编写 C# 编译器,先有鸡还是先有蛋?
前段时间翻译了一篇文章 微软是如何重写 C# 编译器并使它开源的,文章讲了微软用 C# 重写 C# 编译器的坎坷路,引发了一些童鞋的思考:用 C# 编写 C# 编译器(Roslyn),那么 C# 编译 ...
- 程序员买房指南——LZ的三次买房和一次卖房经历
引言 买房,一直是程序员群体绕不开的一个话题,尤其是到了一定年纪和人生阶段以后,买房这件事会变得越来越迫切. 为什么LZ一上来就说,买房是程序员绕不开的一个话题? 其实原因很简单,由于程序员这个职业的 ...