B站千锋教育Kafka视频

知识来源:B站视频

零. 基础知识

类似于RabbitMQ

  • 应用解耦
  • 异步提速
  • 削峰填谷

一. 消息中间件的流派

什么是MQ?

image-20220521124919072

  • 有Broker的MQ:以一台服务器作为消息中转站Broker,生产者把消息发给Broker就完成任务了,Broker负责消息的分发。
    • 重Topic:整个Broker依据topic来进行消息的中转,一定需要topic;如Kafka(性能最快)、ActiveMQ、RocketMQ(性能与kafka比肩,功能更多)
    • **轻Topic:**topic只是一种中转模式,可有可没有;如RabbitMQ(功能多,使用简单)
  • 无Broker的MQ:在生产者和消费者之间没有使用Broker,直接使用socket进行通信。如zeroMQ

二. Kafka基础

1. Kafka安装

image-20220521135707441

2. 基础知识

  • Broker:处理节点,一个Kafka节点就是一个Broker,多个Broker组成Kafka集群
  • Topic:Kafka根据topic对消息进行分类
  • Producer:消息生产者
  • Consumer:消息消费者
  • ConsumerGroup:后面讲
  • Partition:后面讲

3. 创建topic

// 创建一个名为”test“的topic,只有一个partition,备份因子也为1
./kafka-topics.sh --create --zookeeper 192.168.111.0:2181 --replication-factor 1 --partitions 1 --topic test

// 查看当前kafka有哪些topic
./kafka-topics.sh --list --zookeeper 192.168.111.0:2181

// 查看topic情况
./kafka-topics.sh --describe --zookeeper 192.168.111.0:2181  --topic test

4. 发送消息

// 打开producer客户端,向某个kafka的某个topic上发送消息
./kafka-console-producer.sh --broker-list 192.168.111.0:9092 --topic test

5. 消费消息

// 打开consumer客户端,接收消息
// 方式一:从最后一条消息的偏移量+1开始消费
./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092 --topic test
// 方式二:从头开始消费
./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092 --from-beginning --topic test

Note:

  • 消息会被顺序存储在broker本地日志中 -> /opt/kafka/data/kafka-logs/主题-分区/00000000.log
  • 消息的保存是有偏移量offset的,偏移量来描述有序性
  • 消息的消费也是通过offset描述想要消费的那条消息的位置

6. 消费组、单播和多播消息

在一个Kafka的topic中,启动多个消费者,一个生产者。

  • 单播消息:多个消费者在同一个消费组,只有一个消费者可以收到订阅的topic中的消息。

    ./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092 --consumer-property group.id=testGroup --topic test
    
  • 多播消息:不同的消费组订阅同一个topic,每个消费组都有且只有一个消费者可以收到订阅的topic中的消息。

    ./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092 --consumer-property group.id=testGroup1 --topic test
    ./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092 --consumer-property group.id=testGroup2 --topic test
    
// 查看当前主题下有哪些消费组
./kafka-consumer-groups.sh --bootstrap-server 192.168.111.0:9092 --list
// 查看消费组的具体信息:current-offset最后被消费的消息的偏移量、Log-end-offset最后一条消息偏移量(消息总量)、Lag堆积的消息数量
./kafka-consumer-groups.sh --bootstrap-server 192.168.111.0:9092 --describe --group testGroup

7.主题、分区

(1)主题Topic

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

但是,如果一个topic的消息非常多(比如几T),要存到log日志文件中去,文件就太大了。 => Partition分区

(2)分区Partition

通过Partition将一个Topic中的消息分区来存储,这样主要有两个好处:

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

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

    image-20220521215250414

一个partition只能被一个消费组里面的一个消费者消费,避免消息乱序。

// 创建多分区(partitions选项)
./kafka-topics.sh --create --zookeeper 192.168.111.0:2181 --replication-factor 1 --partitions 2 --topic test1

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

  • 00000000.log:这个文件中保存的就是传递的消息

  • _consumer_offset-49:

    kafka内部自己创建了**_consumer_offset主题,包含了50个分区。这个主题用来存放每个消费者消费某个主题的偏移量**(这样的话,如果这个消费者挂了,下个消费者还可以知道要从哪里开始继续消费),设置50个分区是为了提高主题的并发量。

    • 消费者的偏移量提交到哪个分区:hash(consumerGroup)%_consumer_offset主题的分区数
    • 提交到该主题的内容:key是consumerGroup+topic+分区号,value就是当前offset的值
  • 文件中保存的消息默认为7天

三. Kafka集群

1. 搭建Kafka集群(3个broker)

  • 创建3个server.properties

    # 单机搭建 就在同一个机器的kafka上创建三个server.properties 0 1 2
    broker.id=0 / 1 / 2
    listeners=PLAINTEXT://192.168.111.0:9092 / 9093 / 9094
    log.dir=/usr/local/data/kafka-logs-0 / 1 / 2
    
  • 通过命令启动三个broker

    ./kafka-server-start.sh -daemon ../config/server0.properties
    ./kafka-server-start.sh -daemon ../config/server1.properties
    ./kafka-server-start.sh -daemon ../config/server2.properties
    
  • 校验是否启动成功

    进入到zk中查看/brokers/ids中是否有三个znode(0,1,2)

2. 副本相关概念

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

// 创建1个主题,2个分区,3个副本
./kafka-topics.sh --create --zookeeper 192.168.111.0:2181 --replication-factor 3 --partitions 2 --topic my-replicated-topic
  • 副本:相当于ES的副分片(分区相当于主分片),为主题的分区创建多个备份,放在集群的多个broker中,其中一个作为leader,其它的是follower
  • **leader:kafka的读写操作都发生在leader上,**leader负责把数据同步给follower,当leader挂了,经过主从选举,从多个follower中选举产生一个leader
  • follower:接收leader同步的数据
  • isr:可以同步和以同步的节点都存到isr集合中(如果isr中节点的性能较差,会被踢出isr集合)

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

image-20220523171753328

3. 集群消息的发送、接收消息

// 向集群发送消息
./kafka-console-producer.sh --broker-list 192.168.111.0:9092,192.168.111.0:9093,192.168.111.0:9094 --topic my-replicated-topic

// 从集群消费消息
./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092,192.168.111.0:9093,192.168.111.0:9094 --from-beginning --topic my-replicated-topic 

// 指定消费组从集群消费消息
./kafka-console-consumer.sh --bootstrap-server 192.168.111.0:9092,192.168.111.0:9093,192.168.111.0:9094 --from-beginning --consumer-property group.id=testGroup1 --topic my-replicated-topic 

image-20220523174119533

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

  • 一个partition只能被一个消费组中的同一个消费者消费,保证了partition范围内消息的有序性,但是无法保证整体消息的有序性
  • partition的数量要多于同一个消费组中的消费者数量,否则多的消费者消费不到消息
  • 如果消费者挂了,就会触发rebalance机制,让其它消费者来消费该分区

四. Java客户端操作(KafkaClient)

<!--kafka依赖-->
<dependency>
	<groupId>org.apache.kafka</groupId>
	<artifactId>kafka-clients</artifactId>
</dependency>

1. 生产者

// 生产端简单实现
public class SimpleProducer {
    private final static String TOPIC_NAME = "my-replicated-topic";

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.设置参数
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "192.168.111.0:9092, 192.168.111.0:9093, 192.168.111.0:9094");
        // 把发送的key和value从字符串序列化为字节数组
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 2.创建生产消息的客户端,传入参数
        Producer<String, String> producer = new KafkaProducer<String, String>(props);

        // 3.创建消息
        // key:决定了往哪个分区发(若分区不存在,则自动创建),  value:要发送的内容
        ProducerRecord<String, String> producerRecord = new ProducerRecord<>(TOPIC_NAME, "my_key", "hellokafka");

        // 4.同步发送消息,得到消息发送的元数据并输出(应该用try-catch)
        RecordMetadata metadata = producer.send(producerRecord).get();
        System.out.println("同步方式发送消息结果:" + "topic-" + metadata.topic() +
                " |partition-" + metadata.partition() + "|offset-" + metadata.offset());
    }
}

==============================================相关配置======================================================
// 往指定分区0上发送
	ProducerRecord<String, String> producerRecord = new ProducerRecord<>(TOPIC_NAME, 0, "my_key", "hellokafka");

★ 发送消息方式
// 同步发送消息:如果生产者发送消息没有收到ack,生产者就会阻塞,等待3s,如果还没有收到ack,就会重发,重发3次还不行就认为发送失败
	代码如上
// 异步发送消息,生产者发送完消息后就可以执行之后的业务,broker在收到消息后异步调用生产者提供的callback回调方法
    producer.send(producerRecord, new Callback(){
        @Override
        public void onCompletion(RecordMetadata recordMetadata, Exception e) {
            if(e != null){
                System.out.println("消息发送失败:" + e.getStackTrace());
            }
            if(recordMetadata != null){
                System.out.println("异步方式发送消息结果:" + "topic-" + recordMetadata.topic() +
                                   " |partition-" + recordMetadata.partition() + "|offset-" + recordMetadata.offset());
            }
        }
    });

★ ack配置
// 在同步发送的前提下,生产者在获得集群返回的ack之前会阻塞。那么什么时候会返回ack呢?ack有以下三个配置
      -ack=0:kafka-cluster不需要任何broker收到消息,立即返回ack给生产者。效率最高,但是最不安全,容易丢失数据;
      -ack=1:多副本中的leader收到消息,并把消息写到本地log中,才返回ack给生产者。性能和安全性均衡;
      -ack=-1/all:再加上配置min-insync.replicas=2(默认为1,推荐>=2),此时需要leader和一个follower同步完成后,才会返回ack。最安全单性能最差。
    props.put(ProducerConfig.ACKS_CONFIG, "1");

★ 重试配置
// 发送失败后的重试配置,发送失败会重试,默认间隔100ms,默认重试3次
	props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 100);
	props.put(ProducerConfig.RETRIES_CONFIG,3);

★ 消息缓冲区配置
// kafak默认创建一个消息缓冲区,用来存放要发送的消息,默认为32m,kafka本地线程会去缓冲区中一次拉16k的数据,发送到broker,如果拉不到16k,就间隔10ms将已拉到的数据发给broker
	props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
	props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
	props.put(ProducerConfig.LINGER_MS_CONFIG, 10);

2. 消费者

// 消费端简单实现
public class SimpleConsumer {
    private final static String TOPIC_NAME = "my-replicated-topic";
    private final static String CONSUMER_GROUP_NAME = "testGroup";

    public static void main(String[] args) {
        // 1.设置参数
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "192.168.111.0:9092, 192.168.111.0:9093, 192.168.111.0:9094");
        // 消费分组名
        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());


        // 2.创建一个消费者客户端
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        // 3.消费者订阅主题列表
        consumer.subscribe(Arrays.asList(TOPIC_NAME));

        // 4.poll消息
        while (true){
            // poll()是拉取消息的长循环
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));	// 每次花1s拉取消息,拉完就往下执行
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("收到消息:partition = %d, offset = %d, key = %s, value = %s",
                        record.partition(), record.offset(), record.key(), record.value());
            }
        }
    }
}

================================================相关配置====================================================
★ 消费者提交方式   无论是手动还是自动提交,都要把消费者所属消费组+消费主题+分区+偏移量,这些消息提交到集群的_consumer_offsets主题中
// 自动提交(默认):消费者poll消息下来后就自动提交offset		会丢失消息,因为消费者在消费前提交offset,可能还没有消费消息就挂了
	props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
	props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");	// 自动提交offset的间隔时间
// 手动提交
	props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
//	  手动同步提交:在消费完消息后调用同步提交的方法,当集群返回ack前一直阻塞,返回ack后表示提交成功,执行之后的逻辑
        while (true){
            // poll()是拉取消息的长循环
            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",
                        record.partition(), record.offset(), record.key(), record.value());
            }
            // 所有消息已消费完
            if(records.count() > 0){  // 还有消息
                // 手动同步提交offset,然后阻塞到offset提交成功(常用手动同步提交,因为一般提交后也没有什么操作了)
                consumer.commitSync();
            }
        }
	
//	  手动异步提交:在消费完消息后提交,不需要等到集群ack,直接执行之后的逻辑,可以设置一个回调方法,供集群调用
        while (true){
            // poll()是拉取消息的长循环
            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",
                        record.partition(), record.offset(), record.key(), record.value());
            }
            // 所有消息已消费完
            if(records.count() > 0){  // 还有消息
                // 手动异步提交offset,不会阻塞,可以继续执行后面的逻辑
                consumer.commitAsync(new OffsetCommitCallback(){
                    @Override
                    public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception){
                        if(exception != null){
                            System.err.println("Commit failed for " + offsets);
                            System.err.println("Commit failed exception: " + exception.getStackTrace());
                        }
                    }
                })
            }
        }

★ 长轮询poll消息
// 默认情况下,消费者一次会poll500条消息
    props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500); 	// 一次poll最大拉取消息的条数,可以根据消费速度的快慢来设置
// 设置poll长轮询的时间为1000ms
	ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));	// 每次花1s来拉取消息
// 只要满足poll的条数要求或者时间要求,就可以继续往下执行for循环。 也就是说,如果在1s内poll的条数达到500条或者到了1s还没有poll到500条消息,会往下执行。 

// 如果两次poll的间隔超过30s,集群会认为该消费者的消费能力过弱,将该消费者踢出消费组,触发rebalance机制(造成性能开销),可以让一次poll的消息条数少一些。
	props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 30 * 1000);

★ 消费者的健康状态检查
// 消费者每隔1s就给kafka集群发送心跳
    props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 1000);
// 如果集群有10s没接收到心跳, 就把该消费者踢出消费组,触发该组的rebalance机制,将该分区分给该组的其它消费者
	props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 10 * 1000);

★ 指定分区、偏移量或时间进行消费
// 指定分区0进行消费
    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)));	//从头开始消费
	consumer.seek(new TopicPartition(TOPIC_NAME, 0), 10);	//指定offset从10开始消费
// 指定时间进行消费:根据时间,去所有的partition中确定该时间对应的offset,然后去所有的partition中找到该offset之后的消息开始消费
	List<PartitionInfo> topicPartitions = consumer.partitionsFor(TOPIC_NAME);
	//从1小时前开始消费
	long fetchDataTime = new Date().getTime - 1000 * 60 * 60;
	Map<TopicPartition, Long> map = new HashMap<>();
	for(PartitionInfo par = topicPartitions){
        map.put(new TopicPartition(TOPIC_NAME, par.partition()), fetchDataTime);
    }
	Map<TopicPartition, OffsetAndTimestamp> parMap = consumer.offsetsForTimes(map);
	for(Map.Entry(TopicPartition, OffsetAndTimestamp) entry : parMap.entrySet()){
        TopicPartition key = entry.getKey();
        OffsetAndTimestamp value = entry.getValue();
        if(key == null || value == null)	continue;
        Long offset = value.offset();
        System.out.println("partition-" + key.partition() + "|offset-" + offset);
        if(value != null){
            consumer.assign(Arrays.asList(key));
            consumer.seek(key, offset);
        }
    }
	
★ 新消费组的消费offset规则
// 新消费组的消费者在启动之后,默认会从当前分区的最后一条消息的offset+1开始消费(新消息)
    props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest")	// 默认从新消息开始消费
    props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")	// 第一次从头开始消费,之后开始消费新消息(最后消费的offset+1)

五. SpringBoot集成Kafka

  • 引入依赖

    <!--spring-kafka依赖-->
    <dependency>
    	<groupId>org.springframework.kafka</groupId>
    	<artifactId>spring-kafka</artifactId>
    </dependency>
    
  • 配置

    server:
      port: 8080
    
    spring:
      kafka:
        bootstrap-servers: 192.168.111.0:9092, 192.168.111.0:9093, 192.168.111.0:9094
        # 生产者(时间上生产者和消费者不在一个机器上,所以不要写在一起)
        producer:
          retries: 3
          batch-size: 16384
          buffer-memory: 33554432
          acks: 1
          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:
          # RECORD:每一条记录被消费者监听器处理后自动提交
          # BATCH:每一批poll()的数据被处理后自动提交
          # TIME:每一批poll()的数据被处理后,距离上次提交时间打印time时提交
          # COUNT:每一批poll()的数据被处理后,被处理record的数量大于count时提交
          # COUNT_TIME:TIME或COUNT有一个满足时提交
          # MANUAL:每一批poll()的数据被处理后,手动调用ack.acknowledge()后提交
          # MANUAL_IMMEDIATE:手动调用ack.acknowledge()后立即提交 (一般用这种)
          ack-mode: MANUAL_IMMEDIATE
    
        redis:
          host: 192.168.111.0
    
  • 生产者

    @RestController
    @RequestMapping("/msg")
    public class MyKafkaController {
    
        private final static String TOPIC_NAME = "my-replicated-topic";
    
        @Autowired
        private KafkaTemplate<String, String> kafkaTemplate;
    
        @RequestMapping("/send")
        public String sendMsg(){
            kafkaTemplate.send(TOPIC_NAME, 0, "my_key", "this is a message");
    
            return "send success";
        }
    }
    
  • 消费者

    @Component
    public class MyConsumer {
    
        // 简单使用
        // 监听主题my-replicated-topic,所属消费组MyGroup1
        @KafkaListener(topics = "my-replicated-topic", groupId = "MyGroup1")
        //每次拿一条记录,并对其消费
        public void listenGroup(ConsumerRecord<String, String> record, Acknowledgment ack){
            String value = record.value();
            System.out.println(value);
            System.out.println(record);
            // 手动提交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 listenGroup(ConsumerRecord<String, String> record, Acknowledgment ack){
            String value = record.value();
            System.out.println(value);
            System.out.println(record);
            // 手动提交offset
            ack.acknowledge();
        }
    }
    

六. Kafka集群的Controller、Rebalance、HW

1. Controller

每个broker启动时都会向zk创建一个临时序号节点,获得序号最小的那个broker将会成为集群中的controller,controller会负责以下三件事:

  • 当集群中有一个副本的leader挂掉后,需要在集群中选举出一个新的leader,选举的规则是从isr集合中的最左边获得;
  • 当集群中有broker新增或减少时,controller会同步信息给其它的broker;
  • 当集群中有分区新增或减少时,controller会同步信息给其它broker。

2. Rebalance

**前提:**消费组中的消费者没有指明分区来消费

**触发条件:**当消费组中的消费者和分区的关系发生变化时(比如某个消费者不消费原本分区,或者是新增/减少消费者/分区)

**分区分配策略:**在rebalance之前,分区使用以下三种策略进行分配

  • range:根据公式计算每个消费者消费哪几个分区(前面的消费者是分区总数/消费者数量+1,之后的消费者是分区总数/消费者数量)
  • 轮询:把分区轮流分给消费者
  • **sticky:**粘合策略,若要rebalance,在原先分配的基础上进行调整,不改变之前的分配情况(如果这个策略没开,就会进行全部的重新分配)。建议开启。

3. HW和LEO机制(模糊)

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

HW(HighWatermark)是已完成同步的位置。leader和follower都会保存有自己的HW。消息写入leader且每个副本都同步完这个消息后,hw才会变化,在这之前消费者是消费不到这条消息的。同步完成之后,HW才会更新,消费者才能消费这个最新消息,这是为了防止消息的重复消费(消费者消费完最新的还没有同步到其它broker的消息后,leader把消息同步给其它副本,然后leader挂了,其它副本成为新的leader,消费者就会重复读取这个最新的消息)。

image-20220524193542752

七. 相关问题

1. 如何防止消息丢失

  • 生产者:使用同步发送,把ack设为1或者all,并且设置同步的分区数>=2;
  • 消费者:设置为手动提交

2. 如何防止消息的重复消费

同RabbitMQ的幂等性保证,把消息放在数据库中,消费消息前进行判断。

3. 如何做到顺序消费

  • 生产者:保证消息按顺序发送,且消息不丢失——同步发送,ack设置为非0;
  • 消费者:主题只能设置一个分区,消费组中只能有一个消费者(效率降低) 为什么不能指定分区发送消息,消费消息???

4. 消息积压问题

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

解决方案:

  • 在这个消费者中使用多线程,充分利用机器的性能进行消费消息;
  • 通过业务的架构设计,提升业务层面消费的性能;
  • 创建多个消费组,多个消费者,部署到其它机器上,一起消费,提高消费者的消费速度

5. 延迟队列

场景:订单超过30分钟未支付,自动取消订单。

image-20220524211742874

  • kafka中创建相应的主题
  • 消费者轮询消费该主题的消息
  • 消费者消费时判断该消息的创建时间和当前时间是否超过30分钟(前提是订单未支付)
    • 如果是,去数据库中修改订单状态未已取消
    • 如果否,记录当前消息的offset,并不再继续消费之后的消息。等待1分钟后,再次向kafka中拉取该offset及之后的消息,继续进行判断,依次反复。

八. Kafka-Eagle监控平台

  • 去kafka-eagle官网下载安装包

  • 在虚拟机中安装(要有jdk)

  • 配置环境变量

    export KE_HOME=/usr/local/kafka-eagle
    export PATH=$PATH:$KE_HOME/bin
    
  • 修改kafka-eagle内部的配置文件:

     vim system-config.properties
    

    修改里面的zk地址和mysql地址

  • 进入到bin中,启动

    ./ke.sh start
    

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

5. 延迟队列

场景:订单超过30分钟未支付,自动取消订单。

[外链图片转存中…(img-iMRHLS8P-1653444407269)]

  • kafka中创建相应的主题
  • 消费者轮询消费该主题的消息
  • 消费者消费时判断该消息的创建时间和当前时间是否超过30分钟(前提是订单未支付)
    • 如果是,去数据库中修改订单状态未已取消
    • 如果否,记录当前消息的offset,并不再继续消费之后的消息。等待1分钟后,再次向kafka中拉取该offset及之后的消息,继续进行判断,依次反复。

八. Kafka-Eagle监控平台

  • 去kafka-eagle官网下载安装包

  • 在虚拟机中安装(要有jdk)

  • 配置环境变量

    export KE_HOME=/usr/local/kafka-eagle
    export PATH=$PATH:$KE_HOME/bin
    
  • 修改kafka-eagle内部的配置文件:

     vim system-config.properties
    

    修改里面的zk地址和mysql地址

  • 进入到bin中,启动

    ./ke.sh start
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值