面试讲不清 MySQL 索引底层,Java 面试
Kafka 的多副本冗余设计
不管是传统的基于关系型数据库设计的系统,还是分布式的如zookeeper
、redis
、Kafka
、HDFS
等等,实现高可用的办法通常是采用冗余设计,通过冗余来解决节点宕机不可用问题。
首先简单了解 Kafka 的几个概念:
物理模型
逻辑模型
Broker(节点):Kafka 服务节点,简单来说一个
Broker
就是一台 Kafka 服务器,一个物理节点。Topic(主题):在 Kafka 中消息以主题为单位进行归类,每个主题都有一个
Topic Name
,生产者根据 Topic Name 将消息发送到特定的 Topic,消费者则同样根据 Topic Name 从对应的 Topic 进行消费。Partition(分区):
Topic
(主题)是消息归类的一个单位,但每一个主题还能再细分为一个或多个Partition
(分区),一个分区只能属于一个主题。主题和分区都是逻辑上的概念,举个例子,消息 1 和消息 2 都发送到主题 1,它们可能进入同一个分区也可能进入不同的分区(所以同一个主题下的不同分区包含的消息是不同的),之后便会发送到分区对应的 Broker 节点上。Offset(偏移量):分区可以看作是一个只进不出的队列(Kafka 只保证一个分区内的消息是有序的),消息会往这个队列的尾部追加,每个消息进入分区后都会有一个偏移量,标识该消息在该分区中的位置,消费者要消费该消息就是通过偏移量来识别。
其实,根据上述的几个概念,是不是也多少猜到了 Kafka 的多副本冗余设计实现了?别急,咱继续往下看。
在 Kafka 0.8 版本以前,是没有多副本冗余机制的,一旦一个节点挂掉,那么这个节点上的所有Partition
的数据就无法再被消费。这就等于发送到 Topic 的有一部分数据丢失了。
在 0.8 版本后引入副本记者则很好地解决宕机后数据丢失的问题。副本是以Topic
中每个Partition
的数据为单位,每个 Partition 的数据会同步到其他物理节点上,形成多个副本。
每个Partition
的副本都包括一个Leader
副本和多个Follower
副本,Leader 由所有的副本共同选举得出,其他副本则都为 Follower 副本。在生产者写或者消费者读的时候,都只会与 Leader 打交道,在写入数据后 Follower 就会来拉取数据进行数据同步。
就这么简单?是的,基于上面这张多副本架构图就实现了 Kafka 的高可用。当某个Broker
挂掉了,甭担心,这个Broker
上的Partition
在其他Broker
节点上还有副本。你说如果挂掉的是Leader
怎么办?那就在Follower
中在选举出一个Leader
即可,生产者和消费者又可以和新的Leader
愉快地玩耍了,这就是高可用。
你可能还有疑问,那要多少个副本才算够用?Follower 和 Leader 之间没有完全同步怎么办?一个节点宕机后 Leader 的选举规则是什么?
直接抛结论:
多少个副本才算够用? 副本肯定越多越能保证 Kafka 的高可用,但越多的副本意味着网络、磁盘资源的消耗更多,性能会有所下降,通常来说副本数为 3 即可保证高可用,极端情况下将replication-factor
参数调大即可。
Follower 和 Lead 之间没有完全同步怎么办? Follower 和 Leader 之间并不是完全同步,但也不是完全异步,而是采用一种ISR
机制(In-Sync Replica
)。每个 Leader 会动态维护一个 ISR 列表,该列表里存储的是和 Leader 基本同步的 Follower。如果有 Follower 由于网络、GC 等原因而没有向 Leader 发起拉取数据请求,此时 Follower 相对于 Leader 是不同步的,则会被踢出 ISR 列表。所以说,ISR 列表中的 Follower 都是跟得上 Leader 的副本。
一个节点宕机后 Leader 的选举规则是什么? 分布式相关的选举规则有很多,像 Zookeeper 的Zab
、Raft
、Viewstamped Replication
、微软的PacificA
等。而 Kafka 的 Leader 选举思路很简单,基于我们上述提到的ISR
列表,当宕机后会从所有副本中顺序查找,如果查找到的副本在 ISR 列表中,则当选为 Leader。另外还要保证前任 Leader 已经是退位状态了,否则会出现脑裂情况(有两个 Leader)。怎么保证?Kafka 通过设置了一个 controller 来保证只有一个 Leader。
Ack 参数决定了可靠程度
另外,这里补充一个面试考 Kafka 高可用必备知识点:request.required.asks
参数。
Asks 这个参数是生产者客户端的重要配置,发送消息的时候就可设置这个参数。该参数有三个值可配置:0、1、All。
第一种是设为 0,意思是生产者把消息发送出去之后,之后这消息是死是活咱就不管了,有那么点发后即忘的意思,说出去的话就不负责了。不负责自然这消息就有可能丢失,那就把可用性也丢失了。
第二种是设为 1,意思是生产者把消息发送出去之后,这消息只要顺利传达给了 Leader,其他 Follower 有没有同步就无所谓了。存在一种情况,Leader 刚收到了消息,Follower 还没来得及同步 Broker 就宕机了,但生产者已经认为消息发送成功了,那么此时消息就丢失了。注意,设为 1 是 Kafka 的默认配置!!!可见 Kafka 的默认配置也不是那么高可用,而是对高可用和高吞吐量做了权衡折中。
第三种是设为 All(或者-1),意思是生产者把消息发送出去之后,不仅 Leader 要接收到,ISR 列表中的 Follower 也要同步到,生产者才会任务消息发送成功。
进一步思考,Asks=All
就不会出现丢失消息的情况吗?答案是否。当 ISR 列表只剩 Leader 的情况下,Asks=All
相当于Asks=1
,这种情况下如果节点宕机了,还能保证数据不丢失吗?因此只有在Asks=All
并且有 ISR 中有两个副本的情况下才能保证数据不丢失。
解决问题
绕了一大圈,了解了 Kafka 的高可用机制,终于回到我们一开始的问题本身,Kafka
的一个节点宕机后为什么不可用?
我在开发测试环境配置的Broker
节点数是 3,Topic
是副本数为 3,Partition
数为 6,Asks
参数为 1。
当三个节点中某个节点宕机后,集群首先会怎么做?没错,正如我们上面所说的,集群发现有 Partition 的 Leader 失效了,这个时候就要从 ISR 列表中重新选举 Leader。如果 ISR 列表为空是不是就不可用了?并不会,而是从 Partition 存活的副本中选择一个作为 Leader,不过这就有潜在的数据丢失的隐患了。
所以,只要将 Topic 副本个数设置为和 Broker 个数一样,Kafka 的多副本冗余设计是可以保证高可用的,不会出现一宕机就不可用的情况(不过需要注意的是 Kafka 有一个保护策略,当一半以上的节点不可用时 Kafka 就会停止)。那仔细一想,Kafka 上是不是有副本个数为 1 的 Topic?
问题出在了__consumer_offset
上,__consumer_offset
是一个 Kafka 自动创建的Topic
,用来存储消费者消费的offset
(偏移量)信息,默认Partition
数为 50。而就是这个 Topic,它的默认副本数为 1。如果所有的Partition
都存在于同一台机器上,那就是很明显的单点故障了!当将存储__consumer_offset
的 Partition 的 Broker 给 Kill 后,会发现所有的消费者都停止消费了。
这个问题怎么解决?
第一点,需要将__consumer_offset
删除,注意这个 Topic 时 Kafka 内置的 Topic,无法用命令删除,我是通过将logs
删了来实现删除。
第二点,需要通过设置offsets.topic.replication.factor
为 3 来将__consumer_offset
的副本数改为 3。
通过将__consumer_offset
也做副本冗余后来解决某个节点宕机后消费者的消费问题。
最后,关于为什么__consumer_offset
的 Partition 会出现只存储在一个 Broker 上而不是分布在各个 Broker 上感到困惑,如果有朋友了解的烦请指教~
最后
分享一些系统的面试题,大家可以拿去刷一刷,准备面试涨薪。
这些面试题相对应的技术点:
JVM
MySQL
Mybatis
MongoDB
Redis
Spring
Spring boot
Spring cloud
Kafka
RabbitMQ
Nginx
......
大类就是:
Java 基础
数据结构与算法
并发编程
数据库
设计模式
微服务
消息中间件
评论