简单总结:
消费端重复消费:建立去重表
消费端丢失数据:关闭自动提交offset,处理完之后受到移位,enable.auto.commit=false 关闭自动提交位移
生产端重复发送:消费端消费之前从去重表中判重
生产端丢失数据:这个是最麻烦的情况
解决策略:
1.异步方式缓冲区满了,就阻塞在那,等着缓冲区可用,不能清空缓冲区
2.发送消息之后回调函数,发送成功就发送下一条,发送失败就记在日志中,等着定时脚本来扫描(发送失败可能并不真的发送失败,只是没收到反馈,定时脚本可能会重发)
数据丢失情况:
1)使用同步模式的时候,有3种状态保证消息被安全生产,在配置为1(只保证写入leader成功)的话,如果刚好leader partition挂了,数据就会丢失。
2)还有一种情况可能会丢失消息,就是使用异步模式的时候,当缓冲区满了,如果配置为0(还没有收到确认的情况下,缓冲池一满,就清空缓冲池里的消息),数据就会被立即丢弃掉。
只要能避免上述两种情况,那么就可以保证消息不会被丢失。
1)就是说在同步模式的时候,确认机制设置为-1,也就是让消息写入leader和所有的副本。
2)还有,在异步模式下,如果消息发出去了,但还没有收到确认的时候,缓冲池满了,在配置文件中设置成不限制阻塞超时的时间,也就说让生产端一直阻塞,这样也能保证数据不会丢失。
ack:
ack确认机制设置为0,表示不等待响应,不等待borker的确认信息,最小延迟,producer无法知道消息是否发生成功,消息可能丢失,但具有最大吞吐量。
ack确认机制设置为-1,也就是让消息写入leader和所有的副本,ISR列表中的所有replica都返回确认消息。
ack确认机制设置为1,leader已经接收了数据的确认信息,replica异步拉取消息,比较折中。
ack确认机制设置为2,表示producer写partition leader和其他一个follower成功的时候,broker就返回成功,无论其他的partition follower是否写成功。
ack确认机制设置为 "all" 即所有副本都同步到数据时send方法才返回, 以此来完全判断数据是否发送成功, 理论上来讲数据不会丢失。
min.insync.replicas=1 意思是至少有1个replica返回成功,否则product异常
总结:
消息的完整性和系统的吞吐量是互斥的,为了确保消息不丢失就必然会损失系统的吞吐量
producer:
1、ack设置-1
2、设置副本同步成功的最小同步个数为副本数-1
3、加大重试次数
4、同步发送
5、对于单条数据过大,要设置可接收的单条数据的大小
6、对于异步发送,通过回调函数来感知丢消息,使用KafkaProducer.send(record, callback)方法而不是send(record)方法
7、配置不允许非ISR(In-Sync Replicas,副本同步队列)集合中的副本当leader。所有的副本(replicas)统称为 Assigned Replicas,即 AR
8、客户端缓冲区满了也可能会丢消息;或者异步情况下消息在客户端缓冲区还未发送,客户端就宕机
9、block.on.buffer.full = true
consumer:
1、enable.auto.commit=false 关闭自动提交位移
unclean.leader.election.enable 设置为 false(默认参数为 true),意思是,当存有你最新一条记录的 replication 宕机的时候,Kafka 自己会选举出一个主节点,如果默认允许还未同步你最新数据的 replication 所在的节点被选举为主节点的话,你的数据将会丢失,因此这里应该按需将参数调控为 false;
retries设置大一些。设置大于0的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。
replication.factor > min.insync.replicas。如果两者相等,当一个副本挂掉了分区也就没法正常工作了。通常设置replication.factor = min.insync.replicas + 1即可。
同一分区消息乱序:
假设a,b两条消息,a先发送后由于发送失败重试,这时顺序就会在b的消息后面,可以设置max.in.flight.requests.per.connection=1来避免
max.in.flight.requests.per.connection:限制客户端在单个连接上能够发送的未响应请求的个数。设置此值是1表示kafka broker在响应请求之前client不能再向同一个broker发送请求,但吞吐量会下降
0.11.0之后的版本:
幂等性发送:
引入了Producer ID(PID)和Sequence Number实现Producer的幂等语义。
- Producer ID:每个新的Producer在初始化的时候会被分配一个唯一的PID
- Sequence Number:对于每个PID,该Producer发送数据的每个<Topic, Partition>都对应一个从0开始单调递增的Sequence Number。
Broker端也会为每个<PID, Topic, Partition>维护一个序号,并且每次Commit一条消息时将其对应序号递增。对于接收的每条消息,如果其序号比Broker维护的序号(即最后一次Commit的消息的序号)大一,则Broker会接受它,否则将其丢弃:
- 如果消息序号比Broker维护的序号大一以上,说明中间有数据尚未写入,也即乱序,此时Broker拒绝该消息,Producer抛出InvalidSequenceNumber
- 如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息,Producer抛出DuplicateSequenceNumber
这种机制很好的解决了数据重复和数据乱序的问题。
事务机制:
多个操作要么全部成功要么全部失败。Kafka事务的本质是,将一组写操作(如果有)对应的消息与一组读操作(如果有)对应的Offset的更新进行同样的标记(即Transaction Marker)来实现事务中涉及的所有读写操作同时对外可见或同时对外不可见。
补充ISR:
HW 俗称高水位,HighWatermark 的缩写,取一个 partition 对应的 ISR 中最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置。另外每个 replica 都有 HW,leader 和 follower 各自负责更新自己的 HW 的状态。对于 leader 新写入的消息,consumer 不能立刻消费,leader 会等待该消息被所有 ISR 中的 replicas 同步后更新 HW,此时消息才能被 consumer 消费。这样就保证了如果 leader 所在的 broker 失效,该消息仍然可以从新选举的 leader 中获取。对于来自内部 broKer 的读取请求,没有 HW 的限制。
下图详细的说明了当 producer 生产消息至 broker 后,ISR 以及 HW 和 LEO 的流转过程:
由此可见,Kafka 的复制机制既不是完全的同步复制,也不是单纯的异步复制。事实上,同步复制要求所有能工作的 follower 都复制完,这条消息才会被 commit,这种复制方式极大的影响了吞吐率。而异步复制方式下,follower 异步的从 leader 复制数据,数据只要被 leader 写入 log 就被认为已经 commit,这种情况下如果 follower 都还没有复制完,落后于 leader 时,突然 leader 宕机,则会丢失数据。而 Kafka 的这种使用 ISR 的方式则很好的均衡了确保数据不丢失以及吞吐率。
Kafka 的 ISR 的管理最终都会反馈到 Zookeeper 节点上。具体位置为:/brokers/topics/[topic]/partitions/[partition]/state。目前有两个地方会对这个 Zookeeper 的节点进行维护:
1.Controller 来维护:Kafka 集群中的其中一个 Broker 会被选举为 Controller,主要负责 Partition 管理和副本状态管理,也会执行类似于重分配 partition 之类的管理任务。在符合某些特定条件下,Controller 下的 LeaderSelector 会选举新的 leader,ISR 和新的 leader_epoch 及 controller_epoch 写入 Zookeeper 的相关节点中。同时发起 LeaderAndIsrRequest 通知所有的 replicas。
2.leader 来维护:leader 有单独的线程定期检测 ISR 中 follower 是否脱离 ISR, 如果发现 ISR 变化,则会将新的 ISR 的信息返回到 Zookeeper 的相关节点中。
参考文献:
https://www.infoq.cn/article/depth-interpretation-of-kafka-data-reliability