RabbitMQ 介绍与 SpringBootAMQP使用

一、MQ概述

异步通信的优点:

  • 耦合度低
  • 吞吐量提升
  • 故障隔离
  • 流量削峰

异步通信的缺点:

  • 依赖于Broker的可靠性、安全性、吞吐能力
  • 架构复杂,业务么有明显的流程线,不方便追踪管理

什么是的MQ

MQ(Message Queue),消息队列,就是放消息的队列。也是事件驱动架构中的Broker。

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala&Java
协议支持 AMQP、XMPP、 SMTP、STOMP OpenWire、STOMP、 REST、XMPP、AMQP 自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒级
消息可靠性 一般 一般

二、RabbitMQ概述

1. RabbitMQ的结构和概念

  • Channel:操作MQ的工具
  • Exchange:路由消息到队列中
  • Queue:缓存消息
  • Virtual Host:虚拟主机,是对Queue、Exchange等资源的逻辑分组


2. 常见消息模型

  • 基本消息队列(BasicQueue)

    • Publisher:消息发布者,将消息发送到队列Queue
    • Queue:消息队列,负责接受并缓存消息
    • Consumer:订阅队列,处理队列中的消息
  • 工作消息队列(WorkQueue)

    Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息

    当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型,多个消费者共同进行消息处理,提高消费速度。

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

  • Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

  • Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有以下3种类型:

    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
  • Consumer:消费者,与以前一样,订阅队列,没有变化

  • Queue:消息队列也与以前一样,接收消息、缓存消息。


    Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

  • Fanout Exchange: 广播

    在广播模式下,消息发送流程:

    复制代码
    - 1)  可以有多个队列
    - 2)  每个队列都要绑定到Exchange(交换机)
    - 3)  生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
    - 4)  交换机把消息发送给绑定过的所有队列
    - 5)  订阅队列的消费者都能拿到消息
  • Direct Exchange:路由

    在Fanout模式中,一条消息会被所有订阅的队列都消费。但是,在某些场景下,希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

    在Direct模型下:

    • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
    • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey
    • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
  • Topic Exchange:主题

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

通配符规则:

  • #:匹配一个或多个词
  • *:匹配不多不少恰好1个词

如下图:

  • Queue1:绑定的是china.# ,因此凡是以 china.开头的routing key 都会被匹配到。包括china.news和china.weather
  • Queue2:绑定的是#.news ,因此凡是以 .news结尾的 routing key 都会被匹配。包括china.news和japan.news

3. RabbitMQ的安装

1、安装Erlang:RabbitMQ是用Erlang编写的,因此首先需要安装Erlang运行环境(注意Erlang与RabbitMQ的对应版本)。运行以下命令进行安装:sudo apt install erlang

2、在线拉取镜像:docker pull rabbitmq:3-management

3、运行以下命令来下载并启动RabbitMQ Docker镜像:docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

4、浏览器访问RabbitMQ管理页面:http://IP:15672/(注意:若网页无法访问,可能是rabbitmq_management插件未启用)

5、进入sbin目录下,查看插件,命令:rabbitmq-plugins list

6、 若 rabbitmq_management 插件未启用(状态无 * ),通过命令启用该插件:rabbitmq-plugins enable rabbitmq_management

7、启用后,重新访问地址,用户名/密码默认:guest/guest

三、SpringAMQP

AMQP,Adanced Message Queuing Protocol,是用于在应用程序之间传递业务消息的开发标准,与语言和平台无关。

SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。

SpringAmqp的官方地址:https://spring.io/projects/spring-amqp

SpringAMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听器模式,异步接收消息
  • 封装了RabbitTemplate工具,用于发送消息

1、使用SpringBootAMQP- SimpleQueue的步骤

  • 引入AMQP的Starter依赖
java 复制代码
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 配置RabbitMQ地址
java 复制代码
logging:
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        prefetch: 3 # 每次只能获取一条消息,处理完成才能获取下一个消息
  • 利用RabbitTemplate的convertAndSend方法
java 复制代码
package com.example.rabbitmq_demo;

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

@RunWith(SpringRunner.class)
@SpringBootTest
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        //queueName
        String queueName = "ty.simple.queue";

        //message
        String message = "hello world ";

        //send Message
        rabbitTemplate.convertAndSend(queueName, message);
    }

}

2、使用SpringBootAMQP- FanoutExchange的步骤

  • 创建Spring配置类,绑定交换机 - 队列
java 复制代码
package com.example.rabbitmq_demo.consumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfig {

    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("ty.fanout");
    }

    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定交换机与队列
     */
    @Bean
    public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    @Bean
    public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}
  • 利用RabbitTemplate的convertAndSend方法
java 复制代码
package com.example.rabbitmq_demo;

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

@RunWith(SpringRunner.class)
@SpringBootTest
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendFanoutExchange() {
        //exchangeName
        String exchangeName = "ty.fanout";

        //message
        String message = "hello world fanout";

        //send Message
        rabbitTemplate.convertAndSend(exchangeName, "", message);

    }

}

2、使用SpringBootAMQP- Direct的步骤

  • 基于注解来声明队列和交换机
java 复制代码
package com.example.rabbitmq_demo.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ConsumerDemo {

    /**
     * 基于@Bean的方式声明队列和交换机比较麻烦,Spring还提供了基于注解方式来声明
     * 在consumer的SpringRabbitListener中添加两个消费者,同时基于注解来声明队列和交换机
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.direct.queue1"),
            exchange = @Exchange(name = "ty.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "green"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("listener ty.direct.queue1 Get message : " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.direct.queue2"),
            exchange = @Exchange(name = "ty.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("listener ty.direct.queue2 Get message : " + msg);
    }
}
  • 通过convertAndSend发送消息,会根据的RoutingKey,将消息发送至指定队列。
java 复制代码
package com.example.rabbitmq_demo;

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

@RunWith(SpringRunner.class)
@SpringBootTest
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void testSendDirectExchange(){
        String exchangeName = "ty.direct";
        String message = "hello ty";
        rabbitTemplate.convertAndSend(exchangeName, "red", message);
    }
}

3、使用SpringBootAMQP- Tpic的步骤

  • 基于注解来声明队列和交换机
java 复制代码
package com.example.rabbitmq_demo.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ConsumerDemo {
    /**
     * Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.topic.queue1"),
            exchange = @Exchange(name = "ty.topic", type = ExchangeTypes.TOPIC),
            key = "ty.#"
    ))
    public void listenTopicQueue1(String msg){
        System.out.println("listener ty.topic.queue1 Get message : " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.topic.queue2"),
            exchange = @Exchange(name = "ty.topic", type = ExchangeTypes.TOPIC),
            key = "#.tyty"
    ))
    public void listenTopicQueue2(String msg){
        System.out.println("listener ty.topic.queue2 Get message : " + msg);
    }
}
  • 根据RoutingKey通配符,发送到对应Queue
java 复制代码
package com.example.rabbitmq_demo;

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

@RunWith(SpringRunner.class)
@SpringBootTest
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendTopicExchange(){
        String exchangeName = "ty.topic";
        String message = "hello ty";
        rabbitTemplate.convertAndSend(exchangeName, "ty.tyty", message);
    }

}

4、SpringBootAMQP对象序列化

SpringBootAMQP默认使用的是 x-java-serialized-object,JDK序列化数据体积过大、有安全漏洞,且可读性差。

可通过配置JSON转换器,使用Json的方式做序列化和反序列化。

  • 引入jar
java 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>
  • 配置类中增加Bean
java 复制代码
@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}
相关推荐
初级炼丹师(爱说实话版)1 天前
MySql速成笔记5(多表关系)
笔记
七夜星七夜月1 天前
生成与无监督学习 —— 奶茶店的 “新品研发与原料优化体系”
学习
iconball1 天前
个人用云计算学习笔记 --19 (MariaDB服务器)
linux·运维·笔记·学习·云计算
岑梓铭1 天前
《考研408数据结构》第四章(串和串的算法)复习笔记
数据结构·笔记·考研·算法
Lynnxiaowen1 天前
今天我们开始学习python3编程之python基础
linux·运维·python·学习
少吃一口都不行1 天前
脚手架学习
前端·javascript·学习
东风西巷1 天前
Avast Cleanup安卓版(手机清理优化) 修改版
android·学习·智能手机·软件需求
冬夜戏雪1 天前
记录下C盘清理步骤(有效)
经验分享·笔记
我登哥MVP1 天前
Apache Tomcat 详解
java·笔记·tomcat
泽虞1 天前
《Qt应用开发》笔记
linux·开发语言·c++·笔记·qt