博客 flink 读取文件数据写入ElasticSearch

flink 读取文件数据写入ElasticSearch

   数栈君   发表于 2023-11-07 10:08  769  0

一、ElasticSearchSink介绍


在使用Flink进行数据的处理的时候,一个必要步骤就是需要将计算的结果进行存储或导出,Flink中这个过程称为Sink,官方我们提供了常用的几种Sink Connector,例如:


  • Apache Kafka
  • Elasticsearch
  • Elasticsearch 2x
  • Hadoop FileSystem

这篇就选取其中一个常用的ElasticsearchSink来进行介绍,并讲解一下生产环境中使用时的一些注意点,以及其内部实现机制。


二、使用方式


a、添加pom依赖


        <dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-connector-elasticsearch2_2.10artifactId>
<version>1.3.1version>
dependency>


  • 1
  • 2
  • 3
  • 4
  • 5

根据自己所用的filnk版本以及es版本对上面的版本号进行调整


b、实现对应代码


DataStream<String> input = ...;
Map<String, String> config = new HashMap<>();
config.put("cluster.name", "my-cluster-name");
//该配置表示批量写入ES时的记录条数
config.put("bulk.flush.max.actions", "1");
List<InetSocketAddress> transportAddresses = new ArrayList<>();
transportAddresses.add(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 9300));
transportAddresses.add(new InetSocketAddress(InetAddress.getByName("10.2.3.1"), 9300));
input.addSink(new ElasticsearchSink<>(config, transportAddresses, new ElasticsearchSinkFunction<String>() {
public IndexRequest createIndexRequest(String element) {
Map<String, String> json = new HashMap<>();
//将需要写入ES的字段依次添加到Map当中
json.put("data", element);
return Requests.indexRequest()
.index("my-index")
.type("my-type")
.source(json);
}
@Override
public void process(String element, RuntimeContext ctx, RequestIndexer indexer) {
indexer.add(createIndexRequest(element));
}
}));






http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/e932ecbd1ed10c4e6d91b7b41650af77..png


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

c、扩展配置


经过上面的代码已经实现了一个基础版的EsSink,但是上述代码当ES集群出现波动的时候,由于不具备重试机制则有可能出现丢数据的情况。生产环境中为了实现数据完整性,我们需要添加一些失败重试配置,来实现写入失败情况下的容错处理,常用的失败重试配置有:


//1、用来表示是否开启重试机制
config.put("bulk.flush.backoff.enable", "true");
//2、重试策略,又可以分为以下两种类型
//a、指数型,表示多次重试之间的时间间隔按照指数方式进行增长。eg:2 -> 4 -> 8 ...
config.put("bulk.flush.backoff.type", "EXPONENTIAL");
//b、常数型,表示多次重试之间的时间间隔为固定常数。eg:2 -> 2 -> 2 ...
config.put("bulk.flush.backoff.type", "CONSTANT");
//3、进行重试的时间间隔。对于指数型则表示起始的基数
config.put("bulk.flush.backoff.delay", "2");
//4、失败重试的次数
config.put("bulk.flush.backoff.retries", "3");


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

其他的一些配置:



bulk.flush.max.actions: 批量写入时的最大写入条数


bulk.flush.max.size.mb: 批量写入时的最大数据量


bulk.flush.interval.ms: 批量写入的时间间隔,配置后则会按照该时间间隔严格执行,无视上面的两个批量写入配置



三、失败处理器


写入ES的时候很多时候由于ES集群队列满了,或者节点挂掉,经常会导致写入操作执行失败。考虑到这样的失败写入场景,EsSink为用户提供了失败处理器机制,创建Sink对象的时候,同时可以传入一个失败处理器,一旦出现写入失败的情况则会回调所传入的处理器用于错误恢复。具体的用法为:


DataStream input = ...;
input.addSink(new ElasticsearchSink<>(
config, transportAddresses,
new ElasticsearchSinkFunction() {

...},
new ActionRequestFailureHandler() {
@Override
void onFailure(ActionRequest action,
Throwable failure,
int restStatusCode,
RequestIndexer indexer) throw Throwable {
if (ExceptionUtils.containsThrowable(failure, EsRejectedExecutionException.class)) {
// 将失败请求继续加入队列,后续进行重试写入
indexer.add(action);
} else if (ExceptionUtils.containsThrowable(failure, ElasticsearchParseException.class)) {
// 添加自定义的处理逻辑
} else {
throw failure;
}
}
}));






http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/e932ecbd1ed10c4e6d91b7b41650af77..png


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

如果仅仅只是想做失败重试,也可以直接使用官方提供的默认的 RetryRejectedExecutionFailureHandler ,该处理器会对 EsRejectedExecutionException 导致到失败写入做重试处理。


四、其他注意点


1、EsSink代码块不能使用try-catch-Exception来捕捉


之前在使用EsSink的时候,为了防止某次写入失败造成程序中断,对ElasticsearchSinkFunction的 process() 方法使用try-catch-exception语句块进行了捕捉,但实际运行的时候发现程序跑着跑着还是被一个 EsRejectedException 异常中断掉了。让人奇怪的是明明对异常进行了捕捉,为什么这个异常还是能够抛出来,下来通过查看源码发现,如果在初始化EsSink对象的时候没有传入 ActionRequestFailureHandler 则会使用默认的 ActionRequestFailureHandler ,这个处理器的源码如下:


public class NoOpFailureHandler implements ActionRequestFailureHandler {


private static final long serialVersionUID = 737941343410827885L;
@Override
public void onFailure(ActionRequest action, Throwable failure, int restStatusCode, RequestIndexer indexer) throws Throwable {
// 这里抛出的是一个throwable
throw failure;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以看到,在发生异常的时候,默认的处理器会将异常包装成一个 Throw 对象抛出,这就是直接使用 try-Exception 无法捕捉到的原因。


解决方法:


  • 实现自己的失败处理器消化掉异常
  • 使用 throw 来捕捉异常

该问题一定要重点注意,负责会导致实时任务终止掉!



2、失败重试机制依赖于checkpoint


如果想要使用EsSink的失败重试机制,则需要通过env.enableCheckpoint()方法来开启Flink任务对checkpoint的支持,如果没有开启checkpoint机制的话,则失败重试策略是无法生效的。这个是通过跟踪 ElasticsearchSinkBase 类源码的时候发现的,核心的代码如下:


@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
// no initialization needed
}
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
checkErrorAndRethrow();
//如果没有开启checkPoint机制,则该变量为false,也就导致下面的flush重试代码不会执行到
if (flushOnCheckpoint) {
do {
//失败重试的时机是发生在程序在打checkpoint的时候
bulkProcessor.flush();
checkErrorAndRethrow();
} while (numPendingRequests.get() != 0);
}
}






http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/e932ecbd1ed10c4e6d91b7b41650af77..png


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3、 经验学习


可以通过第二点贴出的源码发现,虽然EsSink实现了 CheckpointedFunction
接口,并且重写了checkPoint的相关方法,但其并没有墨守成规的利用checkpoint定义的那样利用State机制用于故障恢复。而是利用了checkpoint的空壳,定时执行的框架来实现了自己的一套失败重试机制。这一点很值得我们借鉴,很多知识点要学会活学活用,他山之石可以攻玉!!!

免责申明:


本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!

《数据治理行业实践白皮书》下载地址:https://fs80.cn/4w2atu

《数栈V6.0产品白皮书》下载地址:
https://fs80.cn/cw0iw1

想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:
https://www.dtstack.com/?src=bbs

同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术群」,交流最新开源技术信息,群号码:30537511,项目地址:
https://github.com/DTStack

0条评论
社区公告
  • 大数据领域最专业的产品&技术交流社区,专注于探讨与分享大数据领域有趣又火热的信息,专业又专注的数据人园地

最新活动更多
微信扫码获取数字化转型资料
钉钉扫码加入技术交流群