目录
场景:
某些特殊业务场景需要延迟数据处理,比如乱序数据。
某些业务场景只需要保留最新数据,中间更新过程忽略不计,比如客服问卷最新状态。
某些业务场景需要结合最近一段时间的数据进行处理,比如客服侧由于系统短时间单条数据更新多个字段,系统侧更新字段顺序错误,导致中间记录的 binlog 数据可能有误,需要结合一段时间的数据进行修正数据。
onTimer 使用关键流程
数据处理流程
- key by 分区,把相同 key 的数据发送到同一个分区。
-
单条数据会设置定时器,可以让单个分区里面多条数据的定时器时间一样,那就只触发一次定时器。
-
单条数据可以更新 state,然后定时器触发时,可以再取 state 的数据进行业务逻辑处理。
代码交互流程
-
processElement 单条数据处理
-
onTimer 定时器回调
-
Managed State 缓存状态
-
TimerService 时间服务
onTimer 延迟数据处理的优劣
优点:
-
利用自带的 state,丰富的数据结构 ValueState 存最新数据、ListState 最一段时间数据、MapState 存 kv 数据。
-
下发数据灵活:定时器下发数据,每条数据都可以注册一个定时器下发一次数据,多条数据的定时器也可以合并成一个定时器下发一次数据。
-
定时器触发时数据处理非常灵活:可以取到 state 里面的所有数据,然后根据业务的逻辑进行数据处理。
-
下发数据量减少:比如2s延迟,多条数据合并成一个定时器,如果业务只要最新状态,就只需要下发一条数据。
缺点:
-
数据会有一定延迟。
onTimer 编码实践
业务场景描述
客服问卷只要最新状态的情况,比如问卷评价的数据一定在问卷未评价的数据之后,有时候问卷已评价和未评价的数据时间相近,下游消费时会乱序,进而发生未评价的数据更新已评价的数据,数据库里面问卷最新状态就是未评价了。
通过 onTimer 延迟5s数据处理,把5s内的数据统一处理,然后只保留最新问卷下发,比如5s内有未评价和已评价两条数据,那就只下发一条已评价数据就行,下游消费就不会乱序了。
代码
1.按照问卷 id 进行 keyBy 分区。
DataStream<String> npsMainStream = npsMainEntityDataStream
.keyBy(new KeySelector<NpsMainEntity, String>() {
@Override public String getKey(NpsMainEntity npsMainEntity) throws Exception {
String key = npsMainEntity.getId() + "";
return key;
}
}).process(new StateTimerForOutOrderDataFunction());
2.processElement 方法里面每条数据来更新 state,这里有两种情况:
-
ValueState 为空会被更新。
-
已评价数据会更新 ValueState。
processElement 里面每条数据会注册一个定时器,ctx.timerService().registerProcessingTimeTimer(triggerTime);
3.onTimer 方法里面定时器触发,可以查询 state 进行业务逻辑处理
/** 延迟下发数据,解决并发乱序问题 */
static class StateTimerForOutOrderDataFunction extends KeyedProcessFunction<String, NpsMainEntity, String> {
private final static int ttlMinute = 1000 * 5;
private ValueState<NpsMainEntity> sendCacheStorage;
@Override
public void open(Configuration parameters) throws Exception {
sendCacheStorage = getRuntimeContext().getState(new ValueStateDescriptor<NpsMainEntity>("CacheStorage", NpsMainEntity.class));
}
@Override public void processElement(NpsMainEntity npsMainEntity, Context ctx, Collector<String> out)
throws Exception {
try {
// 每条 record 注册事件 ttlMinute 代表延迟时间
/** 状态为空,或者状态是未评价,5s时间解决乱序问题 */
if (sendCacheStorage.value() == null || sendCacheStorage.value().getEvaluationStatus() == 2
|| npsMainEntity.getEvaluationCnt() == 1) {
sendCacheStorage.update(npsMainEntity);
writeSampleLog("update:" + npsMainEntity.toString(), NumberEnum.SEVEN);
// 合并定时器,秒级触发
long triggerTime = (System.currentTimeMillis() + ttlMinute) /1000 * 1000;
ctx.timerService().registerProcessingTimeTimer(triggerTime);
}
// 相关操作
} catch (Exception e) {
logger.error(e.getMessage());
}
}
// 到达时间点触发事件
@Override public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out)
throws Exception {
String onTimerValue = JSON.toJSONString(sendCacheStorage.value());
out.collect(onTimerValue);
writeSampleLog("onTimer onTimerValue:" + onTimerValue, NumberEnum.ZERO);
}
}