一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情。
前言
昨天我们已经完成了使用kafka的分区listener来读取kafka的数据,同时我也提到了:要使用flink来消费kafka数据,下面我先简单介绍一下什么是flink以及为什么flink消费kafka比官方的listener都要快
什么是flink
- 定位:分布式处理引擎 flink使用至少一个job调度和至少一个task调度实现分布式处理
- 作用:用来处理有界和无界的数据 这里解释一下,有界和无界的数据处理流程
- 有界:就是指flink消费指定范围内的(如指定时间段内)数据。例如我定义某个作业间隔时间为0.5秒,则flink已0.5秒为界,进行数据处理。有界数据用在离线数据的处理场景较多
- 无界:就是指flink始终监听数据源里的数据,获取到就处理。无界数据往往用在实时数据处理下的场景较多。
我该如何选择用有界处理还是无界处理
我这里结合我们项目的场景来给各位说一下改选那种处理。我们的场景为:1:尽量支持最多的数据落,2:数据必须要准确。所以我们最终了有界处理,将flink的界限设置为0.5秒,0.5秒内收集的所有数据整体使用一个算子消费。保证数据的准确和消费高效性。
代码实现
相信大家如果在网上搜flink消费kafka数据时,搜索的大部分代码差不多都是如下所示的
下面我们来把它优化一下,让它可以“有界”消费kafka数据
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(9);
env.enableCheckpointing(1000);
Properties properties = new Properties();
properties.put("bootstrap.servers", "192.168.131.147:9092");
properties.put("group.id", "flink-consumer-kafka-group");
properties.put("auto.offset.reset", "latest");
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>("demo", new SimpleStringSchema(), properties);
//消费最新数据
consumer.setStartFromLatest();
DataStream<String> stream = env.addSource(consumer);
// 批量读取的方法
stream
//timeWindowAll:时间滚动窗口,滑动窗口会有数据元素重叠可能,而滚动窗口不存在元素重叠
.timeWindowAll(Time.milliseconds(500))
//使用自己定义的apply来收集
.apply(new ReadKafkaFlinkWindowFunction())
//批量的sink方法
.addSink(new KafkaBatchSink());
env.execute();
ReadKafkaFlinkWindowFunction.java
package com.example.data.demo.flink.consumer_kafka;
import org.apache.flink.streaming.api.functions.windowing.AllWindowFunction;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
/**
* 自定义读取kafka的flink window function
*
* @author lmh
*/
public class ReadKafkaFlinkWindowFunction implements AllWindowFunction<String, Iterable<String>, TimeWindow> {
private static final long serialVersionUID = -953759646242892986L;
@Override
public void apply(TimeWindow timeWindow, Iterable<String> values, Collector<Iterable<String>> out) throws Exception {
out.collect(values);
}
}
KafkaBatchSink.java
package com.example.data.demo.flink.consumer_kafka;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.shaded.curator4.com.google.common.collect.Iterables;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
/**
* @author lmh
*/
public class KafkaBatchSink extends RichSinkFunction<Iterable<String>> {
private static final long serialVersionUID = 4421299218123146892L;
/**
* 重写open方法定义自己需要初始化的值
*
* @param config flink的config
* @throws Exception
*/
@Override
public void open(Configuration config) throws Exception {
}
/**
* 此处负责处理读取到的数据,这里是批量读取
*
* @param values 读取到的值
* @param context context
* @throws Exception
*/
@Override
public void invoke(Iterable<String> values, Context context) throws Exception {
System.out.println("KafkaBatchSink.invoke" + Iterables.size(values));
}
/**
* 关闭连接的方法,例如hbase连接等
*
* @throws Exception
*/
@Override
public void close() throws Exception {
}
}
现在,我们就可以使用flink来批量有界消费kafka消息了。但是最后有几个总计需要给大家说明一下
- 一定要有抛出异常的机制:我们都知道抛出异常会终止消费,但是为什么要抛出异常呢?这注意是因为如果用户不抛出异常的话,flink会认为当前的数据时正常消费的,这就造成了我们的kafka数据误消费
- 关于并行度parallelism:并行度的配置都是setParallelism,对于env和stream来说,stream的优先级比env高
- 关于checkpoint:我们如果定义程序运行在SPring Boot时,一定要配置检查点这个是flink实现容错的核心配置!
- 关于并行度:我们在设置并行度的时候,将里边的数字设置为多少,最终就会有多少个线程来执行任务。所以大家一定要清楚对于数据准确性高的数据来说,宁愿牺牲多线程带来的效率提升也要只设置一个线程来执行消费。可能大家没有注意,如果你不设置flink的并行度为1时。它是以的是系统的线程数来作为并行度!
- saveBatch很好,但是我建议你先封装一下或者改为批量的保存。可能大家都知道或者说都用过mybatis plus的saveBatch,它能将一个列表的inseert封装为一条sql(insert into a values(a1),(a2),(a3)), 但是我们一条sql的长度过长的话会存在性能问题。建议在批量处理的时候每隔1000条记录saveBatch一次
结语
今天我们主要学习了flink批量消费kafka的方法,最终我们使用批量消费实现了5W/S的数据落盘,当然其中有别的优化细节。这里就不赘述了,截至今天,我们大数据的入门案例,消费kafka相关的文章就写完了。明天我准备介绍一下我们项目在使用jedis时常用的优化方案。欢迎大家多多关注,点赞