消息队列 RabbitMQ

一、消息队列(MQ)介绍

1.1 什么是消息队列

  • 消息队列,即MQ,Message Queue。
  • 消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。

如果以后有其它系统也依赖商品服务的数据,同样监听消息即可,商品服务无需任何代码修改。

1.2 AMQP和JMS

MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。

在这里插入图片描述

在这里插入图片描述

两者间的区别和联系:

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
  • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
  • JMS规定了两种消息模型;而AMQP的消息模型更加丰富

1.3 常见MQ产品

在这里插入图片描述

  • ActiveMQ:基于JMS
  • RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
  • RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
  • Kafka:分布式消息系统,高吞吐量

二、RabbitMQ消息队列模型

RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习。那么也就剩下5种。

在这里插入图片描述

但是其实3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。

2.1 简单队列模式(Queue)

RabbitMQ是一个消息代理:它接受和转发消息。 你可以把它想象成一个邮局:当你把邮件放在邮箱里时,你可以确定邮差先生最终会把邮件发送给你的收件人。 在这个比喻中,RabbitMQ是邮政信箱,邮局和邮递员。

RabbitMQ与邮局的主要区别是它不处理纸张,而是接受,存储和转发数据消息的二进制数据块。

在这里插入图片描述
P(producer/ publisher):生产者,一个发送消息的用户应用程序。

C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序

队列(红色区域):RabbitMQ内部类似于邮箱的一个概念。虽然消息流经RabbitMQ和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。

P(producer/ publisher):生产者,一个发送消息的用户应用程序。

C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序

队列(红色区域):RabbitMQ内部类似于邮箱的一个概念。虽然消息流经RabbitMQ和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。

2.2 work消息模型

工作队列或者竞争消费者模式
在这里插入图片描述

任务是平均分配给消费者的。也就是说消费者是平均接收的。类似于轮训。

  1. 配置类
@Configuration
public class WorkQueueConfig {
    /**
     * 队列名.
     */
    private final String work = "work_queue";

    @Bean
    public Queue workQueue() {
        return new Queue(work);
    }
}
  1. 接收端
@Component
public class ReceiveListener {

//    @RabbitListener(queues = "spring.test.queue")
    @RabbitListener(queues = "work_queue")
    public void receive01(String message){
        System.out.println("--lis1" + message);
    }

    @RabbitListener(queues = "work_queue")
    public void receive02(String message){
        System.out.println("lis2" + message);
    }


}
  1. 发送端
    @RequestMapping("/send02")
    public void send02() {
        for (int i = 0; i < 50; i++) {
            rabbitTemplate.convertAndSend("work_queue", "工作消息队列" + i);
        }
    }

部分结果:
在这里插入图片描述
可以看到,任务是平均分发的。

但是这样会出现一个问题。处理的快的消费者处理完后就处于闲置状态,而处理得慢的消费者还在慢慢执行。

2.2.3 能者多劳

  • 平均分配出现的问题:

假如消费者1比消费者2的效率要低,一次任务的耗时较长,然而两人最终消费的消息数量是一样的,消费者2大量时间处于空闲状态,消费者1一直忙碌,这好像不太合理。

正确的做法应该是消费越快的人,消费的越多。

  • 怎么实现呢?

我们可以设置每次只处理一个信息,告诉RabbitMQ一次不要向一个消费端发送多于一条消息。 或者换句话说,不要向一个消费端发送新消息,直到它处理并确认了前一个消息。 相反,它会将其分派给不是处于忙碌状态的下一个消费端。

修改配置文件如下:
在这里插入图片描述
部分运行结果:
在这里插入图片描述

2.3 发布者订阅者模式(Publish/Subscribe)

在这里插入图片描述

1、一个生产者,多个消费者

2、每一个消费者都有自己的一个队列

3、生产者没有将消息直接发送到队列,而是发送到了交换机

4、每个队列都要绑定到交换机

5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的

例:
配置类

@Configuration
public class FanoutQueueConfig {
    /**
     * 声明队列名
     */
    private final String fanout1 = "fanout_queue_1";

    private final String fanout2 = "fanout_queue_2";

    /**
     * 声明交换机的名字.
     */
    private final String fanoutExchange = "fanoutExchange";


    //声明队列1
    @Bean
    public Queue fanoutQueue1() {
        return new Queue(fanout1);
    }

    //声明队列2
    @Bean
    public Queue fanoutQueue2() {
        return new Queue(fanout2);
    }

    //声明交换机
    @Bean
    public FanoutExchange exchange() {
        return new FanoutExchange(fanoutExchange);
    }

    // 将队列1绑定到交换机
    @Bean
    public Binding bindingQueue1() {
        return BindingBuilder.bind(fanoutQueue1()).to(exchange());
    }

    // 将队列2绑定到交换机
    @Bean
    public Binding bindingQueue2() {
        return BindingBuilder.bind(fanoutQueue2()).to(exchange());
    }
}

监听器(获得消息的)

@Component
public class ReceiveListener {
    @RabbitListener(queues = "fanout_queue_1")
    public void receive03(String message) {
        System.out.println("f-lis1--" + message);
    }

    @RabbitListener(queues = "fanout_queue_2")
    public void receive04(String message) {
        System.out.println("f-lis2--" + message);
    }
}

发送请求

    @RequestMapping("/send03")
    public void send03() {
        System.out.println("订阅模式消息");
        for (int i = 0; i < 5; i++) {
            rabbitTemplate.convertAndSend("fanoutExchange", "", "订阅模式消息" + i);
        }
    }

在这里插入图片描述

2.4 路由模式(Direct)

在订阅模式中,生产者发布消息,所有消费者都可以获取所有消息。

在路由模式中,我们将添加一个功能 —— 只能订阅一部分消息。 例如,我们只能将重要的错误消息引导到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。

但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

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

消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。

路由模式不指定routing key,就变成了发布者订阅模式

配置类:

@Configuration
public class DirectQueueConfig {

    /**
     * 声明队列名.
     */
    private final String direct1 = "direct_queue_1";

    private final String direct2 = "direct_queue_2";

    /**
     * 声明交换机的名字.
     */
    private final String directExchange = "directExchange";

    //队列1
    @Bean
    public Queue directQueue1() {
        return new Queue(direct1);
    }

    //队列2
    @Bean
    public Queue directQueue2() {
        return new Queue(direct2);
    }

    //交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(directExchange);
    }

    //绑定队列1到交换机
    @Bean
    public Binding bindingDirectExchange1() {
        return BindingBuilder.bind(directQueue1())
                .to(directExchange())
                .with("update");  // 指定路由
    }

    //绑定队列2到交换机
    @Bean
    public Binding bindingDirectExchange2() {
        return BindingBuilder.bind(directQueue2())
                .to(directExchange())
                .with("add");  //指定路由
    }
}

发送请求:

    @RequestMapping("/send04")
    public void send04() {
        for (int i = 0; i < 5; i++) {
            String message = "路由模式--routingKey=update消息" + i;
            System.out.println("我是生产信息的:" + message);
            rabbitTemplate.convertAndSend("directExchange", "update", message);
        }

    }

    @RequestMapping("/send05")
    public void send05() {
        for (int i = 0; i < 5; i++) {
            String message = "路由模式--routingKey=add消息" + i;
            System.out.println("我是生产信息的:" + message);
            rabbitTemplate.convertAndSend("directExchange", "add", message);
        }
    }

监听接收消息

    @RabbitListener(queues = "direct_queue_1")
    public void receive05(String message) {
        System.out.println("d-lis1--" + message);
    }

    @RabbitListener(queues = "direct_queue_2")
    public void receive06(String message) {
        System.out.println("d-lis2--" + message);
    }

2.5 主题模式(Topic)

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
在这里插入图片描述
Routingkey 由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:
#:匹配一个或多个词

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值