kafka的早期版本并没有副本这个概念,所以只是用来存储不常用的数据,所以数据很可能会丢失;随着Kafka的后续更新发展,为了保证数据的可靠性,Kafka 从 0.8.0 版本开始引入了分区副本。每个分区可以人为的配置几个副本(比如创建主题的时候指定 replication-factor
,也可以在 Broker 级别进行配置default.replication.factor
),一般会设置为3。
Kafka 可以保证单个分区里的事件是有序的,但是多分区不行,分区可以在线(可用),也可以离线(不可用)。在分区副本里面有一个副本是 Leader,其余的副本是 follower,所有的读写操作都是经过 Leader 进行的,同时 follower 会定期地去 leader 上的复制数据。当 Leader 挂了的时候,其中一个 follower 会重新成为新的 Leader。通过分区副本,引入了数据冗余,同时也提供了 Kafka 的数据可靠性。
Kafka 的分区多副本架构是 Kafka 可靠性保证的核心,把消息写入多个副本可以使 Kafka 在发生崩溃时仍能保证消息的持久性。
使用过Kafka的朋友都知道,要往 Kafka 对应的主题发送消息,需要通过 Producer 完成。Kafka 的topic对应了多个分区,每个分区下面又对应了多个副本;为了让用户设置数据可靠性, Kafka 在 Producer 里面提供了消息确认机制。也就是说我们可以通过配置来决定消息发送到对应分区的几个副本才算消息发送成功。可以在定义 Producer 时通过 acks
参数指定(在 0.8.2.X 版本之前是通过 request.required.acks
参数设置的)。这个参数支持以下三种值:
min.insync.replicas
参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到悄息,producer会一直重试直到消息被成功提交。这种方式也是最慢的做法,因为producer在继续发送其他消息之前需要等待所有副本都收到当前的消息。但是当ISR列表中只有Leader时,同样有可能造成数据丢失。要保证数据不丢除了设置acks=-1, 还要保证ISR的大小大于等于2。根据实际的应用场景,我们设置不同的
acks
,以此保证数据的可靠性。
另外,Producer 发送消息还可以选择同步(默认,通过 producer.type=sync
配置) 或者异步(producer.type=async
)模式。如果设置成异步,虽然会极大的提高消息发送的性能,但是这样会增加丢失数据的风险。如果需要确保消息的可靠性,必须将 producer.type
设置为 sync。
说了选举就要说ISR列表,每个分区的 leader 会维护一个 ISR 列表,ISR 列表里面就是 follower 副本的 Borker 编号,只有跟得上 Leader 的 follower 副本才能加入到 ISR 里面,这个是通过 replica.lag.time.max.ms
参数配置的,只有 ISR 里的成员才有被选为 leader 的可能。
当 Leader 挂掉了,而且 unclean.leader.election.enable=false
的情况下,Kafka 会从 ISR 列表中选择第一个 follower 作为新的 Leader,因为这个分区拥有最新的已经 committed 的消息。通过这个可以保证已经 committed 的消息的数据可靠性。
综上所述,为了保证数据的可靠性,我们最少需要配置一下几个参数:
Kafka的数据一致性是说不论是老的 Leader 还是新选举的 Leader,Consumer 都能读到一样的数据。
假设分区的副本为3,其中副本Replica0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。虽然副本0已经写入了 Message4和5,但是 Consumer 只能读取到 Message3。因为所有的 ISR 都同步了 Message3,只有 High Water Mark(高水位) 以上的消息才支持 Consumer 读取,而 High Water Mark 取决于 ISR 列表里面偏移量最小的分区,对应于上图的副本Replica2,这个很类似于木桶原理。
这里解释一下High WaterMark,Partition的高水位,取每个partition中对应的ISR中最小的LogEndOffset作为High WaterMark,producer最多只能消费到High WaterMark所在的位置,每个replica都有自己的High WaterMark,Leader和follower各自负责更新自己的High WaterMark状态,并且High WaterMark <= Leader.LogEndOffset.
这样做的原因是还没有被足够多副本复制的消息被认为是“不安全”的,如果 Leader 发生崩溃,另一个副本成为新 Leader,那么这些消息很可能丢失了。如果我们允许消费者读取这些消息,可能就会破坏一致性。试想,一个消费者从当前 Leader(副本0) 读取并处理了 Message5,这个时候 Leader 挂掉了,选举了副本1为新的 Leader,这时候另一个消费者再去从新的 Leader 读取消息,发现这个消息其实并不存在,这就导致了数据不一致性问题。
这样就能保证即使Leader Broker失效,消息仍然可以从新选举的Leader中获取,对于来自内部Broker的读取请求,没有High WaterMark的限制,follower也会维护一份自己的High WaterMark,follower.HW = min(Leader.HW,Follower.offset)
当然,引入了 High Water Mark 机制,会导致 Broker 间的消息复制因为某些原因变慢,那么消息到达消费者的时间也会随之变长(因为我们会先等待消息复制完毕)。延迟时间可以通过参数 replica.lag.time.max.ms
参数配置,它指定了副本在复制消息时可被允许的最大延迟时间。