KafKa详细入门实战

一、为什么用消息队列?

同步通信:
在这里插入图片描述
同步通信存在性能和稳定性问题!
异步通信:
在这里插入图片描述

明显提升系统的吞吐量;即使有服务失败,也可以通过分布式事务来保证最终是成功的。

1.1 MQ的最终目的

MQ的真正目的就是为了通讯,屏蔽底层复杂的通讯协议,定义一套应用层的、更加简单的通讯协议。MQ通过将消息的发送和接收分离来实现应用程序的异步和解耦,这只是MQ的效果。
一个分布式系统中两个模块之间通讯要么是HTTP,要么是自己开发的(rpc) TCP, 但是这两种协议其实都是原始的协议。HTTP 协议很难实现两端通讯–模块 A可以调用B, B也可以主动调用A,如果要做到这个两端都要背上WebServer,而且还不支持长连接(HTTP 2.0的库根本找不到)。TCP 就更加原始了,粘包、心跳、私有的协议,想-想头皮就发麻。MQ所要做的就是在这些协议之上构建一个简单的“协议"–生产者/消费者模型。MQ带给我们的“协议”不是具体的通讯协议,而是更高层次通讯模型。它定义了两个对象–发送数据的叫生产者; 接收数据的叫消费者,提供一 个SDK让我们可以定义自己的生产者和消费者实现消息通讯而无视底层通讯协议。

二、消息队列的应用场景

2.1 缓冲/消峰

有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
在这里插入图片描述

2.2 解耦

允许独立的扩展或修改两边的处理过程,只要确保遵守同样的接口约束。
在这里插入图片描述

2.3 异步通信

允许用户把一个消息放入队列,但不立即处理它,然后在需要的时候再去处理他们。
在这里插入图片描述

三、消息队列的模式

3.1 点对点模式

消费者主动拉取数据,消息收到后清除该消息:

在这里插入图片描述

3.2 发布/订阅模式(√)

1、可以有多个主题(浏览、点赞、收藏、评论);
2、消费者消费数据之后,不删除数据;
3、每个消费者相互独立,都可以消费到数据。
在这里插入图片描述

四、KafKa基础架构

1、生产者、消费者、主题
在这里插入图片描述

2、为方便扩展,并发提高吞吐量,一个topic拆分成多个partion(分区),将100T的数据分别存储在不同的分区中;
在这里插入图片描述

配合分区设计,提出消费者组的概念,组内每个消费者并行消费(同一个分区只允许被一个消费者消费)
在这里插入图片描述

为防止分区挂掉造成数据丢失,为每个分区增加若干副本,副本有leader和follower之分,生产消费只针对leader
在这里插入图片描述

五、安装KafKa

前提是已安装jdk1.8+、zookeeper。直接将对应的kafka安装包上传到Linux服务器解压即可。

5.1 启动zookeeper

在zookeeper的安装根目录的bin目录下,启动zookeeper:
./zkServer.sh start …/conf/zoo.cfg
其中conf目录下的zoo.cfg文件是有自带的zoo_example.cfg改完一个设置直接重命名的,必须为zoo.cfg文件,因为zookeeper启动时会自动去找zoo.cfg文件并加载相关设置。
在这里插入图片描述

5.2 启动kafka

现进入到kafka安装目录下的config目录,修改server.properties文件部分配置:
在这里插入图片描述

在kafka的安装根目录的bin目录下执行命令:
./kafka-server-start.sh -daemon …/config/server.properties
后台静默启动
再输入命令:ps -aux | grep server.properties查看是否启动成功:
在这里插入图片描述

启动成功!

5.3 创建主题

进入到kafka的安装bin目录下,执行命令:
./kafka-topics.sh --create --zookeeper 192.168.1.106:2181 --replication-factor 1 --partitions 1 --topic test
注:因为kafka依赖于zookeeper(kafka3.x版本之后可不需要zookeeper,但目前使用版本为kafka2.4.1),192.168.1.106也就是zookeeper运行服务器地址,即本机的IP地址。
也就是使用kafka-topics.sh 命令在本地的zookeeper服务器上创建一个副本、一个分区、一个主题,主题叫test
在这里插入图片描述

再来查看下这个主题的信息:
./kafka-topics.sh --list --zookeeper 192.168.1.106:2181
在这里插入图片描述

5.4 发送消息

Kafka自带一个命令行客户端,它从文件或标准输入中获取输入,并将其作为message(消息)发送到Kafka集群。默认情况下,每行将作为单独的message发送。
运行 producer,
./kafka-console-producer.sh --broker-list 192.168.1.106:9092 --topic test
之后会进入到自带的命令行客户端
在这里插入图片描述

输入消息即可,默认一条为一条消息:
在这里插入图片描述

5.4.1 同步发送

在这里插入图片描述

生产者向kafka发送一条消息,如果生产者没有收到来自kafka的ack应答,那么生产者会阻塞,超过一定时间如果还没有收到消息,会进行重试。

在kafka集群的情况下:
在这里插入图片描述

在同步发送的前提下,生产者在获得集群返回的ack之前会一直阻塞。那么集群什么时候返回ack呢?
ack应答信号有三种设置:
1、ack=0:kafka集群不需要任何broker收到消息就立即返回ack给生产者。这种方式是最容易丢消息的,但是效率是最高的;
2、ack=1(默认):必须确保多个副本之间的leader已经收到消息,并把消息写入到本地的log中才会返回ack给生产者。这种方式是性能和安全性最均衡的。
3、ack=-1/all:还有一个配置min.insync.replicas=2(默认为1,推荐配置大于等于2),此时就需要leader和一个follower同步完之后才会返回ack给生产者(也就是此时集群中有两个broker已完成数据的接收),这种方式是最安全、但性能最差的(相当于不仅要leader接收到数据还要完成一份备份)。min.insync.replicas=n(1
个leader+(n-1)个follower完成数据的接收)。

5.4.2 异步发送

在这里插入图片描述

异步发送,生产者执行完发送动作后就可以接着执行后面的任务,broker在收到消息后异步调用生产者提供的callback回调方法返回是否发送成功的结果。
异步发送会面临数据丢失的风险,所以一般采用同步发送方式。

5.4.3 消息发送缓冲区

在这里插入图片描述

kafka会默认创建一个消息缓冲区用来存放要发送的消息,缓冲区是32M;
kafka本地线程会去缓冲区中一次拉16kb的数据发送到broker,如果没拉到16kb的数据,间隔10ms后也会将已拉到的数据发到broker。

5.5 消费消息

Kafka 还有一个命令行consumer(消费者),将消息转储到标准输出。
都是在bin目录里面,

方式1:从最后一条消息的偏移量+1开始消费
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092 --topic test
输入命令执行后,此时会进入一个等待状态,当生产者再次发送一条消息,这边消费者就能消费接收到对应的消息。
在这里插入图片描述

方式2:从头开始消费
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092 --topic test --from-beginning
在这里插入图片描述

之前发送过的消息全部被依次消费了。
这种采用发布订阅的方式在消费者消费完之后并不会将消息丢弃,而是保存在对应的日志文件中。
在这里插入图片描述

5.5.1 单播消息

如果多个消费者在同一个消费组,那么只有一个消费者可以收到订阅的topic中的消息。换言之,同一个消费组中只能有一个消费者收到一个topic中的消息。
两个消费者都在一个消费组里面,都订阅同一个主题:
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092 --consumer-property group.id=testGroup --topic test

5.5.2 多播消息

不同的消费组订阅同一个topic, 那么不同的消费组中只有一个消费者能收到消息。实际上也是多个消费组中的多个消费者收到了同一个消息。
简言之,就是两个消费者在不同的消费组里面,这两个消费组都订阅了同一个主题,但是这两个消费者都能收到消息。而同一个消费组里面的各个消费者只有一个消费者能收到消息。
消费者1:
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092 --consumer-property group.id=testGroup --topic test
消费者2:
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092 --consumer-property group.id=testGroup1 --topic test

5.5.3 自动提交offset

在这里插入图片描述

消费者从broker中poll到消息后,会自动向broker的_consumer_offsets主题提交当前主题-分区消费的偏移量offset。
自动提交会丢失消息:因为消费者poll到消息后就会自动提交offset,但是此时消息还没来得及被消费,如果这时候消费者挂了,那么下一个消费者会从已提交的offset的下一个位置开始消费消息,之前未被消费的消息就丢失了。

5.5.4 手动提交offset

手动提交是消费者从broker中poll到消息后,在消费者消费完后再向_consumer_offsets主题提交offset,提交内容都是所属消费组+消费的某个主题+消费的某个分区+消费的偏移量。

5.5.4.1 手动同步提交(√)

在消费完消息后调用同步提交方法,当集群返回ack前一直阻塞,返回ack后表示提交成功再执行后面的逻辑
在这里插入图片描述

5.5.4.2 手动异步提交

在消息消费完后调用异步提交方法,不需要等待集群ack相应,可以直接处理后面的逻辑,可以设置一个回调方法,供集群调用。
在这里插入图片描述

六、查看消费组的详细信息

通过以下命令可以查看到消费组的详细信息:
./kafka-consumer-groups.sh --bootstrap-server 192.168.1.106:9092 --describe --group testGroup
在这里插入图片描述

七、主题和分区

7.1 主题

主题topic在kafka中是一个逻辑的概念,kafka通过topic将消息进行分类。

7.2 分区

但是,如果一个topic中的消息非常多,多到需要几个T来存,因为这些消息都会被保存到log日志文件中的,为了解决这个文件过大的问题,kafka提出了partition分区的概念。
在这里插入图片描述

生产者可以向不同的分区进行写操作,消费者可以消费不同分区的消息,提高了读/写的吞吐量。
好处:
1:分区存储,解决统一存储文件过大的问题;
2:提供了读写的吞吐量:读或写可以同时在多个分区中进行。
为一个主题创建多个分区:
创建主题test2并同时创建两个分区:
./kafka-topics.sh --create --zookeeper 192.168.1.106:2181 --replication-factor 1 --partitions 2 --topic test2
partitions参数指定分区创建数量;
查看主题test2的分区信息:
./kafka-topics.sh --describe --zookeeper 192.168.1.106:2181 --topic test2
在这里插入图片描述

7.2.1 消息日志文件

000000.log:这个文件保存的就是消息
_consumer_offset-49:
kafka内部自己创建了_consumer_offset主题,并默认设置了50个分区,消费者定期将自己消费分区的offset(偏移量)提交给kafka的_consumer_offset主题,提交过去的内容是一对键值对:key是消费者分组id+主题topic+分区号,value就是当前的offset值,kafka会定期清理topic里的消息,最后保留最新的那条数据。而消费者是如何决定提交到_consumer_offset的哪个分区,通过计算公式:
hash(consumerGroupId) % _consumer_offset主题的分区数
文件中保存的消息,默认保存7天,7天后消息会被删除。

八、搭建kafka集群

8.1 配置文件

准备三个server.properties文件,
在kafka的config文件下,复制两份server.properties文件,
在这里插入图片描述

每个文件内容依次调整,
在这里插入图片描述

8.2 启动三台kafka服务器

./kafka-server-start.sh -daemon …/config/server.properties
./kafka-server-start.sh -daemon …/config/server1.properties
./kafka-server-start.sh -daemon …/config/server2.properties
在这里插入图片描述

再输入命令:ps -aux | grep server.properties查看是否启动成功

8.3 副本

副本是对分区的备份,在集群中不同的副本会被部署在不同的broker上,
./kafka-topics.sh --create --zookeeper 192.168.1.106:2181 --replication-factor 3 --partitions 2 --topic my-replicated-topic
创建1个主题,2个分区,3个副本。

8.3.1 查看主题topic的情况

./kafka-topics.sh --describe - -zookeeper 192.168.1.106:2181 --topic my-replicated-topic
在这里插入图片描述
在这里插入图片描述

也就是每个分区都会创建三份副本,分别存放在三台kafka服务器上,每个副本里面包含2个分区,leader指明了当前是哪个副本处于主导地位,也就是生产者和消费者都针对于它,当某个时刻这个leader副本所在的服务器挂了,那么经过主从选举,从剩下的多个follower中选举产生新的leader副本作为生产者和消费者的对象。
follow:接收leader的同步数据
Isr:可以同步和已同步的节点会被放在Isr集合中,如果某一个节点的同步效率太差,会被kafka从Isr中剔除。

8.4 kafka集群消息发送

./kafka-console-producer.sh --broker-list 192.168.1.106:9092, 192.168.1.106:9093, 192.168.1.106:9094 --topic my-replicated-topic
在这里插入图片描述

8.5 kafka集群消息的消费

从集群中消费消息:
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092, 192.168.1.106:9093, 192.168.1.106:9094 --from-beginning --topic my-replicated-topic
在这里插入图片描述
在这里插入图片描述

指定消费组来消费消息:
./kafka-console-consumer.sh --bootstrap-server 192.168.1.106:9092, 192.168.1.106:9093, 192.168.1.106:9094 --from-beginning --consumer -property group.id=testGroup1 --topic my-replicated-topic
总结:
1:一个partiton分区只能被一个消费组里面的一个消费者消费,目的是为了保证消费的顺序性,但是只能保证同一个分区的局部有序,一个主题下的多个partition分区的多个消费者消费的总顺序性是不保证的。(一个主题下的消息被拆分成了多个分区,而每个分区的消费都是对应不同的消费者,每个消费者之间是异步的,所以总的顺序是不被保证的——但是是有办法做到消费的总顺序性的);
2:partition分区的数量决定了消费组中消费者的数量,建议同一个消费组中消费者的数量不要超过partition分区的数量,否则多出来的消费者消费不到消息(因为一个partition只能被消费组里面的一个消费者消费);
3:如果某一个消费者挂了,那么会触发rebalance机制,会让其他消费者来消费这个partition分区。
8.5.1 长轮询poll消息
默认情况下,消费者一次会poll 500条消息,

//一次poll最大拉取消息的条数,可以根据消费速度的快慢来设置
props.put ( ConsumerConfig.MAX_POLL_RECORDS_ CONFIG,500) ;
代码中设置了长轮询的时间是1000毫秒,
while (true) {
    
    
    /*
    * poll() API是拉取消息的长轮询
    */
    ConsumerRecords<String, String> records = consumer.poll( Duration.ofMillis(1000));
    for (ConsumerRecord<String, String> record : records) {
    
    
       System.out.printf ("收到消息: partition = %d,offset = %d,key =%s,value = %s%n", record. partition(),
          record.offset()record.key()record.value());
}

意味着: 如果一次poll到500条,就直接执行for循环; 如果这一次没有poll到500条,且时间在1秒内,那么长轮询继续poll,
要么到500条, 要么到1s; 如果多次poll都没达到500条,且1秒时间到了,那年直接执行for循环。

注意:
消费者在poll到500条消息后,要等到消费结束后才会进行新的poll,如果两次poll的时间间隔超过30s,kafka会认为这个消费者的消费能力过弱,将其踢出消费组。将分区分配给其他消费者
——rebalance机制

8.5.2 消费者的健康状态检查

消费者每隔1s向kafka集群发送心跳,kafka集群发现如果有超过10s没有续约的消费者,将被踢出消费组,触发该消费组的rebalance机制,将该分区交给消费组里的其他消费者进行消费。

//consumer给broker发送心跳的间隔时间 1S
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG,1000);
//kafka如果超过10秒没有收到消费者的心跳,则会把消费者踢出消费组,进行rebalance, 把分区分配给其他消费者。
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_ CONFIG,10*1000);

8.5.3 指定分区消费

consumer.assign(Arrays.asList(new TopicPartition(TOPIC_ NAME,0)));
指定主题的0号分区

8.5.3.1 消息回溯消费

consumer.assign(Arrays.asList(new TopicPartition(TOPIC_ NAME,0)));
consumer.seekToBeginning(Arrays.asList(new TopicPartition(TOPIC_NAME,0)));
指定主题的0号分区从头开始消费。(每次都是从头开始消费)

8.5.3.2 指定偏移量offset消费

consumer.assign(Arrays.asList(new TopicPartition(TOPIC_ NAME,0)));
consumer.seek(new TopicPartition(TOPIC_NAME,0),10);
指定主题的0号分区从偏移量offset为10开始消费。

8.5.3.3 指定时间消费

先根据时间去所有的partition中确认该时间对应的offset,然后再去所有的partition中找到该offset之后的消息开始消费。

8.5.3.4 新消费组的消息offset规则

新消费组中的消费者在启动以后,默认会从当前分区的最后一条消息的offset+1开始消费(也就是说之前被消费过的消息不会再消费)。可以通过设置来让新的消费者第一次从头开始消费,之后再消费最新的消息(之前的offset+1)

九、springboot整合KafKa

1、首先引入依赖

<!--  kafka   -->
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

2、KafKa的相关设置


```yaml
spring:
  application:
    name: springboot-kafka

  kafka:
    # kafka服务器地址
    bootstrap-servers: 192.168.1.106:9092,192.168.1.106:9093,192.168.1.106:9094
    # -------------- producer 生产者 ----------------
    producer:
      retries: 3 # 重试次数(同步发送等待ack响应)
      acks: 1 # 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选01、all/-1)
      batch-size: 16384 # 批量大小16k
      buffer-memory: 33554432 # 生产端缓冲区大小32M
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      # value-serializer: com.itheima.demo.config.MySerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

    #---------------- consumer 消费者 --------------------
    consumer:
      group-id: javagroup # 默认的消费组ID
      enable-auto-commit: false # 是否自动提交offset
      auto-commit-interval: 100  # 提交offset延时(接收到消息后多久提交offset)

      # earliest:第一次启动时从头开始消费,以后都是从最新的消息开始消费
      # latest:从最新的消息开始消费(默认)
      # none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
      auto-offset-reset: earliest
      # 一次最多poll 500条数据
      max-poll-records: 500
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      #      value-deserializer: com.itheima.demo.config.MyDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    #监听器设置
    listener:
      #消费端监听的topic不存在时,项目启动会报错(关掉)
      missing-topics-fatal: false
      #设置消费类型 批量消费 batch,单条消费:single
      #concurrency: 3
      #手动提交偏移量(在ack.acknowledge之后立即提交)
      ack-mode: MANUAL_IMMEDIATE
      #指定容器的线程数,提高并发量
      #concurrency: 3
      #手动提交偏移量(在ack.acknowledge之后立即提交)
      ack-mode: MANUAL_IMMEDIATE

3、生产者

package com.zhmsky.kafka.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 生产者
 * @author zhmsky
 * @date 2022/8/5 15:38
 */
@RestController
@RequestMapping("/kafkaMsg")
public class KafkaController {
    
    

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    //主题
    private final static String TOPIC_NAME = "my-replicated-topic";

    @PostMapping("/sendMsg")
    public String sendMsg() {
    
    
        kafkaTemplate.send(TOPIC_NAME,0,"key","first msg!");
        return "send success";
    }
}

4、消费者

简单方式:

package com.zhmsky.kafka.consumer;

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;

/**
 * 消费者
 * @author zhmsky
 * @date 2022/8/5 15:45
 */
@Component
@Slf4j
public class MyConsumer {
    
    

    @KafkaListener(topics = "my-replicated-topic", groupId = "testGroup")
    public void listenGroup(ConsumerRecord<String, String> record, Acknowledgment ack) {
    
    
        //接收每一条记录
        String value = record.value();
        log.info("接收到消息:"+value);
        System.out.println(record);
        //手动提交offset(如果没有提交offset那么会重复消费)
        ack.acknowledge();
    }
}

详细方式:

    @KafkaListener(groupId = "testGroup",
                   topicPartitions = {
    
    
            @TopicPartition(topic = "topic1", partitions = {
    
    "0", "1"}),
            @TopicPartition(topic = "topic2", partitions = "0", partitionOffsets 
                    = @PartitionOffset(partition = "1", initialOffset = "100"))}, 
                   concurrency = "3")   //concurrency就是同组下的消费者个数,就是并发消费数,建有小于等于分区数
    public void listenGroupPro(ConsumerRecord<String, String> record, Acknowledgment ack) {
    
    
        //接收每一条记录
        String value = record.value();
        log.info("接收到消息:" + value);
        System.out.println(record);
        //手动提交offset(如果没有提交offset那么会重复消费)
        ack.acknowledge();
    }

十、KafKa集群中的controller、rebalance、HW

10.1 controller

集群中谁来充当controller?
Kafka集群中的每个broker会在zk中创建临时序号节点,序号最小的节点(最先创建的节点)将作为集群的controller,负责管理整个集群中的所有分区和副本的状态:
●当某个分区的leader副本出现故障时, 由controller负责为该分区选举新的leader副本(按照Isr集合顺序选举)。
●当检测到某个分区的ISR集合(分区挂掉或者新增)发生变化时,由controller负责通知所有broker更新其元数据信息。
●当使用kafka-topics.sh脚本为某个topic增加分区数量时,同样还是由controller负责让新分区被其他节点感知到。

10.2 rebalance机制

前提是:消费组里面的消费者没有指明分区消费。
触发条件:当消费组里消费者和分区的关系发生变化(消费者新增或挂掉),那么就会触发rebalance机制。
这个机制会重新调整消费者消费哪个分区。
在触发rebalance机制之前,消费者消费哪个分区有三种策略:
●range: 通过公式来计算某个消费者消费哪个分区
●轮询:大家轮着消费
●sticky:粘合策略,在触发了rebalance后, 在消费者消费的原分区不变的基础上进行调整。建议开启。

10.3 HW和LEO

HW俗称高水位,HighWatermark的缩写, 取一个partition对应的ISR中最小的LEO(log end-offset)作为HW, consumer最多只能消费到HW所在的位置。另外每个replication都有HW,leader和follower各自负责更新自己的HW的状态。对于leader新写入的消息,consumer不能立刻消费,leader 会等待该消息被ISR中所有的repicas同步后更新HW,此时消息才能被consumer消费。这样就保证了如果leader所在的broker失效,该消息仍然可以从新选举的leader中获取。这样的目的是为了防止消息丢失。

十一、KafKa线上问题优化

11.1 如何防止消息丢失?

生产者:使用同步发送,将ack设置为1或者-1/all
消费者:将自动提交设置为手动提交

11.2 如何防止消息被重复消费?

在这里插入图片描述

生产者使用同步发送方式(为了防止消息丢失),kafka收到了来自生产者的消息,但是由于网络原因,生产者没有收到kafka返回的ack响应,于是生产者重发消息,这就会造成下游消费者的重复消费。那么怎么解决呢?
关闭同步发送可以解决重复消费问题但是会带来消息丢失的风险,不建议。
在下游消费者端(业务层面):
方案1:在数据库中创建联合主键(自增id+orderId)——对于入库操作来说
方案2:使用分布式锁 Redission.lock(orderId)

此外,KafKa在重平衡过程中是不能消费的,并且也不能提交offset,这就会导致消息重复消费

11.3 如何做到消息顺序消费?

在KafKa中只保证局部有序:单个partition是有序的,但是多个partition之间的全局有序性是不保证的,怎么做到这一点呢?
首先保证生产者发送过来的数据是有序的,也就是broker中的数据是有序的,
生产者:保证消息不丢失——使用同步发送,ack设置为非0值(也就是必须要有broker收到消息后)
消费者:①一个主题只创建一个partition分区,这样消息都发送到了一个分区里面,保证了消费的顺序;
②生产者在发送消息的时候指定要发往那个分区
其实就是把所有的消息都发送到一个分区里面,kafka保证了各个分区自己的有序性

11.4 如何解决消息积压问题?

消费速度远远赶不上消息的生产速度,导致kafka中有大量数据没有被消费,随着消息的堆积,消费者的寻址性能会越来越差,最后导致整个kafka对外提供的服务性能很差,从而造成其他服务也变慢,造成雪崩。
解决方案:
●在这个消费者中,使用多线程,充分利用机器的性能进行消费消息。
●通过业务的架构设计,提升业务层面消费的性能。
●创建多个消费组,多个消费者,部署到其他机器上,一起消费,提高消费者的消费速度
●创建一个消费者,该消费者在kafka另建一个主题,配上多个分区,多个分区再配.上多个消费者。该消费者将polI下来的消息,不进行消费,直接转发到新建的主题上。此时,新的主题的多个分区的多个消费者就开始一起消费了。- --不常用

11.5 延时队列

使用KafKa来实现一个消息队列:
正常情况下只要生产者发出消息,消费者端都是会立即接收到的,那么要想一个办法来让消息延迟发出去:
以订单超过30分钟未付款就自动取消订单为例:
思路:
1、用户取消了订单,延迟消息准备发出,但是延迟消息并不是直接发送到目标topic,而是专门发送到一个处理延迟消息的topic,例如delay-minutes-30
2、创建消费者拉取delay-minutes-30主题的消息,将满足条件的延迟消息发送到真正的目标topic
实现细节:
在消费者中不能用sleep方法来做延时,因为max.poll.interval.ms设置了两次poll的时间间隔,如果超出这个时间,kafka会认为这个消费者已经挂掉,会进行rebalance操作,而且这个消费者再也poll不到消息。

KafkaConsumer
提供了暂停和恢复的API函数,调用消费者的暂停方法后就无法再拉取新的消息,而且长时间不消费kafka也不会认为这个消费者已经挂掉了。于是可以启动一个定时器来实现延时效果,当消费者发现延迟消息不满足(也就是还没有超过30分钟)那么就暂停消费,并把偏移量offset定位到上次消费的位置以便下个周期poll消息再来判断是否满足条件;如果满足条件则直接将延迟消息发送到真的目标topic,接收到后就可以立马进行删除订单操作。

猜你喜欢

转载自blog.csdn.net/weixin_42194695/article/details/126272104