执行一次怎么会写入两次数据_大数据学习:Flink学习笔记——API学习

3ffa275dcfb9b16af6990ad69495585b.png

前面一篇写了flink的原理以及单机安装配置,这篇主要讲Flink 的java API学习。今天想起了上周看到的MIT校训Mind and Hand,可以作为时刻提醒自己的语句,可以作为警醒自己的语句。心有多大,舞台就有多大。

1. DataStream

1.1 keyBy

逻辑上将数据流元素进行分区,具有相同key的记录被分到同一个分区

KeyedStream<String,Tuple> keyedStream = dataStream.keyBy(0);

如果需要定制key,在keyBy里定义new KeySelector<>()对象,实现getKey方法。

1.2 Iterator

输出计算结果时,对dataStream进行遍历,以写入数据库中。

2. Sink输出流

2.1 输出到HBase

public class HBaseOutputFormat implements OutputFormat<Tuple2<String, Integer>> {

    private static final Logger logger = LoggerFactory.getLogger(HBaseOutputFormat.class);
    private org.apache.hadoop.conf.Configuration conf = null;
    private Connection conn = null;
    private Table table = null;
    private static String tableName = "Test";
    private static Map<String,List<String>> columnFamilys = new HashMap<>();
    private static String cf;
    private static List<String> cols;

    @Override
    public void configure(Configuration configuration) {

    }

    public void InitConf(String cf, List<String> cols){
        this.cf = cf;
        this.cols = cols;
    }

    @Override
    public void open(int i, int i1) throws IOException {
        columnFamilys.put(cf,cols);
        InitHBase.createTable(tableName,columnFamilys);
    }

    @Override
    public void writeRecord(Tuple2<String, Integer> stringIntegerTuple2) throws IOException {
        Map<String,String> tmp = new HashMap<>();
        tmp.put(stringIntegerTuple2.f0,String.valueOf(stringIntegerTuple2.f1));
        String rowkey = stringIntegerTuple2.f0;
        if(rowkey.length() == 0){
            rowkey = "null";
        }
        InitHBase.put(tableName,rowkey,cf,tmp);
    }


    @Override
    public void close() throws IOException {

    }
}

通过实现OutputFormat接口,读取Sink的数据。OutputFormat接口会每次读取一个Tuple2<String, Integer>格式的key/value对。

抽象方法

configure:配置输出格式,输出格式会根据配置值设置基本字段的地方,此方法总是在实例化输出格式上首先调用。

open:用于打开输出输出格式的并行实例,以配置存储其并行实例的结果,调用此方法时,将确保配置该方法的输出格式,所以一般会在open方法里进行数据库的连接,配置,建表等操作。

writeRecord:用于将数据写入数据源,在这里调用API进行数据库的写入。

close:关闭数据源的连接。

2.2 输出到mysql

flink自定义sink输出还有一种方式,继承RichSinkFunction类,实现configure(),open(),writeRecord(),close()方法,代码如下:

public class MysqlSink extends RichSinkFunction<Tuple2<String, Integer>> {    

@Override
    public void configure(Configuration configuration) {

    }

    @Override
    public void open(int i, int i1) throws IOException {
    }

    @Override
    public void writeRecord(Tuple2<String, Integer> stringIntegerTuple2) throws IOException {

    }

    @Override
    public void close() {
    }
}

实践中,open()方法在启动时会调用多次,这可能是flink的机制,为了确保open方法能够执行,这也是猜测,后面如果知道原因后,会填补这个空缺。mysql数据库的连接如果并发写数据库压力不大的话,最好写在writeRecord方法中,该方法会每次在reduce后得到的key,value结果对后都会执行一次,也就是写的时候创建数据库连接,写完后关闭数据库连接。在实践中,如果不每次连接和关闭的话,flink集群执行时会调用close方法两次,从而把你的连接会断。而且可能因为数据库的提交执行sql问题,数据会一直不见存进数据库里, 存在缓存里面,提交不了,丢失。当然这个还没有验证,后续验证后会更新此文章。

3. Flink time机制

3.1 Processing Time

指执行相应操作的机器的系统时间,因为在分布式和异步环境中,Processing Time并不能保证确定性,容易受到Event到达系统的速度以及数据在Flink系统内部处理的先后顺序的影响,所以Processing Time不能准确地反映数据产生的时间序列。

3.2 Ingestion Time

事件进入Flink的时间,Source处获取到这个数据的时间。虽然没有Processing Time那样因为Flink分布式系统的先后顺序和数据传输的影响,但存在数据传输过程的网络延迟,不能很好反映数据的时间序列情况。

3.3 Event Time

每条数据在其生产设备上发生的时间。这段时间通常嵌入在记录数据中,然后进入Flink,可以从记录中提取数据的时间戳,能充分反映数据的时间序列。

c6f5eeb8a2ec24771a74eb3d68e8e592.png

设置EventTime:

在创建运行环境后,需要设置时间戳提取器,并将TimeCharactersistic设置为EventTime。

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

该设置用于定义了流处理的时间使用事件时间。然后需要定义时间戳分配器。

使用事件时间作为处理时间需要每个事件都有一个事件时间戳,通常从数据中的某个字段得到。时间戳分配与生成watermark相结合,watermark告诉系统事件时间的处理进度。这里举出两种方法:

                .assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<MobileEvent>() {

                    Long maxOutOfOrderness = 5000L;
                    @Nullable
                    @Override
                    public Watermark getCurrentWatermark() {
                        return new Watermark(System.currentTimeMillis() - maxOutOfOrderness);
                    }

                    @Override
                    public long extractTimestamp(MyEvent element, long previousElementTimestamp) {
                        return Long.parseLong(element.getCreationTime());
                    }
                })

AssignerWithPeriodicWatermarks定期的分配时间戳和生成watermark,watermark生成的时间间隔通过ExecutionConfig.setAutoWatermarkInterval(...)方法来定义。时间戳生成器getCurrentWatermark()方法每次都会被触发,如果返回结果不为空或者大于上一个的watermark,那么新的watermark将会被发送。在这里定义watermark为当前系统时间 - 最大允许延迟时间5秒。

                .assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<MobileEvent>() {

                    private final long maxOutOfOrderness = 5000L;
                    private long currentMaxTimestamp;

                    @Nullable
                    @Override
                    public Watermark getCurrentWatermark() {
                        return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
                    }

                    @Override
                    public long extractTimestamp(myEvent element, long previousElementTimestamp) {
                        long timestamp = Long.parseLong(element.getCreationTime());
                        currentMaxTimestamp = Math.max(timestamp,currentMaxTimestamp);
                        return timestamp;
                    }
                })

第二种方法比第一种较动态,对于应付延迟不可控或大批事件延迟的情况具有比较好的适应。这里时间戳生成是取当前数据时间与当前最大时间戳之间的最大,在这里,如果后面的事件比前面的时间早到达,那么当前最大时间戳还是原来的,直到比该事件后的事件到达,才会更新,这样可以相对保护前面的事件延迟到达会被抛弃。

两种方法无关好坏,个人在比较乱序的情况下,第二种方法会完全乱套了,不能很好的反映数据的意义,所以针对场景进行选择。除了这两种,还有其他方法,这里不详细讲解了,有兴趣的可以到Flink的事件时间和watermarks(翻译Flink官方文档)

Windows操作

窗口化是Flink中阶段性处理数据流的方法,有时候我们需要对数据流进行阶段性的统计或聚合等操作,比如:在过去的一个小时广东各个区域线上化妆品成交量。在这种情况下,我们需要定义一个窗口,收集过去一小时的数据,并对这个窗口的数据进行calculate。窗口可以分为分组的流、非分组的流。区分是分组的stream调用keyBy(...)和window(...),非分组的stream调用windowAll(...)。

窗口分配器

窗口有几种:滚动窗口(Tumbling Windows)、滑动窗口(Sliding Windows)、会话窗口(Session Windows)、滚动计数窗口(Count Windows)。

a66d5d9a406ef7e6367d798c1c93fd3f.png
  • 滚动窗口(Tumbling Windows)

当我们需要统计每一小时用户购买的化妆品数量时,在flink中则使用Tumbling Windows,代码实现很简单,只要一句:

.timeWindow(Time.minutes(60))
  • 滑动窗口(Sliding Windows)

我们需要每隔半个小时统计过去一小时用户购买的化妆品数量时,则需要使用到滑动窗口,实现如下:

.timeWindow(Time.minutes(60),Time.minutes(30))
  • 会话窗口(Session Windows)

Session Windows是由数据的时间来决定的,比如根据用户id进行分组,得到如下的数据:

id1,09:00:00
id1,09:01:00
id1,09:03:00
id1,09:07:00
id1,09:14:00
id1,09:19:00
id1,09:30:00
id1,09:34:00
...

假设设置Session Window的时间gap为5分钟,则得到的窗口如下:

窗口1:(id1,09:00:00,09:12:00,3)
窗口2:(id1,09:14:00,09:24:00,2)
窗口3:(id2,09:30:00,09:34:00,2)
...

时间gap指数据间隔时间,上面设置时间间隔为5分钟,则数据时间间隔超过5分钟就会触发一个Session Window。代码实现如下:

.keyBy(_.userId)             
.window(EventTimeSessionWindows.withGap(Time.minutes(5)))

当然,在设置使用数据时间时,需要定义时间戳生成器,从数据中提取时间戳。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值