知识来源:B站视频
零. 基础知识
类似于RabbitMQ
- 应用解耦
- 异步提速
- 削峰填谷
一. 消息中间件的流派
什么是MQ?
- 有Broker的MQ:以一台服务器作为消息中转站Broker,生产者把消息发给Broker就完成任务了,Broker负责消息的分发。
- 重Topic:整个Broker依据topic来进行消息的中转,一定需要topic;如Kafka(性能最快)、ActiveMQ、RocketMQ(性能与kafka比肩,功能更多)
- **轻Topic:**topic只是一种中转模式,可有可没有;如RabbitMQ(功能多,使用简单)
- 无Broker的MQ:在生产者和消费者之间没有使用Broker,直接使用socket进行通信。如zeroMQ
二. Kafka基础
1. Kafka安装
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中的消息分区来存储,这样主要有两个好处:
-
分区存储,可以解决统一存储文件过大的问题
-
提供了读写的吞吐量:读和写可以同时在多个分区中进行
一个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中。
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
分区分消费组的集群消费中的一些细节:
- 一个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,消费者就会重复读取这个最新的消息)。
七. 相关问题
1. 如何防止消息丢失
- 生产者:使用同步发送,把ack设为1或者all,并且设置同步的分区数>=2;
- 消费者:设置为手动提交
2. 如何防止消息的重复消费
同RabbitMQ的幂等性保证,把消息放在数据库中,消费消息前进行判断。
3. 如何做到顺序消费
- 生产者:保证消息按顺序发送,且消息不丢失——同步发送,ack设置为非0;
- 消费者:主题只能设置一个分区,消费组中只能有一个消费者(效率降低) 为什么不能指定分区发送消息,消费消息???
4. 消息积压问题
消费积压:消费者的消费速度远小于生产者生产消息的速度,导致kafka中有大量的数据没有被消费。随着没有被消费的数据积累越多,消费者寻址的性能会越来越差,最后导致整个kafka对外提供的服务性能很差,从而造成其它服务也访问变慢,造成服务雪崩。
解决方案:
- 在这个消费者中使用多线程,充分利用机器的性能进行消费消息;
- 通过业务的架构设计,提升业务层面消费的性能;
- 创建多个消费组,多个消费者,部署到其它机器上,一起消费,提高消费者的消费速度
5. 延迟队列
场景:订单超过30分钟未支付,自动取消订单。
- 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