目录
- [1 为什么要使用消息队列](#1 为什么要使用消息队列)
- [2 消息队列的流派](#2 消息队列的流派)
- [3 安装Kafka服务器](#3 安装Kafka服务器)
- [05 消息的偏移量及顺序消费原理](#05 消息的偏移量及顺序消费原理)
- 关于消息的细节
- [06 单播和多播消息的实现](#06 单播和多播消息的实现)
- [07 查看消费组的详细信息](#07 查看消费组的详细信息)
- [08 主题和分区的概念](#08 主题和分区的概念)
- [11 kafka集群消费问题](#11 kafka集群消费问题)
- [12 Java客户端实现生产者](#12 Java客户端实现生产者)
- [13 生产者端的同步发送和异步发送](#13 生产者端的同步发送和异步发送)
- [14 生产者端ack的配置](#14 生产者端ack的配置)
- 其他一些细节
- [16 消费者消费消息的基本实现](#16 消费者消费消息的基本实现)
- [17 offset的自动提交和手动提交](#17 offset的自动提交和手动提交)
- 消费者的健康状态检查
- 指定分区消费
- 消息回溯消费
- 指定offset消费
- 从指定时间点消费
- 新消费组消费offset的规则
- [21 Springboot中使用kafka的基本实现](#21 Springboot中使用kafka的基本实现)
- [23 Kafka集群中controller的作用](#23 Kafka集群中controller的作用)
- [26 Kafka优化之防止消息丢失和重复消费](#26 Kafka优化之防止消息丢失和重复消费)
- kafka优化之解决消息积压问题
- [30 监控平台kafkaeagle的使用](#30 监控平台kafkaeagle的使用)
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-0和test-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



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