Kafka 面试题

什么是消息队列

消息队列是一种进程间通信或者同一个进程中不同线程间的通信方式。主要解决异步处理、应用耦合、流量消峰等问题,实现高性能、高可用、可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。

我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。

参与消息传递的双方称为生产者和消费者,生产者负责发送消息,消费者负责处理消息。

简单地说,消息队列就是系统 A 将消息发布到 MQ,然后系统 B 再从 MQ 取消息。

为什么要使用消息队列

那么为什么系统 A 不直接发消息给系统 B 呢?

使用消息队列的目的:

  • 服务解耦
  • 异步通信
  • 流量削峰

1.服务解耦

比如在一个商城系统中,订单服务 A 在用户下完订单后,需要通知服务 B 做一些业务操作,如果不使用消息队列,那么就要在 A 服务中写访问 B 服务的业务代码,此外,A 服务还要考虑 B 服务的可用性,比如对访问超时甚至异常的处理,是否需要失败重试机制等,让 A 服务的代码复杂度上升。如果业务需求增加新的对接服务时,那 A 服务又要修改代码,让 A 服务可维护性降低,对 A 服务的系统稳定性不友好。

如果使用消息队列,A 服务只用关心把消息发给消息队列,不用关心其他服务了;其他服务也只需要从消息队列中消费消息,不用关心 A 的可用性。

2.异步通信

在上面解耦的例子中,其实也是 A 服务跟 B 服务之间的异步通信。如果不用消息队列,那就是 A 对 B 的同步访问,需要等 B 服务做出相应,A 服务的程序才结束。这就增加了 A 服务的接口耗时。如果是 A 系统给消息队列发消息,那对于 A 来说,就没有访问 B 系统的耗时了。

当然这种场景也可以使用编程实现异步,一般是使用线程池。但是在接口要求高吞吐量的场景下,线程池是很小的一个进程,它跟一个独立部署的消息中间件的性能是无法比拟的。

3.流量削峰

在系统流量激增的时候,比如商城系统搞促销,系统收到的请求数过高超过系统的处理能力时,系统就会崩溃宕机。这种场景,可以将用户的请求放到消息队列中,然后系统按照自己的处理速度从消息队列中去消息,起到流量削峰的作用。

什么是 Kafka

Apache Kafka 是 一个分布式流处理平台. 这到底意味着什么呢?
我们知道流处理平台有以下三种特性:

可以让你发布和订阅流式的记录。这一方面与消息队列或者企业消息系统类似。
可以储存流式的记录,并且有较好的容错性。
可以在流式记录产生时就进行处理。

为什么选择 Kafka?

Kafka 主要有两大应用场景:

  • 消息队列:建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
  • 数据处理: 构建实时的流数据处理程序来转换或处理数据流。

和其他消息队列相比,Kafka 的优势在哪里?

我觉得 Kafka 相比其他消息队列主要的优势如下:

  • 极致的性能:基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
  • 生态系统兼容性无可匹敌:Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域。

Kafk名字的由来

Kafka 开发者 Jay Kreps 提及关于 Kafka 名字的由来:

“ 因为 Kafka 系统的写性能很强,所以找一个作家的名字来命名似乎是个好主意,大学期间我上了很多文学课,非常喜欢 Franz Kafka 这个作家,另外,这个名字听上去也很酷~~ ”

Kafka 的架构

  • Producer:Producer 即生产者,消息的产生者,是消息的入口
  • Broker:Broker 是 kafka 一个实例,每个服务器上有一个或多个 kafka 的实例,简单的理解就是一台 kafka 服务器,kafka cluster表示集群的意思
  • Topic:消息的主题,可以理解为消息队列,kafka的数据就保存在topic。在每个 broker 上都可以创建多个 topic 。
  • Partition:Topic的分区,每个 topic 可以有多个分区,分区的作用是做负载,提高 kafka 的吞吐量。同一个 topic 在不同的分区的数据是不重复的,partition 的表现形式就是一个一个的文件夹!
  • Replication:每一个分区都有多个副本,副本的作用是做备胎,主分区(Leader)会将数据同步到从分区(Follower)。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为 Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本
  • Message:每一条发送的消息主体。
  • Consumer:消费者,即消息的消费方,是消息的出口。
  • Consumer Group:我们可以将多个消费组组成一个消费者组,在 kafka 的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量!
  • Zookeeper:kafka 集群依赖 zookeeper 来保存集群的的元信息,来保证系统的可用性。

简而言之,kafka 本质就是一个消息系统,与大多数的消息系统一样,主要的特点如下:

  • 使用推拉模型将生产者和消费者分离
  • 为消息传递系统中的消息数据提供持久性,以允许多个消费者
  • 提供高可用集群服务,主从模式,同时支持横向水平扩展

与 ActiveMQ、RabbitMQ、RocketMQ 不同的地方在于,它有一个分区Partition的概念。

这个分区的意思就是说,如果你创建的topic有5个分区,当你一次性向 kafka 中推 1000 条数据时,这 1000 条数据默认会分配到 5 个分区中,其中每个分区存储 200 条数据。

这样做的目的,就是方便消费者从不同的分区拉取数据,假如你启动 5 个线程同时拉取数据,每个线程拉取一个分区,消费速度会非常非常快!

发送数据

和其他的中间件一样,kafka 每次发送数据都是向Leader分区发送数据,并顺序写入到磁盘,然后Leader分区会将数据同步到各个从分区Follower,即使主分区挂了,也不会影响服务的正常运行。

那 kafka 是如何将数据写入到对应的分区呢?kafka中有以下几个原则:

1、数据在写入的时候可以指定需要写入的分区,如果有指定,则写入对应的分区
2、如果没有指定分区,但是设置了数据的key,则会根据key的值hash出一个分区
3、如果既没指定分区,又没有设置key,则会轮询选出一个分区

消费数据

与生产者一样,消费者主动的去kafka集群拉取消息时,也是从Leader分区去拉取数据。

这里我们需要重点了解一个名词:消费组!

考虑到多个消费者的场景,kafka 在设计的时候,可以由多个消费者组成一个消费组,同一个消费组者的消费者可以消费同一个 topic 下不同分区的数据,同一个分区只会被一个消费组内的某个消费者所消费,防止出现重复消费的问题!

但是不同的组,可以消费同一个分区的数据!

你可以这样理解,一个消费组就是一个客户端,一个客户端可以由很多个消费者组成,以便加快消息的消费能力。

但是,如果一个组下的消费者数量大于分区数量,就会出现很多的消费者闲置。

如果分区数量大于一个组下的消费者数量,会出现一个消费者负责多个分区的消费,会出现消费性能不均衡的情况。

因此,在实际的应用中,建议消费者组的consumer的数量与partition的数量保持一致!

什么是消费者组?

消费者组是 Kafka 独有的概念,如果面试官问这个,就说明他对此是有一定了解的。我先给出标准答案:关于它的定义,官网上的介绍言简意赅,即消费者组是 Kafka 提供的可扩展且具有容错性的消费者机制。切记,一定要加上前面那句,以显示你对官网很熟悉。另外,你最好再解释下消费者组的原理:在 Kafka 中,消费者组是一个由多个消费者实例构成的组。多个实例共同订阅若干个主题,实现共同消费。同一个组下的每个实例都配置有相同的组 ID,被分配不同的订阅分区。当某个实例挂掉的时候,其他实例会自动地承担起它负责消费的分区。

Kafka 的 consumer 是推还是拉?

  • 拉。producer 将消息推送到 broker,consumer 从 broker 拉取消息。
  • 推模式主动将消息推送到下游 consumer 的做法有好有坏,好处是 consumer 能最快速消费消息,但是在推模式下,当 broker 推送的速率远大于 consumer 消费的速率时,consumer 可能会崩溃。所以,由 broker 决定消息推送的速率,对于不同消费速率的 consumer 不好处理。
  • 拉模式的好处: consumer 可以自主决定是否批量的从 broker 拉取数据。拉模式无需考虑下游 consumer 消费能力和消费策略。如果为了避免 consumer 崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。
  • 拉模式有个缺点,如果 broker 没有可供消费的消息,将导致 consumer 不断在循环中轮询,直到新消息到达。为了避免这点,Kafka 有个参数可以让 consumer 阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量推送)。

解释下 Kafka 中位移(offset)的作用

位移概念本身并不复杂,你可以这么回答:在 Kafka 中,每个主题分区下的每条消息都被赋予了一个唯一的 ID 数值,用于标识它在分区中的位置。这个 ID 数值,就被称为位移,或者叫偏移量。一旦消息被写入到分区日志,它的位移值将不能被修改。

Kafka 如何保证消息不重复消费?

消息重复消费的根本原因都在于:已经消费了数据,但是offset没有成功提交。

一、kafka自带的消费机制

  kafka有个offset的概念,当每个消息被写进去后,都有一个offset,代表他的序号,然后consumer消费该数据之后,隔一段时间,会把自己消费过的消息的offset提交一下,代表我已经消费过了。下次我要是重启,就会继续从上次消费到的offset来继续消费。

  但是当我们直接kill进程了,再重启。这会导致consumer有些消息处理了,但是没来得及提交offset。等重启之后,少数消息就会再次消费一次。

  其他MQ也会有这种重复消费的问题,那么针对这种问题,我们需要从业务角度,考虑它的幂等性。

二、通过保证消息队列消费的幂等性来保证

  举个例子,当消费一条消息时就往数据库插入一条数据。如何保证重复消费也插入一条数据呢?

  那么我们就需要从幂等性角度考虑了。幂等性,我通俗点说,就一个数据,或者一个请求,无论来多次,对应的数据都不会改变的,不能出错。

怎么保证消息队列消费的幂等性?

我们需要结合业务来思考,比如下面的例子:

  1.比如某个数据要写库,你先根据主键查一下,如果数据有了,就别插入了,update一下好吧

  2.比如你是写redis,那没问题了,反正每次都是set,天然幂等性

  3.对于消息,我们可以建个表(专门存储消息消费记录)

    生产者,发送消息前判断库中是否有记录(有记录说明已发送),没有记录,先入库,状态为待消费,然后发送消息并把主键id带上。

    消费者,接收消息,通过主键ID查询记录表,判断消息状态是否已消费。若没消费过,则处理消息,处理完后,更新消息记录的状态为已消费。

Kafka 与传统 MQ 消息系统之间有三个关键区别

(1).Kafka 持久化日志,这些日志可以被重复读取和无限期保留
(2).Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过复制数据提升容错能力和高可用性
(3).Kafka 支持实时的流式处理

参考资料

posted @ 2023-06-08 16:52  狭路相逢  阅读(93)  评论(0)    收藏  举报