Flink消费kafka写入HDFS初步探究

主要maven依赖

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

先上代码

import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.fs.bucketing.BucketingSink;
import org.apache.flink.streaming.connectors.fs.bucketing.DateTimeBucketer;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer010;

import java.time.ZoneId;
import java.util.Properties;

public class WriteToHDFS {


    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        ParameterTool params = ParameterTool.fromArgs(args);

        env.enableCheckpointing(30000L);

        //消费kafka
        String topic = "MyTestData";
        Properties prop = new Properties();
        prop.setProperty("bootstrap.servers", "node223:6667,node224:6667,node225:6667");
        prop.setProperty("group.id", "xxxx");
        FlinkKafkaConsumer010<String> myConsumer = new FlinkKafkaConsumer010<>(topic, new SimpleStringSchema(), prop);
        DataStreamSource<String> dataSource = env.addSource(myConsumer);

        BucketingSink<String> sink = new BucketingSink<>(params.get("path","/user/root/flink/writetohdfs"));

        //创建一个按照时间创建目录的bucketer,默认是yyyy-MM-dd--HH,时区默认是美国时间。这里我都改了,一天创建一次目录,上海时间
        sink.setBucketer(new DateTimeBucketer<String>("yyyy-MM-dd", ZoneId.of("Asia/Shanghai")));
        /**
         * 设置大小和滚动时间,只要满足一个,就会滚动,和flume差不多
         */
        //设置每个文件的最大大小 ,默认是384M(1024 * 1024 * 384)
        sink.setBatchSize(1024 * 20); //this is 20KB
        //设置多少时间,就换一个文件写  但是ms  这里我设置的是 一个小时
        sink.setBatchRolloverInterval(1000 * 60 * 60);
//        sink.setPendingSuffix("ccc");
//        sink.setInactiveBucketThreshold(60 * 1000L);
//        sink.setInactiveBucketCheckInterval(60 * 1000L);
//        sink.setAsyncTimeout(60 * 1000);
        dataSource.addSink(sink);

        env.execute("WriteToHDFS");
    }
}

源码分析

1.写入HDFS时,不用加其他HDFS的依赖,只要是master是yarn-cluster即可

2.使用的是FSDataOutputStream,数据写入时,会写入缓冲区,放在内存中

3.每次做checkpoint的时候,会flush缓存区的数据,以及将pending(已经完成的文件,但未被checkpoint记录,可以通过sink.setPendingSuffix("ccc"); 设置)结尾的文件,记录下来

4.每60秒(可以通过sink.setInactiveBucketCheckInterval(60 * 1000L);去设置) 检测,如果一个文件的FSDataOutputStream在60秒内(可以通过sink.setInactiveBucketThreshold(60 * 1000L);去设置),都没有接受的数据,就会被认为是不活跃的bucket(可以认为是文件),那么就会flush然后关闭该文件。

     这里如果说的再深入一点就是,实际上只是在processingTimeService中注册了当前时间 + inactiveBucketCheckInterval(60秒不写入时间),然后有个onProcessingTime方法去时时刻刻的判断是否满足60秒不写入,同时也会判断是否到了滚动时间。当然通俗的讲,也可以认为是每60秒去检测一次。源码如下:

@Override
	public void onProcessingTime(long timestamp) throws Exception {
		long currentProcessingTime = processingTimeService.getCurrentProcessingTime();

		closePartFilesByTime(currentProcessingTime);

		processingTimeService.registerTimer(currentProcessingTime + inactiveBucketCheckInterval, this);
	}

5.flink内部封装了一个Map<String, BucketState<T>> bucketStates = new HashMap<>();  用来记录当前正在使用的文件,key是文件的路径,BucketState内部封装了该文件的所有信息,包括创建时间,最后一次写入时间(这里的写入指的是写入缓存区的时间,不是flush的时间),文件大小,当前文件是打开还是关闭,写缓冲区的方法。都在这里。每次flink要对文件进行操作的时候,都会从这里拿到文件的封装对象

6.当程序别cancel的时候,当前正在操作的文件,会被flush,关闭。然后将文件的后缀名从in-progress改为pending。这个前后缀都能设置,但如果没有什么特殊需求,默认即可。这里拿文件,用的就是5里面说的bucketStates这个map。它在close方法中,会去遍历这个map,去做上述的操作。

@Override
	public void close() throws Exception {
		if (state != null) {
			for (Map.Entry<String, BucketState<T>> entry : state.bucketStates.entrySet()) {
				closeCurrentPartFile(entry.getValue());
			}
		}
	}

7.每次写入的时候,都是会bucketStates这个map中获取对应的对象,如果没有,就新建一个该对象。然后先判断是否需要滚动(通过当前文件大小和滚动时间去判断),然后才将数据写入缓冲区,更新最后写入时间。

@Override
	public void invoke(T value) throws Exception {
		Path bucketPath = bucketer.getBucketPath(clock, new Path(basePath), value);

		long currentProcessingTime = processingTimeService.getCurrentProcessingTime();

		BucketState<T> bucketState = state.getBucketState(bucketPath);
		if (bucketState == null) {
			bucketState = new BucketState<>(currentProcessingTime);
			state.addBucketState(bucketPath, bucketState);
		}

		if (shouldRoll(bucketState, currentProcessingTime)) {
			openNewPartFile(bucketPath, bucketState);
		}

		bucketState.writer.write(value);
		bucketState.lastWrittenToTime = currentProcessingTime;
	}

8.写入和关闭HDFS是通过异步的方式的,异步的超时时间默认是60秒,可以通过 sink.setAsyncTimeout(60 * 1000)去设置。

比较有意思的是,这里判断是否超时,用的是while 循环的方式,每500ms判断是否关闭完成,或者写入完成。

//判断关闭的
boolean isclosed = dfs.isFileClosed(partPath);
StopWatch sw = new StopWatch();
sw.start();
while (!isclosed) {
	if (sw.getTime() > asyncTimeout) {
		break;
	}
	try {
		Thread.sleep(500);
	} catch (InterruptedException e1) {
		// ignore it
	}
	isclosed = dfs.isFileClosed(partPath);
}

//判断写入的
StopWatch sw = new StopWatch();
sw.start();
long newLen = fs.getFileStatus(partPath).getLen();
while (newLen != validLength) {
	if (sw.getTime() > asyncTimeout) {
		break;
	}
	try {
		Thread.sleep(500);
	} catch (InterruptedException e1) {
		// ignore it
	}
	newLen = fs.getFileStatus(partPath).getLen();
}

 

目前初步的就先写到这里,欢迎大家来探讨。。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值