分布式消息队列MQ

微服务通讯之间有同步和异步两种方式

同步通讯:就像打电话,需要实时响应

异步通讯:发邮箱一样,不需要马上回复

同步通讯

就像串行一样,优点:时效性强,可以立即得到结果

问题:

耦合度高:每次加入新的需求,都要修改原来的代码

性能下降:调用者需要等待服务提供者响应,如果调用链较长则响应事件等于每次调用的事件之和

资源浪费:调用链中每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源

级联失败:如果服务提供者中一项出现问题,所有调用方都会出现问题,导致微服务故障

异步通讯

异步调用常见的实现就是事件驱动模式

 

事件发布方和订阅者之间有一个中间人Broker, 发布者发布事件到Broker,不关心谁来订阅事件。订阅者从Broker订阅事件,不关心谁发来的消息。

好处:吞吐量提升:无需等待订阅者处理完成,响应更迅速

故障隔离:服务没有之间调用,不存在级联失败的问题

调用者之间没有阻塞,不会造成无效的资源占用。耦合度低,每个服务之间都可以灵活插拨,可替换。流量削峰:不过发布事件的流量波动多大,都由Broker接收,订阅者按照自己的速度处理事件

缺点:架构复杂了,业务没有明显的流程线,不好处理。需要依赖于Broker的可靠,安全,性能

MQ技术,分布式消息队列,也就是事件驱动架构中的Broker

 消息中间件对比

 RabbitMQ

部署指南

安装镜像:

方式一:在线下载   docker pull rabbitmq:management

方式二:将rabbitmq.tar上传到虚拟机,执行命令安装: docker load -i rabbitmq.tar

运行MQ容器

docker run -id --name rabbitmq -e RABBITMQ_DEFAULT_USER=itcast -e RABBITMQ_DEFAULT_PASS=123321 -p 5672:5672 -p 15672:15672 rabbitmq:management

访问:  http://ip:15672/    ip为你的虚拟机ip地址

15672 http端口 控制台      5672 编程端口

MQ的基本结构

publisher:生产者

consumer:消费者

exchange:交换机,负责消息路由

queue:队列,缓存信息,默认在内存中

virtualHost:虚拟主机 ,隔离不同租户的exchange、queue、消息的隔离

Connection:通道       Channel:虚拟信道

RabbitMQ消息模型

基本消息队列BasicQueue

工作消息队列WorkQueue

发布订阅(publish,Subscribe),根据交换机类型不同分为三种:

Fanout Exchange广播

DirectExchange路由

TopicExchange主题

SpringAMQP

基于RabbitMQ封装的模板,利用啦SpringBoot对其实现啦自动装配

SpringAMQP提供的三个功能:自动声明队列,交换机及其绑定关系。基于注解的监听器模式,异步接收消息。封装RabbitTemplate工具,用于发送消息。

publisher:消息发送者

consumer:消息消费者

1.BasicQueue简单队列模型

在父工程导入依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

消息发送:配置MQ地址,在publisher服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码

在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue(){
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, spring amqp!";
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
 
    }
}

消息接收:配置MQ地址,在consumer服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码

在consumer服务的RabbitConfig类中添加初始化代码用于创建队列

 /**简单队列模型-队列名*/
    public static final String SIMPLE_QUEUE = "simple.queue";
    //简单队列模型下,创建一个队列
    @Bean
    public Queue simpleQueue()
    {
        return new Queue(SIMPLE_QUEUE);
    }

然后在consumer服务的cn.itcast.mq.listener包中新建一个类SpringRabbitListener

@Component
public class SpringRabbitListener {

    @RabbitListener(queues = RabbitConfig.SIMPLE_QUEUE)
    public void simpleQueue(String message){
        System.out.println("简单模型消费者-消费到消息:" +message );
    }
}

启动consumer服务,然后在publisher服务中运行测试代码,发送MQ消息

使用默认的交换机,根据与队列同名的路由key找到队列

WorkQueue

任务模型,多个消费者绑定到一个队列,共同消费队列中的消息(默认消息平均分配的),用于提升效率,有资源竞争的场景

 消息发送:循环发送,模拟大量消息堆积现象。在publisher服务中的SpringAmqpTest类中添加一个测试方法

/**
     * workQueue
     * 向队列中不停发送消息,模拟消息堆积。
     */
@Test
public void testWorkQueue(){
    for (int i = 1; i <= 110; i++) {
        rabbitTemplate.convertAndSend(RabbitConfig.WORK_QUEUE,"work.message"+i);
    }
}

消息接收,在consumer服务的RabbitConfig类中添加初始化代码用于创建队列

    /**工作队列模型-队列名称*/
    public static final String WORK_QUEUE = "work.queue";

    @Bean
    public Queue workQueue()
    {
        return new Queue(WORK_QUEUE,true);
    }

拟多个消费者绑定同一个队列,我们在consumer服务的SpringRabbitListener中添加2个新的方法

@RabbitListener(queues = RabbitConfig.WORK_QUEUE)
public void workQueue1(String message){
    System.out.println("工作队列模型消费者1-消费到消息:" +message );
    try {
        TimeUnit.MILLISECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

@RabbitListener(queues = RabbitConfig.WORK_QUEUE)
public void workQueue2(String message){
    System.out.println("工作队列模型消费者2-消费到消息:" +message );

    try {
        TimeUnit.MILLISECONDS.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

启动ConsumerApplication后,在执行publisher服务中刚刚编写的发送测试方法testWorkQueue。这个时候发现,消息是平均分配给每个消费者的

spring中有一个简单的配置,可以解决这个问题,实现能者多劳,设置prefetch来控制消费者预取的消息数量

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

发布/订阅模型

Exchange交换机只负责转发消息,不具备存储消息的能力,如果没有队列和它绑定,或者没有符合路由规则的队列,消息丢失。

交换机的作用:接收publisher发送的消息,将消息按照规则路由到与之绑定的队列,能缓存消息,路由失败,消息丢失。FanoutExchange的会将消息路由到每个绑定的队列

Spring提供了一个接口Exchange,来表示所有不同类型的交换机:

针对不同的类型使用不同的Exchange,主要是使用最底下的三个 

Fanout广播

广播模式,消息发送流程:可以有多个队列。每个队列都要绑定到Exchange(交换机)。生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。交换机把消息发送给绑定过的所有队列。订阅队列的消费者都能拿到消息

他的效率是最高的,场景:群发消息

消息接收:在consumer的RabbitConfig类中声明队列和交换机及绑定关系

/**广播模型-交换机名*/
    public static final String FANOUT_EXCHANGE = "fantout.exchange";
    /**广播模型-队列1名*/
    public static final String FANOUT_QUEUE1 = "fanout.queue1";
    /**广播模型-队列2名*/
    public static final String FANOUT_QUEUE2 = "fanout.queue2";
//声明队列1
    @Bean
    public Queue fanoutQueue1(){
        return new Queue(FANOUT_QUEUE1);
    }
//声明队列2
    @Bean
    public Queue fanoutQueue2(){
        return new Queue(FANOUT_QUEUE2);
    }
   //声明交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange(FANOUT_EXCHANGE);
    }
    //绑定队列一和交换机
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }
    //绑定队列二和交换机
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }

在consumer服务的SpringRabbitListener中添加两个方法,作为消费者

@RabbitListener(queues = RabbitConfig.FANOUT_QUEUE1)
public void fanout1(String message){
    System.out.println("广播模型消费者1-消费到消息:" +message );
}

@RabbitListener(queues = RabbitConfig.FANOUT_QUEUE2)
public void fanout2(String message){
    System.out.println("广播模型消费者2-消费到消息:" +message );
}

消息发送:在publisher服务的SpringAmqpTest类中添加测试方法

@Test
public void testFanout(){
    rabbitTemplate.convertAndSend(RabbitConfig.FANOUT_EXCHANGE,null,"fanout message");
}

Direct 路由模式

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)

  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey

  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

消息接收:在consumer的RabbitConfig类中声明队列和交换机及绑定关系

    /**路由模型-交换机名*/
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    /**路由模型-路由KEY1名称*/
    public static final String DIRECT_ROUTEKEY_RED = "direct.red";
    /**路由模型-队列1名称*/
    public static final String DIRECT_QUEUE1 = "direct.queue1";
    /**路由模型-路由KEY2名称*/
    public static final String DIRECT_ROUTEKEY_BLUE = "direct.blue";
    /**路由模型-队列2名称*/
    public static final String DIRECT_QUEUE2 = "direct.queue2";
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange(DIRECT_EXCHANGE);
    }
    @Bean  //队列一
    public Queue directQueue1(){
        return new Queue(DIRECT_QUEUE1);
    }
    /**路由模型-创建对列2*/
    @Bean
    public Queue directQueue2(){
        return new Queue(DIRECT_QUEUE2);
    }
    /**路由模型-绑定交换机和队列1*/
    @Bean
    public Binding bindDirectQueue1(DirectExchange directExchange,Queue directQueue1){
        return BindingBuilder.bind(directQueue1).to(directExchange).with(DIRECT_ROUTEKEY_BLUE);
    }
/**路由模型-绑定交换机和队列2*/
    @Bean
    public Binding bindDirectQueue2(DirectExchange directExchange,Queue directQueue2){
        return  BindingBuilder.bind(directQueue2).to(directExchange).with(DIRECT_ROUTEKEY_RED);
    }

在consumer服务的SpringRabbitListener中添加两个方法,作为消费者:

@RabbitListener(queues = RabbitConfig.DIRECT_QUEUE1)
public void direct1(String message){
    System.out.println("路由模型消费者1-消费到消息:" +message );
}

@RabbitListener(queues = RabbitConfig.DIRECT_QUEUE2)
public void direct2(String message){
    System.out.println("路由模型消费者2-消费到消息:" +message );
}

消息发送:在publisher服务的SpringAmqpTest类中添加测试方法

 @Test
public void testDirect(){
    rabbitTemplate.convertAndSend(RabbitConfig.DIRECT_EXCHANGE, RabbitConfig.DIRECT_ROUTEKEY_RED,"red color");

    rabbitTemplate.convertAndSend(RabbitConfig.DIRECT_EXCHANGE,RabbitConfig.DIRECT_ROUTEKEY_BLUE,"blue color");
}

描述下Direct交换机与Fanout交换机的差异?

  • Fanout交换机将消息路由给每一个与之绑定的队列

  • Direct交换机根据RoutingKey判断路由给哪个队列

  • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似

Topic主题模型

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

消息接收: 在consumer的RabbitConfig类中声明队列和交换机及绑定关系

/**主题模型-交换机名*/
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    /**主题模型-路由KEY1名称*/
    public static final String TOPIC_ROUTEKEY1 = "china.*";
    /**主题模型-队列1名称*/
    public static final String TOPIC_QUEUE1 = "topic.queue1";
    /**主题模型-路由KEY2名称*/
    public static final String TOPIC_ROUTEKEY2 = "#.news";
    /**主题模型-队列2名称*/
    public static final String TOPIC_QUEUE2 = "topic.queue2";

    /**主题模型-创建交换机*/
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE);
    }

    /**主题模型-创建队列1*/
    @Bean
    public Queue topicQueue1(){
        return new Queue(TOPIC_QUEUE1);
    }

    /**主题模型-创建对列2*/
    @Bean
    public Queue topicQueue2(){
        return new Queue(TOPIC_QUEUE2);
    }

    /**主题模型-绑定交换机和队列1*/
    @Bean
    public Binding bindTopicQueue1( TopicExchange topicExchange,Queue topicQueue1){
        return BindingBuilder.bind(topicQueue1).to(topicExchange).with(TOPIC_ROUTEKEY1);
    }

    /**主题模型-绑定交换机和队列2*/
    @Bean
    public Binding bindTopicQueue2( TopicExchange topicExchange, Queue topicQueue2){
        return BindingBuilder.bind(topicQueue2).to(topicExchange).with(TOPIC_ROUTEKEY2);
    }
    

在consumer服务的SpringRabbitListener中添加两个方法,作为消费者

@RabbitListener(queues = RabbitConfig.TOPIC_QUEUE1)
public void topic1(String message){
	System.out.println("主题模型消费者1-消费到消息:" +message );
}

@RabbitListener(queues = RabbitConfig.TOPIC_QUEUE2)
public void topic2(String message){
	System.out.println("主题模型消费者2-消费到消息:" +message );
}

消息发送:在publisher服务的SpringAmqpTest类中添加测试方法:

 @Test
public void testTopic(){
    rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE, "china.news","中国加油");
    rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE, "china.today.news","中国今日新闻");
    rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE, "japan.news","日本新闻");
}

消息转换器:

Spring会把发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。Spring默认的序列化方法是JDK序列化,数据体积过大。

配置Json转换器,使用JSON方式做序列化和发序列化

在publisher和consumer两个服务中都引入依赖,实际上将以下依赖添加到父工程pom.xml即可

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

配置消息转换器,在启动类中添加一个Bean即可  使用amqp协议下的

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

Kafka

kafka是一个分布式流媒体平台,类似于消息队列或企业消息传递系统。kafka官网:Apache Kafka

 

名词解释:producer:发布消息的对象称为主题生产者

topic:kafka将消息分门别类,每一类消息称之为一个主题

consumer:订阅消息并处理发布的对象称之为主题消费者

broker:已发布的消息保存在一组服务器中称之为kafka集群。集群中每个服务器都是一个代理Broker。消费者可以订阅一个或多个主题topic,并从Broker拉数据,从而消费这些已发布的消息。

topic:   主题

partition 分区,每个主题至少一个分区(集群:分区数>=broker数量)

分区的好处:高吞吐,高性能,高可用

 kafka的安装配置

Kafka对于zookeeper是强依赖,保存kafka相关的节点数据,所以安装Kafka之前必须先安装zookeeper

Docker安装zookeeper   默认2181

#下载镜像
docker pull zookeeper:3.4.14
#创建容器
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.4.14

Docker安装kafka   kafka默认9092

#下载镜像
docker pull wurstmeister/kafka:2.12-2.3.1
#创建容器,记着将ip替换
docker run -d --name kafka \
--env KAFKA_ADVERTISED_HOST_NAME=192.168.200.130 \
--env KAFKA_ZOOKEEPER_CONNECT=192.168.200.130:2181 \
--env KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.130:9092 \
--env KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
--env KAFKA_HEAP_OPTS="-Xmx256M -Xms256M" \
--net=host wurstmeister/kafka:2.12-2.3.1

先启动zookeeper

docker start zookeeper

再启动kafka

docker start kafka

kafka快速入门

  • 生产者发送消息,多个消费者只能有一个消费者接收到消息

  • 生产者发送消息,多个消费者都可以接收到消息

创建kafka-demo项目,导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- kafkfa -->
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.apache.kafka</groupId>
                <artifactId>kafka-clients</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
    </dependency>
</dependencies>

在resources下创建文件application.yml

server:
  port: 9991
spring:
  application:
    name: kafka-demo
  kafka:
    bootstrap-servers: 192.168.200.130:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: ${spring.application.name}-test
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

生产者代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * 生产者controller
 */
@RestController
public class ProducerController {

    @Autowired
    private KafkaTemplate kafkaTemplate;

    /**
     * 生产消息的方法
     */
    @GetMapping("/send/{key}/{value}")
    public String send(@PathVariable("key") String key,@PathVariable("value") String value){
        kafkaTemplate.send("testTopic",key, value);
        return "ok";
    }
}

消费者代码

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.util.Optional;

/**
 * kafka消费者监听器
 */
@Component
public class ConsumerListener {


    /**
     * 消费消息的方法
     * @param consumerRecord
     */
    @KafkaListener(topics = "testTopic")
    public void receiveMsg(ConsumerRecord<String,String> consumerRecord){
//        if(consumerRecord!=null){
//            System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
//        }
        Optional<ConsumerRecord<String, String>> optional = Optional.ofNullable(consumerRecord);
        optional.ifPresent(x->{
            System.out.println(x.key() +"==>" + x.value());
        });
    }
}

测试:

同一个消费组下消费者,只能有一个消费者收到消息(一对一)

不同消费组下消费者,每个组内起码一个消费者能收到消息(一对多,广播效果)

kafka高可用设计

Kafka 的服务器端由被称为 Broker 的服务进程构成,即一个 Kafka 集群由多个 Broker 组成。如果集群中某台机器宕机,其他机器上的Broker仍然能对外提供服务,这就是kafka提供高可用的手段之一。

备份机制Replication

Kafka 中消息的备份又叫做 副本(Replica)

Kafka 定义了两类副本:

  • 领导者副本(Leader Replica)

  • 追随者副本(Follower Replica)

同步方式

ISR(in-sync replica)需要同步复制保存的follower

如果leader失效后,需要选出新的leader,选举的原则如下:

第一:选举时优先从ISR中选定,因为这个列表中follower的数据是与leader同步的

第二:如果ISR列表中的follower都不行了,就只能从其他follower中选取

极端情况,就是所有副本都失效了,这时有两种方案

第一:等待ISR中的一个活过来,选为Leader,数据可靠,但活过来的时间不确定

第二:选择第一个活过来的Replication,不一定是ISR中的,选为leader,以最快速度恢复可用性,但数据不一定完整

kafka生产者详解

发送类型:

同步发送:使用send()方法发送,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功

RecordMetadata recordMetadata = producer.send(kvProducerRecord).get();
System.out.println(recordMetadata.offset());

异步发送:调用send()方法,并指定一个回调函数,服务器在返回响应时调用函数

//异步消息发送
producer.send(kvProducerRecord, new Callback() {
    @Override
    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
        if(e != null){
            System.out.println("记录异常信息到日志表中");
        }
        System.out.println(recordMetadata.offset());
    }
});

参数详解

  • ack

 代码的配置方式:

//ack配置  消息确认机制
prop.put(ProducerConfig.ACKS_CONFIG,"all");

 retries  重发消息次数

生产者从服务器收到的错误有可能是临时性错误,在这种情况下,retries参数的值决定了生产者可以重发消息的次数,如果达到这个次数,生产者会放弃重试返回错误,默认情况下,生产者会在每次重试之间等待100ms

代码中配置方式:

//重试次数
prop.put(ProducerConfig.RETRIES_CONFIG,10);

消息压缩:默认情况下, 消息发送时不会被压缩。

//数据压缩
prop.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"lz4");

使用压缩可以降低网络传输开销和存储开销,这往往就是kafka发送消息的瓶颈

kafka消费者详解

消费者组

消费者组(Consumer Group)一个或多个 消费者组成的群体

 一个发布在Topic上消息被分发给此消费者组中的一个消费者。如果所有消费者都在一个组中,那么就变成queue模型。所有消费者都在不同组中,就变成了发布-订阅模型

消息的有序性

即时消息中的单对单聊天和群聊,保证发送方消息发送顺序与接收方的顺序一致

充值转账两个渠道在同一个时间进行余额变更,短信通知必须要有顺序

topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。 所以,如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。

kafka顺序问题:单机有序,集群默认无序,集群指定分区有序

提交和偏移量

kafka不会像其他JMS队列那样需要得到消费者的确认,消费者可以使用kafka来追踪消息在分区的位置(偏移量offset)

消费者会往一个叫做topic_consumer_offset的特殊主题发送消息,消息里包含了每个分区的偏移量。如果消费者发生崩溃或有新的消费者加入群组,就会触发重均衡

offset segement文件(简称offset文件):追加方式,每行一条消息,offset从0自增1  

topic_consumer_offset文件:记录 消费群组消费情况 (groupName,topicName,partition,offset,total )

默认偏移量每5秒提交一次

消息删除

kafka定期检查进行删除

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值