今天看到有小伙伴在问,就想着自己实现一下。

问题: Flink FileSink根据输入数据指定输出位置,比如讲对应日期的数据输出到对应目录

输入数据:
20190716 输出到路径 20190716
20190717 输出到路径 20190717
20190718 输出到路径 20190718

目前flink 对与输出到文件有两种实现(write 算子不算,只能指定目录):Rolling File Sink 和 Streaming File Sink

Rolling File Sink 的实现就是 BucketingSink,使用也很简单,直接指定路径就可以了,也可以设置如:目录名称格式(按时间格式滚动),输出文件格式,文件大小、滚动间隔、文件前缀、后缀一类的

// the SequenceFileWriter only works with Flink Tuples
import org.apache.flink.api.java.tuple.Tuple2
val input: DataStream[Tuple2[A, B]] = ... val sink = new BucketingSink[Tuple2[IntWritable, Text]]("/base/path")
sink.setBucketer(new DateTimeBucketer("yyyy-MM-dd--HHmm", ZoneId.of("America/Los_Angeles")))
sink.setWriter(new SequenceFileWriter[IntWritable, Text])
sink.setBatchSize(1024 * 1024 * 400) // this is 400 MB,
sink.setBatchRolloverInterval(20 * 60 * 1000); // this is 20 mins input.addSink(sink)

当然,如果是这么简单,就不会有这篇博客了,下面进入主题

--------------------------------------

默认的 DateTimeBucketer 只能根据时间指定文件名的滚动是规则,没办法根据数据指定文件的输出位置,这需要实现 BasePathBucketer 自定义输出路径

实现如下:

import java.io.File
import org.apache.flink.streaming.connectors.fs.Clock
import org.apache.flink.streaming.connectors.fs.bucketing.BasePathBucketer
import org.apache.hadoop.fs.Path /**
* 根据实际数据返回数据输出的路径
*/
class DayBasePathBucketer extends BasePathBucketer[String]{ /**
* 返回路径
* @param clock
* @param basePath
* @param element
* @return
*/
override def getBucketPath(clock: Clock, basePath: Path, element: String): Path = {
// yyyyMMdd
val day = element.substring(1, 9)
new Path(basePath + File.separator +
day)
}

}

调用如下:

import java.io.File
import java.text.SimpleDateFormat
import com.venn.index.conf.Common
import org.apache.flink.formats.json.JsonNodeDeserializationSchema
import org.apache.flink.runtime.state.filesystem.FsStateBackend
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.node.ObjectNode
import org.apache.flink.streaming.api.{CheckpointingMode, TimeCharacteristic}
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.connectors.fs.StringWriter
import org.apache.flink.streaming.connectors.fs.bucketing.BucketingSink
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.api.scala._ /**
* 使用BucketingSink 实现 根据‘数据’自定义输出目录
*/
object RollingFileSinkDemo { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) val sdf = new SimpleDateFormat("yyyyMMddHHmmss")
val source = new FlinkKafkaConsumer[ObjectNode]("roll_file_sink", new JsonNodeDeserializationSchema, Common.getProp) val sink = new BucketingSink[String]("D:\\idea_out\\rollfilesink")
sink.setBucketer(new DayBasePathBucketer)
sink.setWriter(new StringWriter[String])
sink.setBatchSize(1024 * 1024 * 400) // this is 400 MB,
// sink.setBatchRolloverInterval(24 * 60 * 60 * 1000) // this is 24 hour
//    sink.setInProgressPrefix("inProcessPre")
// sink.setPendingPrefix("pendingpre")
// sink.setPartPrefix("partPre")

    env.addSource(source)
.assignAscendingTimestamps(json => {
sdf.parse(json.get("date").asText()).getTime
})
.map(json => {
json.get("date") + "-" + json.toString // 将日期拼接到前面,方便后面使用
})
.addSink(sink) env.execute("rollingFileSink")
} }

测试数据如下:

{"id" : 1, "name" : "venn1563288621091", "date" : "20190716230020"}
{"id" : 2, "name" : "venn1563288621310", "date" : "20190716231020"}
...
{"id" : 263, "name" : "venn1563288648926", "date" : "20190718184020"}
{"id" : 264, "name" : "venn1563288649029", "date" : "20190718185020"}
{"id" : 265, "name" : "venn1563288649132", "date" : "20190718190020"}

测试结果如下:

可以看到,当天的数据都输出到当天对应的目录中。

遇到个问题:

这里有个问题,因为重写了BasePathBucketer,自定义了输出文件,所有会同时打开多个输出文件,带来文件刷新的问题,在当前文件写完后(这里的表现是:当天的数据以及全部流过,
下一天的文件以及开始写了),会发现当天的文件中的数据不全,因为数据还没有全部刷到文件,这个时候下一个文件又开始写了,会发现上一个文件还没刷完

猜想:

猜想:每个文件都有个输出缓冲,上一个文件最后一点数据还在缓冲区,下一个文件又使用新的缓冲区,没办法刷到上一个文件的数据,只有等缓冲区数据满、超时一类的操作触发刷写 ??

验证:

源码BucketingSink.closePartFilesByTime 默认每60秒或大于滚动时间间隔(batchRolloverInterval)(系统时间) 将当前park文件,将状态从 in-process 修改为 pending,随后关闭当前的part 文件,数据刷到磁盘

代码如下:

private void closePartFilesByTime(long currentProcessingTime) throws Exception {

        synchronized (state.bucketStates) {
for (Map.Entry<String, BucketState<T>> entry : state.bucketStates.entrySet()) {
if ((entry.getValue().lastWrittenToTime < currentProcessingTime - inactiveBucketThreshold)
|| (entry.getValue().creationTime < currentProcessingTime - batchRolloverInterval)) {
LOG.debug("BucketingSink {} closing bucket due to inactivity of over {} ms.",
getRuntimeContext().getIndexOfThisSubtask(), inactiveBucketThreshold);
closeCurrentPartFile(entry.getValue());
}
}
}
}

下篇: Flink FileSink 自定义输出路径——StreamingFileSink、BucketingSink 和 StreamingFileSink简单比较

搞定

Flink FileSink 自定义输出路径——BucketingSink的更多相关文章

  1. Flink FileSink 自定义输出路径——StreamingFileSink、BucketingSink 和 StreamingFileSink简单比较

    接上篇:Flink FileSink 自定义输出路径——BucketingSink 上篇使用BucketingSink 实现了自定义输出路径,现在来看看 StreamingFileSink( 据说是S ...

  2. Hadoop案例(五)过滤日志及自定义日志输出路径(自定义OutputFormat)

    过滤日志及自定义日志输出路径(自定义OutputFormat) 1.需求分析 过滤输入的log日志中是否包含xyg (1)包含xyg的网站输出到e:/xyg.log (2)不包含xyg的网站输出到e: ...

  3. Lrc2srt精灵,增加自定义输出编码

    2015.4.8 对中文支持有点问题,修改了一下,支持自定义输出编码! 修改了建议行末偏移,通常100到200最好了,人的反应时间! http://files.cnblogs.com/files/ro ...

  4. VS 工程的 输出路径和工作路径的区别

    输出路径,是vs编译项目生成可执行文件的路径:工作路径是环境变量,比如我们在程序中写相对路径,就是以这个路径为基础的.在默认情况下,输出路径和工作路径都不写的话,默认是程序的bin下面的debug或者 ...

  5. HD1385Minimum Transport Cost(Floyd + 输出路径)

    Minimum Transport Cost Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/O ...

  6. EDIUS设置自定义输出的方法

    在做后期视频剪辑时,往往根据需求,需要输出不同分辨率格式的视频文件,那在EDIUS中,如何自定义输出设置,使之符合自己的需要呢?下面小编就来详细讲讲EDIUS自定义输出的一二事吧. 当剪辑完影片,设置 ...

  7. C++builder XE 安装控件 及输出路径

    C++builder XE 安装控件 与cb6不一样了,和delphi可以共用一个包. 启动RAD Studio.打开包文件. Project>Options>Delphi Compile ...

  8. HDU 1385 Minimum Transport Cost (最短路,并输出路径)

    题意:给你n个城市,一些城市之间会有一些道路,有边权.并且每个城市都会有一些费用. 然后你一些起点和终点,问你从起点到终点最少需要多少路途. 除了起点和终点,最短路的图中的每个城市的费用都要加上. 思 ...

  9. web项目Log4j日志输出路径配置问题

    问题描述:一个web项目想在一个tomcat下运行多个实例(通过修改war包名称的实现),然后每个实例都将日志输出到tomcat的logs目录下实例名命名的文件夹下进行区分查看每个实例日志,要求通过尽 ...

随机推荐

  1. Django --- 多对多关系创建,forms组件

    目录 多对多三种创建方式 1.系统直接创建 2.自己手动创建 3.自己定义加与系统创建 forms组件 1. 如何使用forms组件 2. 使用forms组件校验数据 3. 使用forms组件渲染标签 ...

  2. C#延迟初始化Lazy<T>

    1. 概述 我们创建某一个对象需要很大的消耗,而这个对象在运行过程中又不一定用到,为了避免每次运行都创建该对象,这时候延迟初始化(也叫延迟实例化)就出场了. 延迟初始化出现于.NET 4.0,主要用于 ...

  3. 了解区块链&比特币

    https://www.bilibili.com/video/av45247943 假如有ABCD四个比特币交易者,其中A交易给B者10个比特币(BTC),而这条信息要广播给其他所有的交易者知道. 假 ...

  4. c语言冒泡排序算法

    案例一: #include <stdio.h> int main(void){ int a[5]; printf("please input sort number:" ...

  5. 2017.10.4 国庆清北 D4T1 财富

    (其实这题是luogu P1901 发射站 原题,而且数据范围还比luogu小) 题目描述 LYK有n个小伙伴.每个小伙伴有一个身高hi. 这个游戏是这样的,LYK生活的环境是以身高为美的环境,因此在 ...

  6. COGS 1583. [POJ3237]树的维护

    二次联通门 : COGS 1583. [POJ3237]树的维护 /* COGS 1583. [POJ3237]树的维护 树链剖分 + 边权化点权 线段树 单点修改 + 区间取相反数 + 查询区间最大 ...

  7. @RestController和@GetMapping

    @RestController 可以代替@Controller使用,使用了@RestController的控制器默认所有请求方法都用了@ResponseBody注解. @GetMapping(&quo ...

  8. Mybatis mapper接口与xml文件路径分离

    为什么分离 对于Maven项目,IntelliJ IDEA默认是不处理src/main/java中的非java文件的,不专门在pom.xml中配置<resources>是会报错的,参考这里 ...

  9. Ruby on Rails框架(1)-安装全攻略

    序 关于Rails的三句箴言 (1)DRY:Don't Repeat Yourself(不要重复你自己) rails的开发理念,不要用你的代码不停的重复,rails框架给开发者提供了一套非常完善的支持 ...

  10. YARN 状态机可视化,生成状态机图

    由于在windows下面,配置好所有 编译hadoop2.4.1源码 的环境会很麻烦,好在我之前已经把hadoop2.4.1的源码成功导入eclipse,并解决了所有错误提示,所以我就可以在eclip ...