目录
本文基于 flink 1.12.0 版本进行源码解析
笔者之前在使用 FlinkSQL 的去重和TopN 功能时只是简单的看了下官网,然后用 sql 实现了功能,但是还有些疑问没有解决。
比如:去重算子占用的状态有多少?另外不使用 mini-batch 模式,去重的结果很单一,升序就一直输出最后一条(降序就只输出第一条数据)。
为了解决这些疑问,特意研究了下去重部分的源码。 去重部分源码类的结构图如下:
去重基类
DeduplicateFunctionBase 定义了去重的状态,由于是去重,所以只需要一个 ValueState 存储一个 Row 的数据就可以了(不管是处理时间还是事件时间)
// state stores previous message under the key. 基于key的状态用于去重
protected ValueState<T> state;
public DeduplicateFunctionBase(
TypeInformation<T> typeInfo,
TypeSerializer<OUT> serializer,
long stateRetentionTime) {
this.typeInfo = typeInfo;
// 状态保留时间,决定去重的数据的去重窗口大小
this.stateRetentionTime = stateRetentionTime;
this.serializer = serializer;
}
@Override
public void open(Configuration configure) throws Exception {
super.open(configure);
ValueStateDescriptor<T> stateDesc = new ValueStateDescriptor<>("deduplicate-state", typeInfo);
// 设置去重状态的 ttl(非常重要)
StateTtlConfig ttlConfig = createTtlConfig(stateRetentionTime);
// 判断 ttl 是开启的
if (ttlConfig.isEnabled()) {
stateDesc.enableTimeToLive(ttlConfig);
}
// 创建用于去重的状态
state = getRuntimeContext().getState(stateDesc);
}
处理时间的 First Row
ROW_NUMBER() OVER (PARTITION BY cate_id ORDER BY process_time asc) AS rownum
即取基于处理时间的第一条数据
处理类为:ProcTimeDeduplicateKeepFirstRowFunction
基于处理时间特性即后一条一定比前一条大这个逻辑,直接判断用于去重 的state.value 是否为空,如果为空则表示是第一条数据,直接下发;非空则说明前面该key数据存在而非第一条,此时不下发。
public class ProcTimeDeduplicateKeepFirstRowFunction
extends DeduplicateFunctionBase<Boolean, RowData, RowData, RowData> {
private static final long serialVersionUID = 5865777137707602549L;
// state stores a boolean flag to indicate whether key appears before.
public ProcTimeDeduplicateKeepFirstRowFunction(long stateRetentionTime) {
super(Types.BOOLEAN, null, stateRetentionTime);
}
@Override
public void processElement(RowData input, Context ctx, Collector<RowData> out) throws Exception {
// 调用处理时间的判断方法: DeduplicateFunctionHelper.processFirstRowOnProcTime
processFirstRowOnProcTime(input, state, out);
}
}
DeduplicateFunctionHelper.processFirstRowOnProcTime 函数如下:
static void processFirstRowOnProcTime(
RowData currentRow,
ValueState<Boolean> state,
Collector<RowData> out) throws Exception {
// 仅处理只包含插入行为的row,否则报错
checkInsertOnly(currentRow);
// ignore record if it is not first row
//非空则说明前面该key数据存在而非第一条,此时不下发
if (state.value() != null) {
return;
}
// 添加第一次出现key对应的状态
state.update(true);
// emit the first row which is INSERT message
// 下发第一次出现的key对应的row
out.collect(currentRow);
}
处理时间的 Last Row
ROW_NUMBER() OVER (PARTITION BY cate_id ORDER BY process_time desc) AS rownum
即取基于处理时间的最后一条数据
处理时间的逻辑同样基于后一条一定比前一条大这个逻辑,直接判断去重 state.value 是否为空,为空则表示是第一条数据,直接输出,不为空则前面有数据,判断是否更新上一条数据,并输出当前数据;
另外Last row 不同的地方是,如果接收的 cdc 数据源源,是可以支持删除前一条数据的(在本文中暂时不讨论)
public class