Flink作为新一代的流式计算框架,提供了 exactly-once 语义,但是其仅仅支持Flink内部数据流转的 exactly-once 语义,如需保证整条数据链路(即上下游交互)的完整 exactly-once 语义,需要第三方系统支持或者业务上进行保证

项目背景

广告推送系统需要根据 广告的点击量,对第三方进行收费、抽佣,需要做到 exactly-once 语义。

需解决的问题

Flink 内部对 exactly-once 语义的支持,省略了大量 开发成本,但是因为公司 kafka 技术栈 仍然停留在 0.8.2 版本(Kafka 0.11.0.0版本才引入了幂等语义和事务,才为支持 exactly-once 语义提供方便),所以考虑 通过 业务进行控制,以实现 exactly-once 语义。

要实现 Flink 与 第三方组件的 exactly-once 语义,需考虑解决以下问题:

  1. Flink上游 组件 重复消费
  2. Flink上游 组件 丢数据
  3. Flink下游 组件 丢数据
  4. Flink下游 组件 重复发送数据

1. Flink上游 Kafka Source Consumer 重复消费

主要原因:重启Flink应用,导致 Consumer 有些数据处理了,但是没来得及提交 offset,那么少部分数据就会 被再次消费

解决方式

开发一套 幂等性 机制

业务上 每条数据 都生成唯一的id,每次处理数据的时候去缓存(内存、redis)中查,看有没有处理过;

如果缓存中已存在id,则表示已处理过,此条数据无需不处理;如果缓存中不存在此id,则表示未处理过,需要处理

Flink是分布式框架,Kafka Source Consumer 线程分布在不同的机器上,所以本地缓存无法实现,考虑借用 redis 实现 缓存功能

采用二级缓存:实现Map的软引用 + Redis缓存

Redis架构与配置:
1. 申请 主从架构 + 哨兵(sentinal)部署方式,保证高可用
2. 开启持久化机制(RDB + AOF)
3. 一主一从:redis单机内存为8G

2. Flink上游 Kafka Source Consumer 丢数据 (此情况不会发生)

2.1 Kafka Consumer 会丢数据,主要原因是:

offset 已自动提交,但是处理数据的时候 Consumer (Flink程序)挂了,消息没来得及处理,消息就丢了

2.2 常规解决方式:

关闭 kafka offset 自动提交,在处理完数据的时候手动提交。针对此时 Consumer 挂了会出现重复消费的情况,用幂等性保证(第一点)

处理结束提交offset(无需每处理一条数据就提交一次,可处理一批数据后再提交)

但是在Flink中 如果采用 Checkpoint exactly-once 语义机制,可以避免这一点,因为 在 Checkpoint 点,可保证数据已发送给下游,不会丢失数据

3. Flink下游 Kafka Sink Producer 丢数据

3.1 主要原因

Kafka Producer 数据已发送,但未到 leader 机器,或者 消息已到 leader 机器,未来得及同步到 follower,此时 leader crash

3.2 解决方式

依赖 Kafka isr 和 ask 机制,可确保不丢数据

配置四个参数:

  1. kafka服务端 replication.factor :这个值必须大于1,要求每个partition必须有至少2个副本
  2. kafka服务端 min.insync.replicas :这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower
  3. producer端 acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了
  4. producer端 retries=MAX:这个是要求一旦写入失败,就无限重试,卡在这里了

4. Flink下游 组件 重复发送数据

Flink Kafka Sink 组件 这层 要做到 不重复发送数据,只能通过预写日志(WAL机制),在Checkpoint点再往下游发送,但是这牺牲了 实时性。

所以可在 Flink 下游系统那里,以类似 上面 第一点(处理重复数据)的方式,避免重复消费。