消息中间件学习笔记 (一)

消息中间件概述

1. 含义
  • 百科对消息中间件的解释:面向消息的系统。
  • 消息中间件是在分布式系统中完成消息的发送和接收的基础软件
  • 消息中间件也可以称消息队列,是指用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
2. 常用的消息中间件
  1. RabBitMQ
  2. ActiveMQ
  3. RockertMQ
  4. Kafka
3. 消息中间件的一般组成
  • Broker: 消息服务器
  • Producer: 生产者
  • Consumer: 消费者
  • Topic: 主题
  • Queue: 队列
  • Message: 消息体
4. 同步调用
  • 优点:时效性强,等待到结果后才返回
  • 缺点:拓展性差,性能下降(同步的功能太多,速度变慢),级联失败

例:妈妈直接打电话给儿子,告诉他中午回家吃饭

5. 异步调用
  • 包含:
    1. 消息发送者:投递消息的人(即调用方)
    2. 消息代理:管理、暂存、转发消息
    3. 消息接收者:接收和处理消息的人(即服务提供方
  • 优点:接触耦合,拓展性强;无需等待,性能好;故障隔离;缓存消息,流量削峰填谷
  • 缺点:不能立即得到调用结果,时效性差;业务安全依赖于Broker的可靠性不确定下游业务执行是否成功(即上例中,不知道儿子最后知不知道中午要回家吃饭)—>需要确认机制

例:

  1. 发微信
  2. 妈妈要通知儿子中午回家吃饭,跟老师说,由老师再转告给儿子,就是异步消息。
  3. 外卖员将外卖送到取餐柜,拿外卖的人不需要跟外卖员接触(外卖员和拿外卖的人解耦)

RabbitMQ

1. 含义
  • RabbitMQ是一款实现了高级消息队列协议(Advanced Message Queuing Protocol, AMQP)的开源消息代理软件(或称中间件)。
  • 主要作用:存储分发消息、异步通信、解耦业务模块等
  • 基本介绍
    • virtual-host:虚拟主机,起到数据隔离的作用
    • publisher:消息发送者
    • consumer:消息消费者
    • queue:队列,存储消息
    • exchange:交换机,负责路由消息
2. AMQP
  • 消息队列的一个协议
  • 组成:交换器,队列,绑定
  • 生产者把消息发布到交换器上;消息最终到达队列,并被消费者接收;绑定决定了消息如何从交换器到特定的队列。

3. RabiitMQ安装配置

(网上有很多教程咯就不细说啦)

4. SpringBoot整合RabbitMQ

  1. 配置RabbitMQ就不多说了
  2. 添加依赖
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-amqp</artifactId>
     </dependency>
    
  3. 在application.yml中配置RabbitMQ
     rabbitmq:
     host: 127.0.0.1   
     port: 5672
     username: guest     
     password: guest
     virtualHost: /
    
  4. 编写RabbitMQConfig ,配置Bean
    1. CachingConnectionFactory
    2. SimpleRabbitListenerContainerFactory
    3. RabbitTemplate
     package jmu.dmt.service.config;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.springframework.amqp.core.*;
     import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
     import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
     import org.springframework.amqp.rabbit.connection.CorrelationData;
     import org.springframework.amqp.rabbit.core.RabbitTemplate;
     import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
     import org.springframework.core.env.Environment;
    
     @Configuration
     public class RabbitMQConfig {
         private static final Logger log= LoggerFactory.getLogger(RabbitMQConfig.class);
         @Autowired(required = false)
         private CachingConnectionFactory cachingConnectionFactory; //MQ连接
         @Autowired(required = false)
         private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer; //监听器配置
    
         /**
         * 单一消费者
         * @return
         */
         @Bean(name = "singleListenerContainer")
         public SimpleRabbitListenerContainerFactory listenerContainer(){ //客户端连接监听器(Connection, Channel....)
             SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
             factory.setConnectionFactory(cachingConnectionFactory);
             //factory.setMessageConverter(new Jackson2JsonMessageConverter()); //TODO: JSON
             factory.setConcurrentConsumers(1);
             factory.setMaxConcurrentConsumers(1);
             factory.setPrefetchCount(1);
             return factory;
         }
    
         /**
         * RabbitMQ发送消息的操作组件实例
         * @return
         */
         @Bean
         public RabbitTemplate rabbitTemplate(){
     //        cachingConnectionFactory.setPublisherConfirms(true);
             cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE);
             cachingConnectionFactory.setPublisherReturns(true);
             RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
             rabbitTemplate.setMandatory(true);
             rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
                 public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                     log.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
                 }
             });
             rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
                 @Override
                 public void returnedMessage(ReturnedMessage returnedMessage) {
                     log.info("消息丢失:{}", returnedMessage);
                 }
     //            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
     //                log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
     //            }
             });
             return rabbitTemplate;
         }
     }
    

5. SpringAMQP收发信息简单代码

5.1. 发送消息

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

5.2. 接收消息

  • 通过注解在方法上声明要监听的队列名称,将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。
@Component
public class SpringRabbitListener {
  // 利用RabbitListener来声明要监听的队列信息
  // 将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。
  // 可以看到方法体中接收的msg就是消息体的内容
  @RabbitListener(queues = "simple.queue")
  public void listenSimpleQueueMessage(String msg) throws InterruptedException {
      System.out.println("spring 消费者接收到消息:【" + msg + "】");
  }
}

WorkQueues模型

  • Work queues任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息
  • 使用场景:当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。
模拟
  1. 首先,我们在控制台创建一个新的队列,命名为work.queue
  2. 消息发送
    这次我们循环发送,模拟大量消息堆积现象。
    在SpringAmqpTest类中添加一个测试方法:
/**
     * workQueue
     * 向队列中不停发送消息,模拟消息堆积。
     */
@Test
public void testWorkQueue() throws InterruptedException {
    // 队列名称
    String queueName = "simple.queue";
    // 消息
    String message = "hello, message_";
    for (int i = 0; i < 50; i++) {
        // 发送消息,每20毫秒发送一次,相当于每秒发送50条消息
        rabbitTemplate.convertAndSend(queueName, message + i);
        Thread.sleep(20);
    }
}
  1. 消息接收
    要模拟多个消费者绑定同一个队列,我们在SpringRabbitListener中添加2个新的方法:
@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
    System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
    Thread.sleep(20);
}

@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
    System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
    Thread.sleep(200);
}

注意到这两消费者,都设置了Thead.sleep,模拟任务耗时:

  • 消费者1 sleep了20毫秒,相当于每秒钟处理50个消息
  • 消费者2 sleep了200毫秒,相当于每秒处理5个消息
  1. 测试
  • 结果:从测试结果可以看到消费者1和消费者2平均每人消费了25条消息:
    • 消费者1很快完成了自己的25条消息
    • 消费者2却在缓慢的处理自己的25条消息。
  • 分析:当多个消费者绑定到一个队列时,队列的消息会平均分配给每个消费者。消息平均分给消费者,并没有考虑到消费者的处理能力,不能充分利用每一个消费者的能力,最终消息处理的耗时远远超过了1秒。这就是 消费者信息推送限制 的问题。
消费者信息推送限制
  • 含义:默认情况下,RabbitMQ会将消息依次轮询投递给绑定在队列上的每一个消费者,但并没有考虑到消费者是否已经处理完消息,可能会出现消息推挤。
  • 解决方法:
    修改application.yml文件,设置preFetch值为1,确保同一时刻最多投递给消费者一条消息
    spring:
    rabbitmq:
        listener:
        simple:
            prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
    
  1. 修改过application.yml文件后,再测试
    • 可以发现,由于消费者1处理速度较快,所以处理了更多的消息;消费者2处理速度较慢,只处理了6条消息。而最终总的执行耗时也在1秒左右,大大提升。
    • 这样充分利用了每一个消费者的处理能力,可以有效避免消息积压问题。
总结

Work模型的使用:

  • 多个消费者绑定到一个队列,可以加快消息处理速度
  • 同一条消息只会被一个消费者处理
  • 通过设置prefetch来控制消费者预取的消息数量,实现能者多劳
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值