消息队列架构

一.整体架构

我们可以把Kafka宏观看作三层:Producer(生产者),Server(中转者),Consumer(消费者)

Producer,很好理解,就是生产者,在Kafka中,Producer负责创建消息并将其发送到Kafka服务器。Producer是消息的源头,它们将消息发送到特定的主题中,以供Consumer订阅和消费。

Server,即Kafka服务,可以认为是消息的中转站,因为消息不是从生产者直接发送到消费者的,而是先经过一个中转站存放,就像生活中的菜鸟驿站一样,Kafka的服务端就担任了菜鸟驿站的角色,但跟菜鸟驿站不同的一点是Server会持久化存储信息,不是说消费了就没有了。

  • Broker: 可以理解为机器或者节点把,也可以理解为就是运行Kafka程序的服务器
  • Topic: 主题是Kafka中的一个核心概念,它是对消息进行分类的一种方式。生产者将消息发送到特定的主题中,而消费者则通过订阅主题来接收相关消息。但是要注意的是,主题是一个逻辑概念,实际上,一个主题可以被分为多个分区(Partition),以实现消息的并行处理和负载均衡;数据是存储在Partition这个级别的。
  • **Partition:**分区是Kafka中的一个重要概念,它是主题的物理存储单位。每个分区都是一个有序的、不可变的消息序列,可以被独立地读写。分区在物理上对应一个文件夹及文件夹下面的文件,分区的命名规则为主题名称后接"一"连接符,之后再接分区编号,比如TopicA-1就表示主题A得1号分区,每个分区又可以有一至多个副本(Replica),以提高可用性。

Consumer,即消费者,是Kafka中的另一个重要角色,它们负责订阅主题并消费其中的消息,当生产者向Server传递了消息之后,如果是消费者订阅了对应的主题,那么消费者就会从Server拉取消息做业务处理。

通过上述,我们对Kafka整体有了宏观认知,但是对于一些具体运作的细节,我们还没涉及,所以有一些问题还无法解决,比如:

  1. 比如生产者到底写入哪个Partition?
  2. 消费者如何消费一个多分片的主题?
  3. 多个Broker如何协作?

在下述正式讲解前,先通过b站类比有个大概印象:

1. 角色定义:谁在产生,谁在干活?

  • Producer(生产者) → UP主

    • 动作:UP主上传视频。

    • 本质:生产者往 Kafka 里写入原始数据消息。

  • Consumer(消费者) → 审核员

    • 动作:审核员(小张、小李)坐在流水线旁,按顺序一个一个审视频。

    • 本质:业务后端从 Kafka 拉取数据并进行处理。

2. 空间逻辑:数据存哪,怎么分流?

  • Topic(主题) → B站分区

    • 例子:B站有"动漫"、"生活"、"美食"等分区。

    • 本质:Kafka 里的 Topic 专门存放某一类逻辑相关的消息,实现业务隔离。

  • Partition(分区) → 审核流水线

    • 背景:"美食区"流量太大,一个审核员忙不过来,于是拆成 6 条流水线。

    • 规则:视频在流水线上按上传顺序排列,不能插队、不能删改。

    • 本质:这是 Kafka 高并发的秘密,通过物理拆分实现多线程并行处理。

3. 协作机制:多人怎么分工不乱?

  • Consumer Group(消费者组) → 审核团队

    • 设定:"美食分区审核团队"有 3 个审核员(小张、小李、小王)。

    • 分工:Kafka 自动分配,确保每条流水线都有人守着,且每个人不会重复看同一段视频。

    • 多组并行:可以有"人工审核组"和"AI 训练组"同时看美食区,它们互不干扰,各自有各自的进度。

  • Offset(偏移量) → 审核进度表

    • 功能:记录审核员"上次审到了第几个视频(编号)"。

    • 本质:这是 Kafka 断点续传的保证。即使审核员下班(服务重启),回来后看一眼进度表,就能接着干。

4. 基础设施:系统怎么保证不倒?

  • Broker 集群 → 各地 B 站机房

    • 物理形式:B 站服务器分布在上海、北京、广州。

    • 高可用(Replica):同一个视频会在不同机房存多份(副本)。如果上海机房停电了,北京机房的视频(副本)立刻顶上,审核和播放都不停。

二.Topic主题

Topic就是主题,相同业务可以放同一个主题,我们可以类比MySQL,一类数据就放在一张表里,而消息队列中,某类数据就可以放入一个主题,这其实可以看作数据分片的一种方式。

比如秒杀消息,就放入秒杀主题,比如短信消息,就放入短信主题,通过主题Kafka实现了业务的隔离,从主题A拿到的消息一定是A对应类型的消息,消费者可以做对应的处理,试想如果各式各样的消息混在一起,处理起来得多乱。

那么代码中如何使用topic呢?

在业务开发时候,也可以不必提前创建主题,只要向某个主题发送消息,他就自动创建了

java 复制代码
@RestController
@RequestMapping("/shop")
public class OrderController {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @PostMapping("/pay")
    public String payOrder(@RequestParam String orderId) {
        // 1. 业务逻辑:处理支付...
        String payMsg = "订单 " + orderId + " 支付成功!";

        // 2. 发送到【支付主题】:就像往支付表里插数据
        kafkaTemplate.send("topic-pay-success", payMsg);
        
        return "支付指令已发出";
    }

    @PostMapping("/ship")
    public String shipOrder(@RequestParam String orderId) {
        // 1. 业务逻辑:打单发货...
        String shipMsg = "订单 " + orderId + " 已从上海仓库发出。";

        // 2. 发送到【物流主题】:就像往物流表里插数据
        kafkaTemplate.send("topic-shipping-info", shipMsg);
        
        return "物流指令已发出";
    }
}
java 复制代码
@Component
public class ShopMessageListeners {

    // 这个"收音机"只调到了支付频道
    @KafkaListener(topics = "topic-pay-success", groupId = "group-finance")
    public void handlePayment(String message) {
        System.out.println("【财务系统】收到支付消息,准备开具发票: " + message);
    }

    // 这个"收音机"只调到了物流频道
    @KafkaListener(topics = "topic-shipping-info", groupId = "group-logistics")
    public void handleShipping(String message) {
        System.out.println("【物流中心】收到发货消息,准备更新地图状态: " + message);
    }
}

那么Topic存在哪里?

简单描述的话,可以认为topic是存在服务器上的,Kafka把服务器称作Broker,如果是集群环境下,一个topic实际会跨多个Broker。

实际上,topic在Kafka里其实是一个逻辑上的概念,也就是架构和逻辑上有主题这个概念,但是实际存储的时候不是以一个主题来,而是将主题划分为分片来存储,也就是说Kafka会将主题分片跨Broker进行存储,关于分片和Broker相关的内容在下面的小节会展开讲解。

三.Partition分片

通过将Topic进一步划分为Partition,可以得到以下的好处:

  1. 提高写入性能,分片使得数据部署在不同的Broker上,运行并行处理更多的数据请求,从而提高整体系统的吞吐量。
  2. 提高消费并发度,因为有多了个分片,那么不同消费者就可以对不同分片进行拉取消费,消费者和分区的关系在后面会单独展开
  3. 有了分片,显然后续更容易实现Kafka的水平扩展的能力
  4. 一定程度提高了容错性,分片可以提高系统的容错能力。如果一个服务器上的分片发生故障,其他服务器上的分片可以继续处理数据请求,确保系统的高可用性。

分片的逻辑结构:

在逻辑结构中,同一个 Topic 下的不同分片(Partition),存的消息是不同的。我们从上面的例子里,也可以看到Partition会使用数字来标记,假设有3个Partition,那分片编号就是从0开始,到2结束,消息就是追加到每个Partition的尾部。

注意,**同一个Partition里的数据,消息是有序的,**也就是说Kafka的Partition内部遵循严格的FIFO,不支持内建的优先级队列功能。但是同一个主题里的消息,如果分布到不同的Partition,不同的Partition消息之间是无序的,就是说比如发信息的多条消息,如果放在一个分片中,可以保证它执行的顺序,如果放在不同的分片中,不能保证这些消息的执行顺序。

数据流入哪个分片

一个主题的数据分散成了多个分片,我们就需要有一种方式决定消息是写入哪个分片的,规则如下:

  1. 如果指定了Partition,那么就是发送到特定的Partition,但是一般情况下,业务其实不需要感知Partition,除非有特殊理由,否则不建议直接指定要发送到哪个Partition;
  2. 如果没有指定Partition,但是指定了一个Key,那么就是根据Key的Hash对Partition数目取模来决定是哪个Partition也就是说只要发送时指定了相同的Key,那么相关消息一定会发送到相同的Partition,比如图中我们假设Key是bbb时,Hash取模算出来是1,那么就写入对应的Partition1,如果每条数据都指定Key是bbb,那么这些数据都会流入Partition1;
  3. 如果没有指定Partition,也没有指定Key,那么就采取轮询调度算法,也就是每一次把来自用户的请求轮流分配给Partition,从第一个开始,直到最后一个,然后又从第一个开始,用我们上图的分片情况来。说,就是第一条消息是Partition0,第二条消息是Partition1,第三条消息是Partition2,第四条又轮到Partition0,至此就是打了一圈。

可以理解指定partition或者指定key的效果是一样的,指定partition的话我需要知道partition的名称,指定key的话我随便给key设置一个字符串,我想放到一个分区的消息都用同一个字符串就行了,看起来都是一样的效果,但是指定key解耦了业务层,不需要知道partition的信息,也能实现放到一个分区

四.Broker服务器节点

前面我们学习了Topic和Partition,知道了信息是逻辑上写入主题,物理上存入Partition,那Partition又是存在哪里呢?显然,是存放在Kafka的物理节点上,Kafka称之为Broker。

Broker是什么?

Broker实际就是一个Kafka的服务器节点,服务器节点上运行了Kafka必要的应用程序。Broker提供以下功能:

  1. 接收从客户端来的连接
  2. 支持客户端查询Kafka集群的信息,比如集群内其它的broker信息
  3. 接收来自客户端的读写请求
  4. 当然,最重要的,存储消息,也就是说Kafka的消息是存储在Broker上的,也就是存储在服务器本地

Broker集群部署

如果是单机部署,那么只有一个Broker,如果是集群模式部署,那么一个Kafka集群下就存在多个Broker,集群无非就是用多个Broker共同协作对外提供整体的服务。

问: kafka的多个Broker是部署在一台服务器上,还是部署在多台服务器上呢?

答: 多台,一个Broker就是一个服务器。

到底是单机部署还是集群模式,还是看具体的业务场景,如果是小规模应用希望短平快,那么用单机是没有问题的,如果是大规模应用,通常还是会多台Broker,无论从性能还是从可靠性而言,都会更有优势,比如Uber在他们的业务中就部署了上百个Broker来处理数据。

在集群中,一个Broker是通过一个唯一的数字ID来标识自己的身份

Broker和Partition的关系

Partition是存放在Broker节点上的,如果单个Broker,很好理解,Partition都在Broker上。如果是多个Broker,则Partition会分布到不同的Broker上,最理想的状态是topic中一个partition只对应一个Broker,一个Broker可以存放多个Partition

具体而言某个Topic的Partition分配的规则如下,我们以2个Partition的TopicB举例:

  1. 先随机选取一个Broker,比如Broker11
  2. 将主题对应的第一个分片,放入Broker11,即TopicB-Partition0放到了Broker11,和上面的图一样
  3. 依次往后放TopicB后续的分片,比如TopicB-Partition1放入了Broker12,TopicB在我们的例子中只有2个Partition,假设有3个,那么下一个就放到Broker10

总结一下规则kafka是先随机挑选一个broker放置分区0,然后再按顺序放置其他分区

客户端如何连接集群

单机时候连接很好做,直接访问单机地址即可,但是集群下有那么多Broker,怎么找到需要的那台呢?这里其实一般有三种解决问题的思路:

  • 第一种是加个代理,由代理来和众Broker打交道;
  • 第二种则是重定向,Redis的集群模式就是重定向的思路,即访问其中一台,如果不是目标节点,它会告诉你正确的节点是哪台
  • 第三种是客户端先查询路由,客户端再根据路由表去访问。

Kafka就是第三种方式,具体而言:

每个Broker都会有其它Broker的信息,也就是说Broker之间是互相知晓的,这是一个大的前提,有了这个前提,客户端怎么连接Kafka集群就呼之欲出了:

  1. 访问任意一台Broker
  2. 得到所有Broker的信息列表
  3. 根据规则连接到具体的Broker,可能同学们会问这个规则是什么,规则就是上一节Partition的规则由生产者算出来该是哪个Partition,就发送给这个Partition所在的节点。

Partition分在哪个Broker上,是由Kafka服务端决定,在创建Partition的那一步就确认了。消息该存在哪个Partiton上是由生产者根据规则计算出来的。

五.生产者Producer

只要有发送信息到Kafka服务端这个动作(KafkaTemplate.send),就是Producer,一般而言发送逻辑是在一个前置一些的业务服务里。

生产消息的流程:

  1. 构建消息,即将要发送的内容,打包成一个Kafka的消息结构
  2. 序列化为二进制内容,以在网络中传输
  3. 就行分区选择,即计算要发到哪个Partition,发送消息到该Partition对应的Broker

消息结构:

要注意的是,消息是在生产者这一端产生的,也就是生产消息的第一步,这里我们重点关注消息具体的格式:

  • Key: 根据Key的Hash对Partition数目取模来决定是哪个Partition,也就是说只要发送时指定了相同的Key,那么相关消息一定会发送到相同的Partition,Key一般而言都是字符串,最终都会被序列化为二进制。
  • Value:发送的具体内容,比如发送的消息是"你好",Value就是"你好"Value最终都会被序列化为二进制
  • Compression Type: 压缩类型,其实就是压缩算法类型,这个字段决定了用哪种算法压缩Kafka消息,枚举值有none,gzip,lz4,snappy等。
  • Headers: 可以通过这个字段传递额外的Header,其实就是传递一些自定义的key-value对,比如想传递TraceID,就可以通过这个字段来进行。
  • Partition+ Offset : 这个字段生产出来时候是空的,发送到Kafka的服务端后,会写入具体的分区的偏移,主题+分区+偏移其实就唯一对应了条消息
  • Timestamp:时间戳,记录消息的时间。

可以看到,所谓消息,就是一个封装好的数据结构,Kafka每一条消息都对应了这么一个结构。

序列化:

序列化即将消息从常规类型,变成二进制类型,以在网络中传输,以下图为例:

  1. Key对象,会根据Key本身的类型,用不同的序列化工具进行序列化,比如INT就是用INT序列化器,STRING就是STRING序列化器,最终被转化为了二进制数据
  2. Value对象,也是经过序列化,最终被转化为了二进制数据

发送模式:

Java提供了三种不同的模式供使用,分别是同步发送,发送即忘,异步发送:

1.同步发送,这种方式会阻塞调用线程,直到消息发送成功或抛出异常。适用于需要确保消息发送成功后才进行后续处理的场景。这种速度会慢一些,但是可以很准确知道发送是否成功。调用 send().get()

java 复制代码
// 注意末尾的 .get(),它会让线程停下来等结果
kafkaTemplate.send("topic", msg).get();

示例:

java 复制代码
try {
    // .get() 会导致当前线程阻塞,直到 Kafka 返回发送结果或超时抛出异常
    kafkaTemplate.send("topicName", "message").get();
} catch (InterruptedException | ExecutionException e) {
    // 捕获中断异常或执行异常(如网络不可达、Broker 挂了等)
    e.printStackTrace();
}

2.发送即忘(Fire-and-forget),发送了就不管了,这种方式是最简单的发送方式。消息发送后不等待任何响应,也不处理可能的异常,即只管把消息扔给 send() 方法,完全不关心 Kafka 到底收到没有

java 复制代码
kafkaTemplate.send("topicName","message");

3.异步发送,发送了就做其它事情去了,这种方式不会阻塞调用线程,而且可以注册回调函数来处理发送结果或异常。调用 send() 的同时传入一个 Callback(回调函数)。发完后线程继续干别的,当 Kafka 有结果了,会自动触发回调。

java 复制代码
kafkaTemplate.send("topic", msg).addCallback(
    success -> log.info("发成功了,Offset是:" + success.getRecordMetadata().offset()),
    failure -> log.error("发失败了,原因:" + failure.getMessage())
);

注意: 第二和第三都是异步,只不过第三种会有回调

实践建议:

  • 同步发送 方式适用于需要确保消息成功发送的场景,尤其是对数据一致性要求较高的场景。
  • 发送即忘方式适用于不需要对发送结果进行处理的场景,尤其是对性能要求较高的场景。
  • 异步发送 方式适用于需要在高性能和可靠性之间取得平衡的场景。可以在需要时处理发送失败的情况。

通过选择适合的发送方式,可以在保证系统性能的同时,满足不同业务场景下对消息传递可靠性的要求

那么消息该发给哪个Broker呢?

实际上,我们是先找到一个消息发送给哪个分区,根据分区我们能找到分区所在的Broker,最终是和这个Broker进行交互。

六.消费者

从上图,我们可以看到如下几个关键信息:

  1. 不同消费者可以在同一时间对同一主题进行消费
  2. 相同消费者可以在同一时间从同一主题的不同分片读取信息
  3. 如果一个消费者,同时消费多个分片下,无法保证消息之间的先后顺序
  4. 如果一个消费者,只消费一个分片,消费顺序即生产顺序,符合队列的先入先出的特性

默认情况下,Kafka的消费者只会消费连接之后新产生的消息,如果想消费历史消息,是需要传递参数指定的。

解释:

  1. 核心行为: Kafka 消费者的起始位置由 auto.offset.reset 参数决定。默认值通常为 latest,意味着当一个全新的消费者组启动时,它将从分区末尾开始消费,仅处理连接建立后流入的新消息。

  2. 历史消息消费: 若需回溯并消费主题中尚未过期的历史数据,必须将 auto.offset.reset 显式设置为 earliest。此时,若该消费者组无有效的位移记录,Kafka 将重置其偏移量至分区的起始位置。

  3. 关键约束: 上述重置逻辑仅在 "位移丢失" 或 "首次加入组"时生效。一旦消费者组在 Broker 中成功提交过位移,后续重启将始终优先遵循 "断点续传" 原则,从上次提交的 Offset 位置继续向下消费,不再受该参数影响。

消息消费是推还是拉

选择拉模式的主要原因还是为了让消费者可以按自身情况来控制消费速度根据系统资源利用情况(如CPU、内存等)、业务需要等因素合理拉取消息,避免因消息处理速度不合理带来的资源浪费或过载。

我们可以通过调节max.poll.records来调节消费者拉取的频率,这个参数是设置限制每次调用pol返回的消息数,如果你的消息处理逻辑较轻量且快速,可以增大这个值,以提高吞吐量。如果你的消息处理逻辑较复杂且耗时较长,可以减小该值,以减少每次拉取的消息数量,防止处理超时。

可能这里就会问了,max.poll.records是每次拉取的个数,但是多久拉取一次呢?实际上,Kafka消费者是循环拉取模式,也就是说当你这批拉取的消息都处理完了,才会去拉取下一批

下述为例子:

application.yml 中全局配置

这种方式会对项目里所有的 @KafkaListener 生效(除非你在代码里单独覆盖它)。

XML 复制代码
spring:
  kafka:
    consumer:
      # 单次拉取消息的最大条数,默认是 500
      max-poll-records: 50
      # 配合拉取频率,如果想慢一点,可以增加两次 poll 之间的时间间隔限制
      # max-poll-interval-ms: 300000

方式二:在 @KafkaListener 注解中局部配置

如果你只想针对某个特定的业务进行限制,可以使用 properties 属性。

java 复制代码
@KafkaListener(
    topics = "tp-mq-decoupling", 
    groupId = "TEST_GROUP",
    // 这里的配置会覆盖 yml 中的全局配置
    properties = {"max.poll.records=10"} 
)
public void topic_test(List<ConsumerRecord<?, ?>> records, Acknowledgment ack) {
    // 注意:如果配置了 max.poll.records > 1,建议参数用 List 接收批量处理
    log.info("本次拉取到了 {} 条消息", records.size());
    
    for (ConsumerRecord<?, ?> record : records) {
        // 执行你的业务逻辑...
    }
    
    // 处理完这一批后手动提交
    ack.acknowledge();
}

max.poll.interval.ms 不是"拉取频率",而是"业务处理限时"。

  • 它规定了消费者处理完一整批消息(max.poll.records)的总时长上限。如果你的业务逻辑里有耗时极长的循环、大文件下载或复杂的算法,一定要调大这个参数,否则会导致消费者频繁被踢出组(Rebalance),引发消息重复消费。
  • 可以理解为一个兜底参数,超过这个时间,就由其他消费者来拉取,不然这个分片就没人来消费了

offset

每条消息Kafka中都会有Partition ID以及OFFSET,通过这两个消息就可以定位到一条消息。消费者组消费消息之后会提交它在某个Partition对应的OFFSET,这样子下一次就可以从下个位置(OFFSET+1)开始消费。注意分片只会给消费组里面的一个消费者消费,不会被消费组的多个消费者同时消费

比如下图,消费了5这个位置的消息,也做了对应的提交,所以Commited offset也是5,下一次消费的就是6这个位置的消息了。

注意,提交操作是和Kafka Broker打交道,所以消费者是不会直接去访问Broker上的topic的,而是通过broker来实现。

同时,如果一个指定的offset被确认,那么它之前的信息就相当于都确认了,下次消费是从它的下一条消息开始消费。

困惑点1: kafka的消息怎么清除?

  • Kafka可以开启清除策略,类似于可以保留XX 天数据或者XX GB数据,与消费者是否消费无关。

困惑点2: 比如某主题只有一个分区,两个消费者都在消费这个分区,那broker是不是都记录两个消费者的offset,怎么知道哪个offset是哪个消费者的?

  • 0.9版本之前存在zookeeper,0.9版本之后是放内部主题consumer_offsets 中.

主动提交和被动提交

上面说到消费者可以自动周期性提交Offset ,另一种方式是手动提交,也就是由消费者业务代码自己控制提交时机,主动调用函数来提交,

这两种区别主要在于如果自动提交,那么有可能事情还没做完,就提交给Broker "兄弟,我这条消息完事儿了",后面就只能拉到更后面的消息了,如果再发生一些异常,比如消费者在做完事情之前崩溃重启,那这条消息就丢了。

手动提交则一般是为了安全考虑,当某条消息的处理流程都ok了,再向Broker主动提交,这样更为稳健。

Consumer Group(消费组)

消费组就是把一些消费者组织起来一起工作。

一个消费组由一个唯一的groupid来标识,这个groupid是在消费者这一侧指定的,拥有相同groupid的消费者就属于同一个消费组,比如下图中的test-group1,test-group2就是使用者自定义的两个groupid,各自代表了一个消费组。

具体而言消费组做这么一件事:

一个消费组中的消费者消费同一个Topic,每个消费者可以承接这个Topic一部分Partition的消息,这样消费能力就实现了水平扩展。

注意:

  1. 同一个消费组中,每个分片只会分配给一个消费者
  2. 同一个消费组中,消费者可以被指派多个分片
  3. 不同消费组可以同一时间消费同一主题
  4. 同一消费组中的消费者可以消费不同topic

可能有同学会问,为什么需要消费组这个功能,可以程序之间指定某个Consumer消费某个分片,不也可以实现并发消费吗?可以是可以,不过会存在几个问题:

  1. 当不使用消费者组时,Kafka不会自动管理分区和消费者之间的关系,需要手动指定消费者要消费的分区,这就意味着如果Partition数量变了,消费者代码就得跟着升级,没法做到自动切换。
  2. 更重要的,消费组只用关心主题维度,而不用关心分片维度,对于使用者而言,很大程度降低了理解和应用难度,比如秒杀场景,使用者只需要知道自己要处理秒杀相关的主题,他底下有几个分片,则是对使用者是透明的,这就是封装的魅力。

看到这里,我们其实已经明白了消费者组是存在自动切换的逻辑的,这个机制在Kafka中有个特有名词,叫做Rebalance,也就是再平衡,下一个节我们会展开讲解Rebanlance机制。

消费者组分区分配策略

一个消费者组里有多个消费者,分区是按什么规则来分配给这些消费者的呢?我们可以关注消费端partition.assignment.strategy这个配置,这个参数就是分区的策略,它有如下几种选择:

1. Range Assignor(范围分配策略) ------ 默认设置

  • 基于范围的分配策略,将分区按照范围分配给消费者
  • 范围分配器在每个主题的基础上工作。对于每个主题,我们按数字顺序布置可用分区,并按字典顺序布置消费者。然后,我们将分区数除以消费者总数,以确定分配给每个消费者的分区数。如果不均匀划分,那么前几个消费者将多一个分区

2. RoundRobin Assignor(轮询分配策略)

  • 把所有订阅的 Topic 的所有分区全部铺在桌上,然后按消费者顺序一个一个发牌。

3. Sticky Assignor(粘性分配策略)

  • 首先尽量保证分配均匀(类似轮询)。

  • 当有一个消费者挂了触发 Rebalance (再平衡)时,原本属于你的分区尽量不动

  • 例子:C1 挂了,C2 和 C3 只会分摊 C1 丢下的烂摊子,而 C2 原本在处理的分区尽量保持不变。

  • 优点:减少了分区在消费者之间"挪窝"带来的开销(比如重新连接、重新拉取数据)。

4. Cooperative Sticky(协作粘性分配)

  • 以前的 Rebalance 是"Stop-the-world"模式,重分配时所有人都要停下手里的活等分配完,系统会瞬间卡顿。

  • 支持 "增量式" 分配。没被影响的消费者可以继续干活,只有需要调整的分区才会短暂停止。

  • 相比于前面三者优点在于消费不会全部暂停,且消费者的分配关系变动较小,当然付出的代价就是完成再平衡的时间可能会更久一点

七.消费组再平衡机制Rebalance

消费者组一个核心作用,就是可以动态维护一个消费者组织,让它们看似如同一个整体一般去进行消费,如果在消费过程中,发生消费者的变化,比如新增消费者、减少消费者,消费者组都会通过再平衡这个功能,使其最终能继续正常运行,履行消费工作。

当Kakfa遇到下述三种情况,会触发Rebalance机制:

  1. 新消费者加入:当一个新的消费者加入消费者组时,Kafka需要重新分配分区,以包括新的消费者。
  2. 消费者离开:当一个消费者离开(无论是正常关闭还是崩溃)时,需要重新分配该消费者负责的分区给其他消费者。
  3. 主题分区变化:当主题的分区数量发生变化时(例如,增加新的分区),Kafka需要重新分配这些分区。

再平衡的步骤:

  1. 暂停消费: 在再平衡的过程中,消费者会暂停对消息的消费,以防止在重新分配期间发生数据丢失或者重复
  2. 触发再平衡: 由消费者组协调器(通常是Kafka集群中的一个Broker)触发再平衡。
  3. 分配分区: 消费者配合协调器根据当前消费者组的成员重新分配主题的分区。根据消费者配置的分配策略分配
  4. 消费者完成分工: 重新分配完成后,所有消费者会从协调器拿到新的分配情况。
  5. 恢复消费: 消费者收到新的分配后,恢复消费,开始处理被分配到的新分区。

这里有一个问题,我们可以看到第一步中消费者会短暂地暂停消息消费,这可能导致服务中断和延迟,好在Kafka提供了不止一种策略来解决这个问题。这里的策略其实就是消费者组分区分配策略,也就是我们上一节提到的。

相关推荐
dLYG DUMS3 小时前
Spring Boot集成Kafka:最佳实践与详细指南
spring boot·kafka·linq
s1mple“”3 小时前
大厂Java面试实录:从Spring Boot到AI技术的UGC内容社区场景深度解析
spring boot·redis·微服务·kafka·向量数据库·java面试·ai技术
阿里云云原生19 小时前
海尔智家 x 阿里云 Kafka 实践:轻松支撑百亿级消息,稳定性与效率双提升
kafka
s1mple“”1 天前
大厂Java面试实录:从Spring Boot到AI技术的医疗健康场景深度解析
spring boot·redis·微服务·kafka·向量数据库·java面试·ai技术
s1mple“”1 天前
大厂Java面试实录:从Spring Boot到AI技术的面试场景深度解析
spring boot·redis·微服务·kafka·java面试·rag·ai技术
s1mple“”1 天前
大厂Java面试实录:从Spring Boot到AI技术的在线教育场景深度解析
spring boot·redis·微服务·kafka·向量数据库·java面试·ai技术
枫叶v.1 天前
Kafka 怎么保证消息的顺序性
分布式·kafka
yitian_hm1 天前
深入理解 Kafka Producer 核心源码:消息发送全链路解析
分布式·kafka·linq
枫叶V2 天前
Kafka 怎么保证消息的顺序性
kafka