RabbitMQ

RabbitMQ

RabbitMQ是基于Erlang语言开发的开源消息队列系统,基于AMQP协议。

AMQP协议

AMQP协议是具有现代特征的二进制协议,是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。

AMQP协议中的几个重要概念

  • Server:接收客户端的连接,实现AMQP实体服务。
  • Connection:应用程序和Server的网络连接,TCP连接。
  • Channel:信道,消息读写等操作在信道中进行,客户端可以建立多个信道,每个信道代表一个会话任务。
  • Message:消息,应用程序和服务器之间传递的数据,消息可以非常简单,也可以非常复杂。由Properties和Body组成。Properties为外包装,可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。
  • Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机里可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名称的Exchange或Queue。
  • Exchange:交换机,接收消息,按照路由规则将消息路由到一个或多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换机类型有direct、topic、fanout、headers四种。
  • Bindings:绑定,交换机和消息队列之间的虚拟连接,绑定中可以包含一个或者多个RoutingKey。
  • RoutingKey:路由键,生产者将消息发送给交换机的时候,会发送一个RountingKey,用来指定路由规则,这样交换机就知道把消息发送给哪个队列。路由键通常为一个“.” 分割的字符串,例如"com.rabbitmq’。
  • Queue:消息队列,用来保存消息,供消费者消费。

工作原理

AMQP协议模型由三部分组成:生产者、消费者和服务端,执行流程如下:

  1. 生产者连接到Server,建立一个连接,开启一个信道。
  2. 生产者声明交换机和队列,设置相关属性,并通过路由键将交换机和队列进行绑定。
  3. 消费者也需要进行建立连接,开启信道等操作,便于接收消息。
  4. 生产者发送消息,发送到服务端中的虚拟主机。
  5. 虚拟主机中的交换机根据路由键选择路由规则,发送到不同的消息队列中。
  6. 订阅了消息队列的消费者就可以获取到消息,进行消费。

在这里插入图片描述

常用交换机

RabbitMQ常用的交换机有四种,分别是direct、topic、fanout和headers。

Direct Exchange

直连交换机,该交换机绑定一个队列,要求消息与一个特定的路由键完全匹配,进行一对一、点对点的发送。

fanout Exchange

fanout交换机,一个发送到fanout交换机的消息都会被转发到与该交换机绑定的所有队列上,类似子网广播,每个子网内的主机都获得一份复制的消息,简单点说就是发布订阅。

topic Exchange

topic交换机,该种交换机使用通配符进行消息匹配,路由到对应的队列。通配符有两种:“*”、“#”。需要注意的是通配符前面必须要加上"."符号。

  • *:匹配且只匹配一个词,比如a.*可以匹配到a.ba.c,但是匹配不到a.b.c
  • #:匹配一个或多个词,比如rabbit.#既可以匹配到rabbit.a.b,也可以匹配到rabbit.a.b.c

headers Exchange

headers也就是头部,该种交换机的路由不是用RoutingKey进行路由匹配的,而是根据匹配请求头中所带的键值进行路由。创建队列需要设置绑定的头部信息,有两种模式,全部匹配和部分匹配。交换机会根据生产者发送过来的头部信息携带的键值去匹配队列绑定的键值,路由到对应的队列。

RabbitMQ代码示例

Spring AMQP

Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。

在rabbitmq的官方文档中给出了使用Spring AMQP时的六种使用方式:
分别为:

  • Hello World
  • Word Queues
  • Publish/Subscribe
  • Routing
  • Topics
  • RPC

在这里插入图片描述

Demo 项目搭建

创建consumer服务作为消费者,创建publisher服务作为生产者
在这里插入图片描述

在pom文件中引入amqp的依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置yml:

spring:
  rabbitmq:
    host: 175.24.180.***
    port: 5672
    virtual-host: /
    username: root
    password: 123456

简单的Hello World方式

直接创建一个消息队列,生产者和消费者通过队列直连
在这里插入图片描述

生产者:

package com.example.publisher;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void testSendMessage2Queue() {
        String queueName = "simple.queue";
        String msg = "hello, rabbit";
        rabbitTemplate.convertAndSend(queueName, msg);
    }
}

消费者:

package com.example.consumer.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MqListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg){
        System.out.println("消费者收到消息: " + msg);
    }
}

效果:
生产者向队列中发送消息:
在这里插入图片描述

运行消费者程序,消费者从队列中取消息:
在这里插入图片描述

Work Queues

工作队列模式,一个生产者,多个消费者,一个队列
在这里插入图片描述
主要思想是避免排队等待,避免一个消息处理时间过久而无法处理下一个的问题。
rabbitmq中的工作队列模式默认采用轮询的方式,如果有两个消费者,消息逐一分给每个消费者进行消费。

生产者:
生成五十条消息,放入消息队列

@Test
    void testWorkQueue() throws InterruptedException {
        String queueName = "work.queue";
        for (int i = 1;i <= 50;i++){
            String msg = "test work queue_" + i;
            rabbitTemplate.convertAndSend(queueName ,msg);
            Thread.sleep(20);
        }
    }

消费者:
创建两个消费者监听同一个队列

@RabbitListener(queues = "work.queue")
    public void listenWorkQueue1(String msg){
        System.out.println("消费者1收到消息: " + msg);
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue2(String msg){
        System.out.println("消费者2收到消息: " + msg);
    }

结果:
在这里插入图片描述
从这里也可以看出,每条消息只会被消费一次。

当两个消费者消费能力不同时(让消费者1休眠20毫秒,消费者2休眠200毫秒):
在这里插入图片描述
可以看出,这里没有考虑消费者的处理能力,导致处理慢的消费者最后堆积了很多消息,增长了处理时间。

解决方法:设置prefetch=1,即每次消费者预取一条消息。
在这里插入图片描述

效果:
在这里插入图片描述

拓展:如何解决消息堆积问题?

  • 绑定多个消费者
  • 优化业务代码,让消费者处理速度变快

Publish/Subscribe

发布订阅模式,类似于广播模式,通过Fanout交换机实现。生产者将消息发送给fanout交换机,fanout交换机将收到的消息发送给所有绑定他的队列。
在这里插入图片描述
在这里插入图片描述

这里通过控制台创建两个队列
在这里插入图片描述
创建一个交换机:
在这里插入图片描述
并进行绑定
在这里插入图片描述
生产者:

@Test
    void testSendFanout() {
        String exchangeName = "root.fanout";
        String msg = "hello everyone";
        rabbitTemplate.convertAndSend(exchangeName, "", msg);
    }

消费者:

@RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) {
        System.out.println("消费者1收到来自fanout queue1 的消息 " + msg);
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) {
        System.out.println("消费者2收到来自fanout queue2 的消息 " + msg);
    }

效果:
在这里插入图片描述

Routing

路由模式,通过指定路由规则,结合Direct 交换机,我们可以将交换机接收到的消息发送给指定的Queue,称为定向路由,比Fanout要更加灵活。
每个queue都与Exchange设置一个BindingKey,发布者发送消息时,指定消息的RoutingKey,交换机将消息路由到BindingKey与消息RoutingKey一致的队列。
在这里插入图片描述

不同的队列可以有相同的BindingKey,
在这里插入图片描述

创建两个队列:
在这里插入图片描述
创建direct交换机并进行绑定,并指定routingkey
在这里插入图片描述
生产者:

@Test
    void testSendDirect() {
        String exchangeName = "root.direct";
        String routingKey = "blue";
        String msg = "hello " + routingKey;
        rabbitTemplate.convertAndSend(exchangeName, routingKey, msg);
    }

消费者:

@RabbitListener(queues = "direct.queue1")
    public void listenDirectQueue1(String msg) {
        System.out.println("消费者1收到来自direct queue1 的消息 " + msg);
    }

    @RabbitListener(queues = "direct.queue2")
    public void listenDirectQueue2(String msg) {
        System.out.println("消费者2收到来自direct queue2 的消息 " + msg);
    }

效果:
在这里插入图片描述

Topics

主题模式,routingkey可以是多个单词的列表,并且以.分割。
Queue和Exchange指定Bingdingkey时可以使用通配符:
#:指代0个或多个单词
*:指代1个单词

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
生产者:

@Test
    void testTopic(){
        String exchangeName = "root.topic";
        String routingKey = "China.weather";
        String msg = "今天天气不错";
        rabbitTemplate.convertAndSend(exchangeName, routingKey, msg);
    }

消费者:

@RabbitListener(queues = "topic.queue1")
    public void listenTopicQueue1(String msg) {
        System.out.println("消费者1收到来自topic queue1 的消息 " + msg);
    }

    @RabbitListener(queues = "topic.queue2")
    public void listenTopicQueue2(String msg) {
        System.out.println("消费者2收到来自topic queue2 的消息 " + msg);
    }

效果:
在这里插入图片描述

Spring AMQP声明队列和交换机

使用控制面板声明队列和交换机在实际的生产环境中很不方便且容易出错,Spring AMQP提供了声明队列、交换机和绑定关系对应的方式。

配置类的方式

  • Queue:用于声明队列,可以用工厂类QueueBuilder构建
  • Exchange:用于声明交换机,可以用工厂类ExchangeBuilder构建
  • Binding:用于声明队列和交换机的绑定关系,可以用工厂类BindingBuilder构建

具体操作:
声明一个配置类,在该类中声明队列、交换机和绑定关系。

package com.example.consumer.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfiguration {
    @Bean
    public FanoutExchange fanoutExchange(){
//        ExchangeBuilder.fanoutExchange("root.fanout2").build();
        return new FanoutExchange("root.fanout2");
    }

    @Bean
    public Queue fanoutQueue3() {
//        QueueBuilder.durable("fanout.queue3").build();
        return new Queue("fanout.queue3");
    }

    @Bean
    public Binding fanoutBinding3(Queue fanoutQueue3, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue3).to(fanoutExchange);
    }

    @Bean
    public Queue fanoutQueue4() {
        return new Queue("fanout.queue4");
    }

    @Bean
    public Binding fanoutBinding4() {
        return BindingBuilder.bind(fanoutQueue4()).to(fanoutExchange());
    }
}

缺点:例如当direct交换机绑定多个队列时,需要写多个Bean来声明绑定关系。

使用注解声明

使用@RabbitListener注解
注解源码:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@MessageMapping
@Documented
@Repeatable(RabbitListeners.class)
public @interface RabbitListener {
    String id() default "";

    String containerFactory() default "";

    String[] queues() default {};

    Queue[] queuesToDeclare() default {};

    boolean exclusive() default false;

    String priority() default "";

    String admin() default "";

    QueueBinding[] bindings() default {};

    String group() default "";

    String returnExceptions() default "";

    String errorHandler() default "";

    String concurrency() default "";

    String autoStartup() default "";

    String executor() default "";

    String ackMode() default "";

    String replyPostProcessor() default "";

    String messageConverter() default "";

    String replyContentType() default "";

    String converterWinsContentType() default "true";

    String batch() default "";
}

示例:

@RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "direct.queue1", durable = "true"),
            exchange = @Exchange(name = "root.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg) {
        System.out.println("消费者1收到来自direct queue1 的消息 " + msg);
    }

RabbitMQ特性

保证生产者可靠性–生产者重连

spring:
  rabbitmq:
    connection-timeout: 1s        # 设置MQ的连接超时时间
    template:
      retry:
        enabled: true             # 开启超时重试机制
        initial-interval: 1000ms  # 失败后的初始等待时间
        multiplier: 1             # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier
        max-attempts: 3           # 最大重试次数

在这里插入图片描述

注意:当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的,会影响业务性能。
如果对于业务性能有要求,建议禁用重试机制。如果一定要使用,请合理配置等待时长和重试次数,当然也可以使用异步线程来执行发送消息的代码。

生产者确认

RabbitMQ提供了Publisher ConfirmPublisher Return两种确认机制,开启确认机制后,在MQ成功收到消息后会返回确认消息给生产者,返回的结果有以下几种情况:

  • 消息投递到了MQ,但是路由失败。此时会通过PublisherReturn返回路由异常原因,然后返回ACK,告知投递成功。
  • 临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功。
  • 持久消息投递到了MQ,并且入队完成持久化,返回ACK,告知投递成功。
  • 其他情况都会返回NACK,告知投递失败。
    在这里插入图片描述
    在yml中添加下列配置:
    publisher-confirm-type: correlated  # 开启publisher confirm机制,并设置confirm类型
    publisher-returns: true             # 开启publisher return机制

publisher-confirm-type三种取值:
none:关闭confirm机制
simple:同步阻塞等待MQ的回执消息
correlated:MQ异步回调方式返回回执消息

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值