Flink的Interval-join实现原理,以及关联不上怎么办

IntervalJoin实现原理:

我们给定两个时间点,分别叫作间隔的“上界”(upperBound)和“下界”(lowerBound);于是对于一条流(不妨叫作A)中的任意一个数据元素a,就可以开辟一段时间间隔:[a.timestamp + lowerBound, a.timestamp + upperBound],即以a的时间戳为中心,下至下界点、上至上界点的一个闭区间:我们就把这段时间作为可以匹配另一条流数据的“窗口”范围。所以对于另一条流(不妨叫B)中的数据元素b,如果它的时间戳落在了这个区间范围内,a和b就可以成功配对,进而进行计算输出结果。所以匹配的条件为:

a.timestamp + lowerBound <= b.timestamp <= a.timestamp + upperBound

这里需要注意,做间隔联结的两条流A和B,也必须基于相同的key;下界lowerBound应该小于等于上界upperBound,两者都可正可负;间隔联结目前只支持事件时间语义

Interval-join的源码执行过程总结:

0:两条流合流-connect,然后keyBy()

①维护两个MapState状态分别保存左流和右流的数据:

②判断数据的时间戳是否是迟到数据,

若不是则:

A流新来一条数据以时间戳为key,数据本身放到List中作为状态的value-->leftBuffer

B流新来一条数据以时间戳为key,数据本身放到List中作为状态的value-->rightBuffer

③用此数据和另外一条流中的缓存数据做关联
④根据关联逻辑进行处理并将关联后的数据输出到下游

⑤判断数据是否超过上限时间,做缓存清除--->Interval-join自己会清除数据,不用设置ttl

上源码:

 源码维护的两个状态,以及状态的赋值

private transient MapState<Long, List<BufferEntry<T1>>> leftBuffer;
private transient MapState<Long, List<BufferEntry<T2>>> rightBuffer;
public void initializeState(StateInitializationContext context) throws Exception {
        super.initializeState(context);
//构建 左流缓冲区,类型为keyedState的MapState 其中时间戳是key,因为相同时间戳可能会来多条数据
        this.leftBuffer =
                context.getKeyedStateStore()
                        .getMapState(
                                new MapStateDescriptor<>(
                                        LEFT_BUFFER,
                                        LongSerializer.INSTANCE,
                                        new ListSerializer<>(
                                                new BufferEntrySerializer<>(leftTypeSerializer))));
//构建 右流缓冲区,类型为keyedState的MapState 其中时间戳是key,因为相同时间戳可能会来多条数据
        this.rightBuffer =
                context.getKeyedStateStore()
                        .getMapState(
                                new MapStateDescriptor<>(
                                        RIGHT_BUFFER,
                                        LongSerializer.INSTANCE,
                                        new ListSerializer<>(
                                                new BufferEntrySerializer<>(rightTypeSerializer))));
    }

对两条流数据的处理:

public void processElement1(StreamRecord<T1> record) throws Exception {
        processElement(record, leftBuffer, rightBuffer, lowerBound, upperBound, true);
    }
public void processElement2(StreamRecord<T2> record) throws Exception {
        processElement(record, rightBuffer, leftBuffer, -upperBound, -lowerBound, false);
    }
private <THIS, OTHER> void processElement(
            final StreamRecord<THIS> record,
            final MapState<Long, List<IntervalJoinOperator.BufferEntry<THIS>>> ourBuffer,
            final MapState<Long, List<IntervalJoinOperator.BufferEntry<OTHER>>> otherBuffer,
            final long relativeLowerBound,
            final long relativeUpperBound,
            final boolean isLeft)
            throws Exception {

        final THIS ourValue = record.getValue();
        final long ourTimestamp = record.getTimestamp();

        if (ourTimestamp == Long.MIN_VALUE) {
            throw new FlinkException(
                    "Long.MIN_VALUE timestamp: Elements used in "
                            + "interval stream joins need to have timestamps meaningful timestamps.");
        }

        if (isLate(ourTimestamp)) {
            sideOutput(ourValue, ourTimestamp, isLeft);
            return;
        }

        addToBuffer(ourBuffer, ourValue, ourTimestamp);

        for (Map.Entry<Long, List<BufferEntry<OTHER>>> bucket : otherBuffer.entries()) {
            final long timestamp = bucket.getKey();

            if (timestamp < ourTimestamp + relativeLowerBound
                    || timestamp > ourTimestamp + relativeUpperBound) {
                continue;
            }

            for (BufferEntry<OTHER> entry : bucket.getValue()) {
                if (isLeft) {
                    collect((T1) ourValue, (T2) entry.element, ourTimestamp, timestamp);
                } else {
                    collect((T1) entry.element, (T2) ourValue, timestamp, ourTimestamp);
                }
            }
        }
//判断时间戳是否过期=>若过期调用定时器清理数据
        long cleanupTime =
                (relativeUpperBound > 0L) ? ourTimestamp + relativeUpperBound : ourTimestamp;
        if (isLeft) {
            internalTimerService.registerEventTimeTimer(CLEANUP_NAMESPACE_LEFT, cleanupTime);
        } else {
            internalTimerService.registerEventTimeTimer(CLEANUP_NAMESPACE_RIGHT, cleanupTime);
        }
    }

真实使用的代码截图:

关联不上的解决办法:

原因分析: 若是使用的interval-join那么很有可能关联不上迟到数据,因此可以放弃interval-join,在FlinkSQL中使用left-join

flinkSQL普通Join(left-join,right-join)底层维护了俩状态,所以一定要设置状态的有效时间---否则会内存溢出(OOM->Out Of Memory)

left-join的原理:

支持所有时间语义

A流数据都会保存在MapStateA状态中,B流数据都会保存在MapStateB中,

以A流为主流,B流为关联流,

A每来一条数据,都会关联一次B--->并输出关联结果:有三种可能

①未关联上B,会输出: A.col1,A.col2,null(B.col1),null(B.col2)

②关联上1条:  A.col1,A.col2,B.col1,B.col2

③关联上多条: A.col1,A.col2,B.col1,B.col2;A.col1,A.col2,B.col3,B.col4;...

B流每来一条数据 都会关联一次A,若是每关联上,会输出null,

若是关联上1条输入同②,若是关联上多条,输出同③

下游Sink用回撤流/upsertKafka

动态表---做Join转换的流--->(假如左主)会受到三条消息:左null+,左null-,左右+

其他源码解析:

Flink intervalJoin 使用和原理分析 - 简书

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值