Apache Flink中Watermark、Window、State三者的关系

前言

Watermark、Window和State是Apache Flink的三个高光名词,也是大家在实际应用中利器。

  • Watermark致力于解决数据的乱序和延迟问题;
  • Window尽心尽力提供各种数据切分机制;
  • State勤勤恳恳记录中间状态,并在数据恢复等场景承担着重要的角色。

这三个算得上是Flink的模范标兵。大家有想过三者之间的关系么,咱们从几个问题来探讨吧。

理想三问

  1. Watermark和Window的关系?
  2. Window和Watermark是基于key的么?
  3. Window和State的关系?

下文主要是从上面三个点予以说明。源码版本1.6.1

Watermark和Window的关系

这个问题,Flink初学者的一个必然思考。官网已经予以了非常详尽的解释,如果鸡蛋里挑骨头,那就是英文对广大底层劳动人民不够友好,说的不够直接。能用最简单的语言去说清楚么?尝试下:watermark是触发time window的时间依据。

具体来说,Window可以为流提供分段处理机制, Watermark则主要应对数据时间乱序问题。在实际应用中,数据源往往很多个且时钟无法严格同步,数据汇集过程中传输的距离和速度也必然不同,在上游多节点处理过程中的处理速度也有差异,这些因素使得event time的乱序基本是一个必然现象。 这样也可以理解Watermark对非time window类的操作没有什么意义。

下面代码是element处理过程中判断是否要触发窗口计算的逻辑,从中就可以看出Window和Watermark在代码层面的关联关系:

//From EventTimeTrigger.java
@Override
public TriggerResult onElement(Object element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {
	if (window.maxTimestamp() <= ctx.getCurrentWatermark()) {
		// if the watermark is already past the window fire immediately
		// 触发窗口计算
		return TriggerResult.FIRE;
	} else {
		// 继续,不触发窗口计算
		ctx.registerEventTimeTimer(window.maxTimestamp());
		return TriggerResult.CONTINUE;
	}
}

Window和Watermark是基于key的么

为什么这个问题这么高频?举个场景:大家将同一类业务存储在同一个Kafka的Topic中,但是由于数据采集、传输的中间过程不同,可能会造成某些数据延迟较大,并不局限于某个partition,partition内的数据也有可能部分延迟较大。大家就会有疑问了,这种场景下Flink会怎样处理,延迟较大的数据还能抢救么?

在进一步探究之前,我们先明确两点:

  1. 每一个Operator对应于JVM中的一个java instance;
  2. Window 算子对应的Class是WindowOperator;

也就是说,如果我们对KeyedStream做Window,如果并发度是n,在集群中就有n个WinwowOperator的instance。OK,开撸代码,翻开你的WindowOperator:

//From WindowOperator
protected transient InternalTimerService<W> internalTimerService;

public long getCurrentWatermark() {
			return internalTimerService.currentWatermark();
		}

上面了两行代码已经揭示了一个Operator只有一个Watermark。流经该Operator的多个Key都会公用一个Watermark。小延迟的数据可以通过late的控制来cover住,大的延迟通过late来控制会带来大量的数据存储,可以通过OutputTag等方式handle,如果不加任何处理,默认是被丢弃的。

Window和State的关系

Window中的数据全是靠State来缓存,不多说,直击WindowedStream的code,可以发现window创建了一个name为“window-contents”,类型为ListState的StateDescriptor:

// From WindowedStream
rivate <R> SingleOutputStreamOperator<R> apply(InternalWindowFunction<Iterable<T>, R, K, W> function, TypeInformation<R> resultType, Function originalFunction) {

	final String opName = generateOperatorName(windowAssigner, trigger, evictor, originalFunction, null);
	KeySelector<T, K> keySel = input.getKeySelector();

	WindowOperator<K, T, Iterable<T>, R, W> operator;

	if (evictor != null) {
		...
		ListStateDescriptor<StreamRecord<T>> stateDesc =
				new ListStateDescriptor<>("window-contents", streamRecordSerializer);

		operator =
			new EvictingWindowOperator<>(windowAssigner,
				windowAssigner.getWindowSerializer(getExecutionEnvironment().getConfig()),
				keySel,
				input.getKeyType().createSerializer(getExecutionEnvironment().getConfig()),
				stateDesc,
				function,
				trigger,
				evictor,
				allowedLateness,
				lateDataOutputTag);

	} else {
		ListStateDescriptor<T> stateDesc = new ListStateDescriptor<>("window-contents",
			input.getType().createSerializer(getExecutionEnvironment().getConfig()));

		operator =
			new WindowOperator<>(windowAssigner,
				windowAssigner.getWindowSerializer(getExecutionEnvironment().getConfig()),
				keySel,
				input.getKeyType().createSerializer(getExecutionEnvironment().getConfig()),
				stateDesc,
				function,
				trigger,
				allowedLateness,
				lateDataOutputTag);
	}

	return input.transform(opName, resultType, operator);
}

进一步关注WindowOperator在open过程中创建State的过程,原注释也比较清晰,看码:

// From WindowOperator
@Override
public void open() throws Exception {
	super.open();
	...
	// 这里和watermark有点渊源
	internalTimerService =
			getInternalTimerService("window-timers", windowSerializer, this);
	...
	// create (or restore) the state that hold the actual window contents
	// NOTE - the state may be null in the case of the overriding evicting window operator
	if (windowStateDescriptor != null) {
		windowState = (InternalAppendingState<K, W, IN, ACC, ACC>) getOrCreateKeyedState(windowSerializer, windowStateDescriptor);
	}

	...
}

关于使用过程,在解读Flink双流Join中有一个场景的详细记录。关于状态的解读,参考这两篇
Flink状态管理(一) 原理和数据持久化
Flink状态管理(二)状态数据结构和注册流程

后记

至此三个问题都已经有了答案,对Flink中三个高频词之间的关联关系也开始有了清晰的认知。继续前进,“Back pressure”在前面等候已久。

你可能感兴趣的:(Flink)