【Kafka】

目录

1 为什么要使用消息队列

1.使⽤同步的通信⽅式来解决多个服务之间的通信

2 消息队列的流派

rabbitMQ

rocketMQ

kafka

zeroMQ

这些消息队列中间件有什么区别?

有broker

重topic:Kafka、RocketMQ、ActiveMQ

整个broker,依据topic来进行消息的中转,在重topic的消息队列里必然需要topic的存在

轻topic:RabbitMQ

topic只是一种中转模式

无broker

在生产者和消费者之间没用使用broker,例如zeroMQ,直接使用socket进行通信

3 安装Kafka服务器

kafka介绍

kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition),多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统,低延迟的实时系统,Storm/Spark流式处理引擎,web/nginx日志,访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目

Kafka基本概念

Kafka是一个分布式的,分区的消息(官方称之为commit log)服务,他提供了一个消息系统应该具备的功能,但是确有着独特的设计,可以这样来说,Kafka借鉴了JMS规范的思想,但是确并没有完全遵循JMS规范

基础的消息(Message)相关术语

名称 解释
Broker 消息中间件处理节点,一个Kafka节点就是一个broker,一个或者多个broker可以组成一个kafka集群
topic kafka根据topic对消息进行归类,发布到kafka集群的每条消息都需要指定一个topic
Producer 消息生产者,向Broker发送消息的客户端
Consumer 消息消费者,从Broker读取消息的客户端
ConsumerGroup 每个Consumer属于一个特定的Consumer Group,一条消息可以被多个不同的Consumer Group消费,但是一个Consumer Group中只能有一个Consumer能够消费该消息
Partition 一个topic可以分为多个partition,每个partition内部消息是有序的

发送消息

Kafka自带了一个producer命令客户端,可以从本地文件中读取内容,或者我们也可以在命令行中直接输入内容,并将这些内容以消息的形式发送到kafka集群中,在默认情况下,每一个行会被当做成一个独立的消息,使用kafka的发送消息的客户端,指定发送到的kafka服务器地址和topic

消费消息

对于consumer,kafka同样也携带了一个命令行客户端,会将获取到内容在命令行中进行输出,默认是消费最新的消息

方式一:从最后一条消息的偏移量+1开始消费

复制代码
kafka-console-consumer.sh --bootstrap-server master:9092 --topic topic_db

方式二:从头开始消费

复制代码
kafka-console-consumer.sh --bootstrap-server master:9092,slave1:9092 --topic topic_db --from-beginning

注意:

复制代码
消息会被存储
消息是顺序存储
消息是有偏移量的
消费时可以指明偏移量进行消费

05 消息的偏移量及顺序消费原理

关于消息的细节

生产者将消息发送给broker,broker会将消息保存在本地的日志文件中

复制代码
/opt/module/kafka/datas/topic_db-0/00000000000000000000.log

消息的保存是有序的,通过offset偏移量来描述消息的有序性

消费者消费消息时也是通过offset来描述当前要消费的那条消息的位置

06 单播和多播消息的实现

单播

复制代码
在单分区情况下,如果多个消费者在同一个消费者组,那么只有一个消费者可以收到订阅的topic中的消息,换言之,同一个消费者组中只能有一个消费者收到一个topic中的消息

有如下生产者

复制代码
kafka-console-producer.sh --bootstrap-server master:9092 --topic topic_db

如下消费者

复制代码
kafka-console-consumer.sh --bootstrap-server master:9092 --topic topic_db --consumer-property group.id=g1

生产java数据,并消费数据

此时,新增一个消费者,同样是g1

再次生产数据scala,发现只有新增的消费者在消费数据

多播

单分区情况下,不同的消费组订阅同一个topic,那么不同的消费组中只有一个消费者能收到消息,实际上也是多个消费组中的多个消费者收到了同一个消息

在一些业务场景中需要让一条消息被多个消费者消费,那么就可以使用多播模式

kafka实现多播,只需要让不同的消费者处于不同的消费组即可

复制代码
kafka-console-consumer.sh --bootstrap-server master:9092 --topic tp --consumer-property group.id=g1

kafka-console-consumer.sh --bootstrap-server master:9092 --topic tp --consumer-property group.id=g2

07 查看消费组的详细信息

查看集群中所有消费者组名称

复制代码
kafka-consumer-groups.sh --bootstrap-server master:9092 --list
复制代码
kafka-consumer-groups.sh --bootstrap-server master:9092 --describe --group g1
复制代码
CURRENT-OFFSET:最后被消费的消息的偏移量
LOG-END-OFFSET:消息总量(最后一条消息的偏移量)
LAG:积压了多少条消息

08 主题和分区的概念

主题Topic

主题topic在kafka中是一个逻辑的概念,kafka通过topic将消息进行分类,不同的topic会被订阅该topic的消费者消费

但是有一个问题,如果说这个topic中的消息非常非常多,多到需要几T来存,因为消息是会被保存到log日志文件中的,为了解决这个文件过大的问题,kafka提出了Partition分区的概念

分区Partition

可以通过如下命令查看topic的分区信息

复制代码
kafka-topics.sh --bootstrap-server master:9092 --describe --topic tp

通过partition将一个topic中的消息分区来存储,这样的好处有多个

分区存储,可以解决统一存储文件过大的问题

提高了读写的吞吐量:读和写可以同时在多个分区中进行

(生产者吞吐量:每秒能往 Kafka 发多少条消息)

(消费者吞吐量:每秒能从 Kafka 读多少条消息)

实际上是存储在data/kafka-logs/test-0test-1中的000000000.log文件中

kafka中消息日志文件中保存的内容

复制代码
00000000000000000000.log:这个文件中保存的就是消息

__consumer_offsets-49:kafka内部自己创建了__consumer_offsets主题包含了50个分区,这个主题用来存放消费者消费某个主题的偏移量

kafka内部自己创建了__consumer_offsets主题包含了50个分区,这个主题用来存放消费者消费某个主题的偏移量,因为每个消费者都会自己维护着消费的主题的偏移量,也就是说每个消费者会把消费的主题的偏移量自主上报给kafka中的默认主题:consumer_offsets,因此kafka为了提升这个主题的并发性,默认设置了50个分区

主题:__consumer_offsets

细节:定期将自己消费分区的offset提交给kafka内部topic:__consumer_offsets,提交过去的时候,key是consumerGroupId+topic+分区号value就是当前offset的值,kafka会定期清理topic里面的消息,最后就保留最新的那条数据

因为__consumer_offsets可能会接收高并发的请求,kafka默认给其分配50个分区,可以通过offsets.topic.num.partitions设置,这样可以通过加机器的方式抗大并发

通过如下公式可以选出consumer消费的offset要提交到__consumer_offsets的哪个分区

公式hash(consumerGroupId) % __consumer_offsets主题的分区数

文件中保存的消息,默认保存7天,7天后消息会被删除

副本的概念

副本是对分区的备份,在集群中,不同的副本会被部署在不同的broker上

副本是为了主题中的分区创建多个备份,多个副本在kafka集群的多个broker中,会有一个副本作为leader,其他事follower

leader:kafka的写和读的操作都发生在leader上,leader负责把数据同步给follower,当leader挂了,经过主从选举,从多个follower中选举产生一个新的leader

follower

接收leader的同步的数据

isr

可以同步和已同步的节点会被存入到isr集合中,如果isr中的节点性能较差,会被踢出isr集合

集群中有多个broker,创建主题时可以指明主题有多个分区(把消息拆分到不同的分区中存储),可以为分区创建多个副本,不同的副本存放在不同的broker里面

关于分区消费组消费者的细节

kafka集群中每个broker中有多个parititon,一个parititon只能被一个消费组里的某一个消费者消费,从而保证消费顺序,kafka只在partition的范围内保证消息消费的局部顺序性,不能在同一个topic中的多个partition中保证总的消费顺序性,一个消费者可以消费多个partition

消费组中消费者的数量不能比一个topic中的partition数量多,否则多出来的消费者消费不到消息

11 kafka集群消费问题

分区分消费组的集群消费中的细节

一个partition只能被一个消费组中的一个消费者消费,目的是为了保证消费的顺序性,但是多个partition的多个消费者消费的总的顺序性是得不到保证的

partition的数量决定了消费组中消费者的数量,建议同一个消费组中消费者的数量不要超过partition的数量,否则多的消费者消费不到消息

如果消费者挂了,那么会触发rebalance机制,会让其他消费者来消费该分区

12 Java客户端实现生产者

复制代码
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
public class KafkaProducerDemo {
    private static final String BOOTSTRAP_SERVERS = "master:9092";
    // 要发送的主题
    private static final String TOPIC_NAME = "wsp";
    public static void main(String[] args) throws Exception{
        // 1. 配置生产者属性
        Properties props = new Properties();
        // Kafka 集群地址
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
        // 序列化 key 的类(字符串序列化)
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        // 序列化 value 的类(字符串序列化)
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        // 可选配置(根据需求添加)
        // 消息确认机制(1:仅leader确认;0:不确认;all:所有ISR节点确认)
        props.put(ProducerConfig.ACKS_CONFIG, "1");
        // 重试次数
        props.put(ProducerConfig.RETRIES_CONFIG, 3);
        // 批次大小(默认16384字节)
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        //  linger.ms:等待批次满的时间(默认0,即立即发送)
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        // 缓冲区大小(默认33554432字节)
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        // 2. 创建生产者实例
        Producer<String, String> producer = new KafkaProducer<>(props);
        
        ProducerRecord<String, String> producerRecord = new ProducerRecord<>(TOPIC_NAME, "mykeyvalue8", "hellokafka88");

        RecordMetadata recordMetadata = producer.send(producerRecord).get();

        System.out.println("topic是:"+ recordMetadata.topic() + "   偏移量是:" + recordMetadata.offset() + "    分区是:" + recordMetadata.partition());
    }
}

发送消息到指定分区上

复制代码
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(TOPIC_NAME,1, "mykeyvalue5", "hellokafka55");

如果未指定分区,则会通过业务key的hash运算,算出消息往哪个分区上发送

13 生产者端的同步发送和异步发送

同步发送

生产者同步发送消息,在收到kafka的ack告知发送成功之前一直处于阻塞状态

如果生产者发送消息没有收到ack,生产者会阻塞,阻塞到3s的时间,如果还没有收到消息,会进行重试,重试的次数是3次

生产者的异步发送消息

14 生产者端ack的配置

在同步发送的前提下,生产者在获得集群返回的ack之前会一直阻塞,那么集群什么时候返回ack呢?此时ack有三个配置

复制代码
ack = 0,kafka-cluster不需要任何的broker收到消息,就立即返回ack给生产者,最容易丢消息的,效率是最高的
ack=1 多副本之间的leader已经收到消息,并把消息写入到本地的00000000000000000000.log中,才会返回ack给生产者,性能和安全是最均衡的
ack=-1/all,里面有默认配置min.insync.replicas=2(默认为1,推荐配置大于等于2),此时就需要leader和1个follower
同步完成后,才会返回ack给生产者(此时集群中有2个broker已完成数据的接收),这种方式最安全,但性能最差

发送失败会重试,默认重试间隔100ms,重试能保证消息发送的可靠性,但是也可能造成消息重复发送,比如网络抖动,所以需要在接收者那边做好消息接收的幂等性处理

其他一些细节

发送会默认重试3次,每次间隔100毫秒

发送的消息会先进入到本地缓冲区(32mb),kafka会跑一个线程,该线程去缓冲区中取16k的数据,发送到kafka,如果到10毫秒数据没有取满16k,也会发送一次

kafka默认会创建一个消息缓冲区,用来存放要发送的消息,缓冲区是32m

kafka本地线程会去缓冲区中一次拉16k的数据,发送到broker

如果线程拉不到16k的数据,间隔10ms也会将已拉到的数据发到broker

16 消费者消费消息的基本实现

java 复制代码
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;

public class MyConsumer {

    private final static String TOPIC_NAME = "wsp";
    private final static String CONSUMER_GROUP_NAME = "g1";

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "master:9092");
        // 消费分组名
        props.put(ConsumerConfig.GROUP_ID_CONFIG, CONSUMER_GROUP_NAME);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        // 消费者订阅主题列表
        consumer.subscribe(Arrays.asList(TOPIC_NAME));

        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());
            }
        }
    }
}

17 offset的自动提交和手动提交

自动提交offset

设置自动提交参数-默认

复制代码
// 是否自动提交offset,默认就是true
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");

// 自动提交offset的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"1000");

消费者poll到消息后默认情况下,会自动向broker的__consumer_offsets主题提交当前主题-分区消费的偏移量

自动提交会丢失消息:因为如果消费者还没消费完poll下来的消息就自动提交了偏移量,那么此时消费者挂了,于是下一个消费者会从已提交的offset的下一个位置开始消费消息,之前未被消费的消息就丢失掉了

提交的内容

消费者无论是自动提交还是手动提交,都需要把所属的消费组+消费的某个主题+消费的某个分区及消费的偏移量,这样的信息提交到集群的__consumer_offsets主题

什么是自动提交

自动提交

消费者poll消息下来以后就会自动提交offset

复制代码
// 是否自动提交offset,默认就是true
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");

// 自动提交offset的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"1000");

注意:自动提交会丢消息,因为消费者在消费前提交offset,有可能提交完后还没消费时消费者挂了

自动提交offset

设置自动提交参数-默认

复制代码
// 是否自动提交 offset,默认就是true
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");

// 自动提交offset的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"1000");

消费者poll到消息后默认情况下,会自动向broker的__consumer_offsets主题提交当前主题-分区消费的偏移量

自动提交会丢消息:因为如果消费者还没消费完poll下来的消息就自动提交了偏移量,那么此时消费者挂了,于是下一个消费者会从已提交的offset的下一个位置开始消费消息,之前未被消费的消息就丢失掉了

手动提交

需要把自动提交的配置改成false

复制代码
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");

手动提交又分成了两种

手动同步提交

在消费完消息后调用同步提交的方法,当集群返回ack前一直阻塞,返回ack后表示提交成功,执行之后的逻辑

一般使用同步提交,因为提交之后一般也没有什么逻辑代码了

手动异步提交

在消息消费完后提交,不需要等到集群ack,直接执行之后的逻辑,可以设置一个回调方法,供集群调用

消费者poll消息的过程

消费者建立了与broker之间的长连接,开始poll消息

默认一次poll500条消息

复制代码
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG,500);

可以根据消费速度的快慢来设置,因为如果两次poll的时间如果超出了30s的时间间隔,kafka会认为其消费能力过弱,将其踢出消费组,将分区分配给其他消费者

可以通过这个值进行设置

复制代码
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,30*1000);)

如果每隔1s没有poll到任何消息,则继续去poll消息,循环往复,直到poll到消息,如果超出了1s,则此次长轮询结束

复制代码
ConsumerRecords<String,String> records = consumer.poll(Duration.ofMillis(1000));

消费者发送心跳的时间间隔

复制代码
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG,1000);

kafka如果超过10秒没用收到消费者的心跳,则会把消费者踢出消费组,进行rebalance,把分区分配给其他消费者

复制代码
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,10*1000);

长轮询poll消息

默认情况下,消费者一次会poll500条消息

复制代码
// 一次poll最大拉取消息的条数,可以根据消费速度的快慢来设置
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG,500);

代码中设置了长轮询的时间是1000毫秒

意味着

复制代码
如果一次poll到了500条,就直接执行for循环
如果这次没有poll到500条,且时间在1秒内,那么长轮询继续poll,要么到500条,要么到1秒
如果多次poll都没达到500条,且1秒时间到了,那么直接执行for循环

如果两次poll的间隔超过30s,集群会认为该消费者的消费能力过弱,该消费者被踢出消费组,触发rebalance机制,rebalance机制会造成性能开销,可以通过设置这个参数,让一次poll的消息条数少一点

复制代码
一次poll最大拉取消息的条数,

消费者的健康状态检查

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

指定分区消费

复制代码
        consumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME,0)));

消息回溯消费

复制代码
        consumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME,0)));
        consumer.seekToBeginning(Arrays.asList(new TopicPartition(TOPIC_NAME,0)));

指定offset消费

复制代码
        consumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME,0)));
        consumer.seek(new TopicPartition(TOPIC_NAME,0),1);

从指定时间点消费

新消费组消费offset的规则

21 Springboot中使用kafka的基本实现

创建项目

复制代码
https://start.aliyun.com

导入依赖

复制代码
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

编写配置文件

复制代码
server:
  port: 8080
spring:
  kafka:
    bootstrap-servers: 192.168.206.128:9092,192.168.206.129:9092,192.168.206.130:9092
    producer: # 生产者
      retries: 3 # 设置大于0的值,则客户端会将发送失败的记录重新发送
      batch-size: 16384 # 每次发送时多少一批次 这里设置的是16kb
      buffer-memory: 33554432 # 设置内存缓存区32Mb
      acks: 1 # leader收到消息后就返回ack
      # 指定消息key和消息体的编解码方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: default-group # 组内单播,组间广播
      enable-auto-commit: false # 关闭消费自动提交
      auto-offset-reset: earliest # 新消费组启动会从头信息消费
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      max-poll-records: 500 # 每次长轮询拉取多少条消息
    listener:
      # 当每一条记录被消费者监听器(ListenerConsumer)处理之后提交
      # RECORD
      # 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后提交
      # BATCH
      # 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后,距离上次提交时间大于TIME时提交
      # TIME
      # 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后,被处理record数量大于等于COUNT时提交
      # COUNT
      # TIME | COUNT 有一个条件满足时提交
      # COUNT_TIME
      # 当每一批poll()的数据被消费者监听器(ListenerConsumer)处理之后, 手动调用Acknowledgment.acknowledge()后提交
      # MANUAL
      # 手动调用Acknowledgment.acknowledge()后立即提交,一般使用这种
      # MANUAL_IMMEDIATE
      ack-mode: MANUAL_IMMEDIATE
  redis:
    host: 172.16.253.21

编写消息生产者

复制代码
package com.example.demo;

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/msg")
public class MyKafkaController {

    private final static String TOPIC_NAME = "second";

    @Resource
    private KafkaTemplate<String,String> kafkaTemplate;

    @RequestMapping("/send")
    public String sendMessage(){

        kafkaTemplate.send(TOPIC_NAME,0,"key","this is a message!");

        return "send success!";

    }
}

编写消息消费者

复制代码
package com.example.demo;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;
@Component
public class MyConsumer {
    @KafkaListener(topics = "second",groupId = "MyGroup1")
    public void listenGroup(ConsumerRecord<String, String> record, Acknowledgment ack) {
        String value = record.value();
        System.out.println(value);
        System.out.println(record);
        ack.acknowledge();
    }
}

23 Kafka集群中controller的作用

controller

集群中谁来充当controller

每个broker启动时会向ZK创建一个临时序号节点,获得的序号最小的那个broker将会作为集群中的controller,主要负责

当集群中有一个副本的leader挂掉,需要在集群中选举出一个新的leader,选举的规则是从ISR集合中最左边获得

当集群中有broker新增或减少,controller会同步信息给其他broker

当集群中有分区新增或减少,controller会同步信息给其他broker

rebalance机制

前提是:消费组中的消费者没有指明分区来消费,当消费组中消费者和分区的关系发生变化,那么就会触发rebalance机制,这个机制会重新调整消费者消费哪个分区

在触发rebalance机制之前,消费者消费哪个分区有三种策略:

range:通过公式计算来得到每个消费者消费哪几个分区:前面的消费者是分区总数/消费者数量+1,之后的消费者是分区总数/消费者数量

轮询:大家轮着消费

sticky:粘合策略,如果需要rebalance,会在之前已经分配的基础上调整,不会改变之前的分配情况,如果该策略没开,那么就要进行全部的重新分配,建议开启

HW和LEO机制

HW俗称高水位,HighWatermark的缩写,取一个partition对应的ISR中最小的LEO(log-end-offset)作为HW

LEO是某个副本最后消息的消息位置(log-end-offset)

HW是已完成同步的位置,消息在写入broker时,且每个broker完成这条消息的同步后,HW才会变化,在这个之前消费者是消费不到这条消息的

26 Kafka优化之防止消息丢失和重复消费

如何防止消息丢失

生产者:使用同步发送,把ack设成1或者all,并且设置同步的分区数>=2

消费者:把自动提交改成手动提交

如何防止消息的重复消费

一条消息被消费者消费多次,如果为了消息的不重复消费,而把生产端的重试机制关闭,消费端的手动提交改成自动提交,这样反而会出现消息丢失,那么可以

在消费者端解决幂等性消费问题

幂等性的保证:

方案一:

在防止消息丢失的方案中,如果生产者发送完消息后,因为网络抖动,没有收到ack,但实际上broker已经收到了,此时生产者会进行重试,于是broker就会收到多条相同的消息,从而造成消费者的重复消费

怎么解决:

生产者关闭重试:会造成丢消息(不建议)

消费者解决非幂等性消费问题:

所谓的幂等性:多次访问的结果是一样的,对于rest的请求(get(幂等)、post(非幂等)、put(幂等)、delete(幂等))

解决方案:

在数据库中创建联合主键,防止相同的主键,创建出多条记录

使用分布式锁,保证只有一条记录能够创建成功

如何做到顺序消费

生产者:保证消息按顺序消费,且消息不丢失------使用同步的发送,ack设置成非0的值

消费者:主题只能设置一个分区,消费组中只能有一个消费者

kafka的顺序消费使用场景不多,因为牺牲掉了性能,但是比如rocketmq在这一块有专门的功能已设计好

kafka优化之解决消息积压问题

解决消息积压问题

消息积压会导致很多问题,比如磁盘被打满,生产端发消息导致kafka性能过慢,就容易出现服务雪崩,就需要有相应的手段

方案一:在一个消费者中启动多个线程,让多个线程同时消费 ------提升一个消费者的消费能力

方案二:如果方案一还不够的话,这个时候可以启动多个消费者,多个消费者部署在不同的服务器上,其实多个消费者部署在同一服务器上也可以提高消费能力------充分利用服务器的cpu资源

方案三:让一个消费者去把收到的消息往另外一个topic上发,另一个topic设置多个分区和多个消费者,进行具体的业务消费

消息积压问题的出现

消息的消费者的消费速度远赶不上生产者的生产消息的速度,导致kafka中有大量的数据没有被消费,随着没有被消费的数据堆积越多,消费者寻址的性能会越来越差,最后导致整个kafka对外提供的服务的性能很差,从而造成其他服务也访问速度变慢,造成服务雪崩

消息积压的解决方案

在这个消费者中,使用多线程,充分利用机器的性能进行消费消息

通过业务的架构设计,提升业务层面消费的性能

创建多个消费组,多个消费者,部署到其他机器上,一起消费,提高消费者的消费速度

创建一个消费者,该消费者在kafka另建一个主题,配上多个分区,多个分区再配上多个消费者,该消费者将poll下来的消息,不进行消费,直接转发到新建的主题上,此时,新的主题的多个分区的多个消费者就开始一起消费了(不常用)

kafka优化之实现延时队列

应用场景

订单创建后,超过30分钟没有支付,则需要取消订单,这种场景可以通过延时队列来实现

具体方案

kafka中创建相应的主题

消费者消费该主题的消息(轮询)

消费者消费消息时判断消息的创建时间和当前时间是否超过30分钟(前提是订单没支付)

如果是:去数据库中修改订单状态为已取消

如果否:记录当前消息的offset,并不再继续消费之后的消息,等待1分钟后,再次向kafka拉取该offset及之后的消息,继续进行判断,以此反复

30 监控平台kafkaeagle的使用

选择consumers和对应的group

这里可以看到消息是否积压

相关推荐
FinTech老王1 小时前
集中式 vs 分布式数据库:金融用户如何选择?——金仓数据库的双架构实践与选型指南
数据库·分布式·金融
Roye_ack3 小时前
【黑马点评 - 高级篇】Redis分布式缓存原理(Redis持久化 RDB AOF + 主从集群 哨兵 分片集群 + 多级缓存)
redis·分布式·缓存·aof·redis持久化·rdb·redis主从哨兵分片集群
b***59433 小时前
分布式WEB应用中会话管理的变迁之路
前端·分布式
Z_Easen3 小时前
RabbitMQ 技术深度解析:从核心概念到可靠性实践
分布式·rabbitmq
7***37454 小时前
HarmonyOS分布式能力的核心技术
分布式·华为·harmonyos
q***75184 小时前
RabbitMQ 客户端 连接、发送、接收处理消息
分布式·rabbitmq·ruby
昵称为空C5 小时前
kafka的替代品redpanda部署与SpringBoot集成使用案例
spring boot·后端·kafka
一只小青团6 小时前
Hadoop之javaAPI写HDFS的shell命令
大数据·hadoop·分布式
Mr_sun.9 小时前
Day07——RabbitMQ-高级
分布式·rabbitmq