1. 背景描述
flink实时任务:
- 从kafka集群读取源数据
- 从redis定期全量拉取用户白名单,然后进行广播
- 源数据connect白名单数据,源数据根据白名单数据进行过滤处理
- 过滤处理完后的数据,http推送、写redis、写log等
2. 问题分析
上线验证的时候,有些数据丢失,而且比较频繁,分析可能原因:
- kafka源数据丢失。这个有可能,但是小概率事件,不应该那么频繁出现。
- redis拉取白名单后广播操作。这个有可能,需要重点排查。
- 过滤以及数据处理。这个不太可能,可以添加日志证明自己猜想,实际也是没有问题的。
- http推送、写redis、写log等错误。这些操作比较稳定,也没有太大问题。
3. 定位排查
3.1 redis拉取数据并广播
分析代码,有两个问题
- redis拉取间隔短,每隔2秒钟拉取一次。如果间隔短,数据量大会有性能问题;如果间隔大,白名单生效时间又受影响。刷新间隔将2秒改成2分钟,还是可以接受。
- 广播变更更新逻辑:获取广播状态,先clear后更新,clear和更新之间会有问题,示例代码如下:
if (in2._2.nonEmpty) {
val broadcastState = context.getBroadcastState(whiteListDesc) // 获取广播状态
broadcastState.clear // 先删除
broadcastState.put(in2._1, in2._2) // 后更新
}
上述代码有三个问题:一是clear后可能刚好有有数据到达,但还put还没有生效,这时数据无法正确处理;二是clear操作其实可以删除,put操作时会删除旧和更新最新值;三是没有比较新旧值,直接put操作,频率写操作,性能低下,事实上白名单也并不经常更新,所以put之前最好判断一下。
正确处理方式:应该获取广播变量,与最新数据对比,如果不相等,才更新广播,优化后如下 :
// 更新状态
if (value._2.nonEmpty) {
val broadcastState = context.getBroadcastState(whiteListDesc) // 获取广播状态
// 获取广播变量,新旧比较,如果不等才更新,否则忽略
val newSet = value._2
val oldSet = broadcastState.get(value._1)
if (Tools.equals(oldSet, newSet) == false) {
broadcastState.put(value._1, value._2)
}
}
3.2 kafka集群消息丢失
变量广播问题解决之后,发现还是有数据丢失。仔细跟踪,生产端是双活生产,就是数据可能随机写两个kafka集群,但消费端将两个fafka集群的brokers合成当作一个kafka集群的brokers。实际两个集群的brokers之间是有分号隔开的。
示例代码如下
DataStream<ConsumerRecord<String, String>> stream = env.addSource(getConsumer(params, brokers));
将两个或者多个集群的brokers按分号隔开,创建两个或者多个DataStream,然后再union,修改后如下
DataStream<ConsumerRecord<String, String>> streams = null;
List<String> brokerList = Arrays.asList(params.get(game + ".kafka.source.brokers").split(";"));
for (int index = 0; index < brokerList.size(); ++index) {
String brokers = brokerList.get(index);
DataStream<ConsumerRecord<String, String>> stream = env.addSource(getConsumer(params, brokers))
.setParallelism(parallelism)
.name("kafka-reader-" + index)if (streams == null) {
streams = stream;
} else {
streams = streams.union(stream);
}
}
重新验证问题,数据丢失的问题解决。