分布式专题-分布式消息通信之RabbitMQ01-初识RabbitMQ

前言

前面的章节我们分析了《Kafka使用以及原理分析》《ActiveMQ的使用以及原理分析》,从这一节开始,来说说RabbitMQ。
我们知道,消息中间件的特点有:削峰异步解耦,区别点在于语言支持、吞吐量、持久化等差异

关于RabbitMQ,共计分为两小节进行阐述:

RabbitMQ开篇第一节,我会围绕以下几点进行展开

  1. 典型应用场景
  2. 基本介绍
  3. Java API编程
  4. 进阶知识
  5. UI管理界面
  6. Spring配置方式集成RabbitMQ
  7. Spring Boot集成RabbitMQ

*Tips:本文末有福利哦,不要错过!另本节的所有演示代码已上传GitHub,地址我也在文末一并送出~

本人郑重承诺不玩微信公众号,不接任何广告,无任何收费以及广告倾向,只为打造CSDN文化净土,一心知识分享,天道酬勤,让我们技术人携手共进,风雨同舟,开创互联网的美好未来,从今天的你我做起~

典型应用场景

  1. 跨系统的异步通信 人民银行二代支付系统,使用重量级消息队列 IBM MQ,异步,解耦,削峰都有体现。

  2. 应用内的同步变成异步秒杀:自己发送给自己

  3. 基于Pub/Sub模型实现的事件驱动 放款失败通知、提货通知、购买碎屏保 系统间同步数据 摒弃ELT(比如全量同步商户数据); 摒弃API(比如定时增量获取用户、获取产品,变成增量广播)。

  4. 利用RabbitMQ实现事务的最终一致性

基本介绍

关于RabbitMQ的安装,文末后记有相关安装的文章链接,笔者亲自试用没有问题,包括Windows与Linux的版本,这里就不细说了,直接上干货~

AMQP协议

我们说为什么要有设计模式?其实最重要的在与规范化,大家达成一致才好干活,而AMQP就是为解决各种消息中间件的多语言,多平台的不统一的一个协议,它制订了一套消息通信的规范,使得软件开发变得甜甜的。

AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品、不同的开发语言等条件的限制。

AMQP的实现有:RabbitMQ、OpenAMQ、Apache Qpid、Redhat Enterprise MRG、AMQP Infrastructure、ØMQ、Zyre等。

RabbitMQ的特性

RabbitMQ使用Erlang语言编写,使用Mnesia数据库存储消息。

  • 可靠性(Reliability) RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。

  • 灵活的路由(Flexible Routing) 在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。

  • 消息集群(Clustering) 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。

  • 高可用(Highly Available Queues) 队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。

  • 多种协议(Multi-protocol) RabbitMQ 支持多种消息队列协议,比如 AMQP、STOMP、MQTT 等等。

  • 多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby、PHP、C#、 JavaScript 等等。

  • 管理界面(Management UI) RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点。

  • 插件机制(Plugin System)RabbitMQ提供了许多插件,以实现从多方面扩展,当然也可以编写自己的插件。

工作模型

在这里插入图片描述
这里解释一下图上的几个概念:

概念解释
Broker即RabbitMQ的实体服务器。提供一种传输服务,维护一条从生产者到消费者的传输线路,保证消息数据能按照指定的方式传输。
Exchange消息交换机。指定消息按照什么规则路由到哪个队列Queue。
Queue消息队列。消息的载体,每条消息都会被投送到一个或多个队列中。
Binding绑定。作用就是将Exchange和Queue按照某种路由规则绑定起来。
Routing路由关键字。Exchange根据Routing Key进行消息投递。定义绑定时指定的关键字称为Key Binding Key。
Vhost虚拟主机。一个Broker可以有多个虚拟主机,用作不同用户的权限分离。一个虚拟主机持有一组Exchange、Queue和Binding。
Producer消息生产者。主要将消息投递到对应的Exchange上面。一般是独立的程序。
Consumer消息消费者。消息的接收者,一般是独立的程序。
ConnectionProducer 和 Consumer 与Broker之间的TCP长连接。
Channel消息通道,也称信道。在客户端的每个连接里可以建立多个Channel,每个Channel代表一个会话任务。在RabbitMQ Java Client API中,channel上定义了大量的编程接口。

三种主要的交换机

Direct Exchange直连交换机

定义:直连类型的交换机与一个队列绑定时,需要指定一个明确的binding key。

路由规则:发送消息到直连类型的交换机时,只有routing key跟binding key完全匹配时,绑定的队列才能收到消息。

例如:

//只有队列1能收到消息
channel.basicPublish("MY_DIRECT_EXCHANGE", "key1", null, msg.getBytes());

在这里插入图片描述

Topic Exchange主题交换机

定义:主题类型的交换机与一个队列绑定时,可以指定按模式匹配的routing key。

通配符有两个,*代表匹配一个单词。#代表匹配零个或者多个单词。单词与单词之间用 . 隔开。

路由规则:发送消息到主题类型的交换机时,routing key符合binding key的模式时,绑定的队列才能收到消息。

例如:

//只有队列1能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "sh.abc", null, msg.getBytes());

//队列2和队列3能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "bj.book", null, msg.getBytes());

//只有队列4能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "abc.def.food", null, msg.getBytes());

在这里插入图片描述

Fanout Exchange广播交换机

定义:广播类型的交换机与一个队列绑定时,不需要指定binding key。

路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。

例如:

//3个队列都会收到消息
channel.basicPublish("MY_FANOUT_EXCHANGE", "", null, msg.getBytes());

在这里插入图片描述

Java API编程

创建Maven工程,pom.xml引入依赖

<dependency>
   <groupId>com.rabbitmq</groupId>
   <artifactId>amqp-client</artifactId>
   <version>4.1.0</version>
</dependency>

生产者

public class MyProducer {
    private final static String EXCHANGE_NAME = "SIMPLE_EXCHANGE";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        // 连接IP
        factory.setHost("192.168.200.111");
        // 连接端口
        factory.setPort(5672);
        // 虚拟机
        factory.setVirtualHost("/");
        // 用户
        factory.setUsername("guest");
        factory.setPassword("guest");

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 发送消息
        String msg = "Hello world, Rabbit MQ";

        // String exchange, String routingKey, BasicProperties props, byte[] body
        channel.basicPublish(EXCHANGE_NAME, "best", null, msg.getBytes());

        channel.close();
        conn.close();
    }
}

消费者

public class MyConsumer {
    private final static String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
    private final static String QUEUE_NAME = "SIMPLE_QUEUE";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        // 连接IP
        factory.setHost("192.168.200.111");
        // 默认监听端口
        factory.setPort(5672);
        // 虚拟机
        factory.setVirtualHost("/");

        // 设置访问的用户
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 声明交换机
        // String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
        channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);

        // 声明队列
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" Waiting for message....");

        // 绑定队列和交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"best");

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("Received message : '" + msg + "'");
                System.out.println("consumerTag : " + consumerTag );
                System.out.println("deliveryTag : " + envelope.getDeliveryTag() );
            }
        };

        // 开始获取消息
        // String queue, boolean autoAck, Consumer callback
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

测试用例

slf4j警告可以忽略,不影响测试~
消费者:
在这里插入图片描述
生产者:
在这里插入图片描述
Web页面:
在这里插入图片描述

参数说明

如上面代码所示,我们看看配置通信的过程中参数的设置

声明交换机的参数

// 声明交换机
// String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);

String type:交换机的类型,direct, topic, fanout中的一种。

boolean durable:是否持久化,代表交换机在服务器重启后是否还存在。

Channel.queueDeclare方法的构造函数

声明队列的参数

// 声明队列
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);

boolean durable:是否持久化,代表队列在服务器重启后是否还存在。

boolean exclusive:是否排他性队列。排他性队列只能在声明它的Connection中使用,连接断开时自动删除。

boolean autoDelete:是否自动删除。如果为true,至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,队列会自动删除。

Map<String, Object> arguments:队列的其他属性,例如x-message-ttl、x-expires、x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-dead-letter-routing-key、x-max-priority。

消息属性BasicProperties

消息的全部属性有14个,以下列举了一些主要的参数:

参数释义
Map<String,Object> headers消息的其他自定义参数
Integer deliveryMode2持久化,其他:瞬态
Integer priority消息的优先级
String correlationId关联ID,方便RPC相应与请求关联
String replyTo回调队列
String expirationTTL,消息过期时间,单位毫秒

RabbitMQ的进阶知识

  1. 怎么自动删除没人消费的消息?
  2. 无法路由的消息,去了哪里?
  3. 可以让消息优先得到消费吗?
  4. 如何实现延迟发送消息?
  5. MQ怎么实现RPC?
  6. RabbitMQ流量控制怎么做?设置队列大小有用吗?

TTL

TTL: time to live

  • 消息的过期时间
  • 队列的过期时间

消息的过期时间

 public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        String msg = "Hello world, Rabbit MQ, DLX MSG";

        // 通过队列属性设置消息过期时间
        Map<String, Object> argss = new HashMap<String, Object>();
        argss.put("x-message-ttl",6000);

        // 声明队列(默认交换机AMQP default,Direct)
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare("TEST_TTL_QUEUE", false, false, false, argss);

        // 对每条消息设置过期时间
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2) // 持久化消息
                .contentEncoding("UTF-8")
                .expiration("10000") // TTL
                .build();

        // 此处两种方式设置消息过期时间的方式都使用了,将以较小的数值为准

        // 发送消息
        channel.basicPublish("", "TEST_DLX_QUEUE", properties, msg.getBytes());

        channel.close();
        conn.close();
    }

死信队列

死信队列: 生产者生产一条消息后,但此消息没有路由到任何一个队列的时候,这个消息就变成了死信,如果我们没有设置一些比如备份交换机、死信交换机这种策略的话,那么这样的消息就会被直接删除,但是有种方法可以将死信消息堆积起来,就是DLX。

有三种情况消息会进入DLX(Dead Letter Exchange)死信交换机。

  1. (NACK || Reject ) && requeue == false
   // 创建消费者,并接收消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("Received message : '" + msg + "'");

                if (msg.contains("拒收")){
                    // 拒绝消息
                    // requeue:是否重新入队列,true:是;false:直接丢弃,相当于告诉队列可以直接删除掉
                    // TODO 如果只有这一个消费者,requeue 为true 的时候会造成消息重复消费
                    channel.basicReject(envelope.getDeliveryTag(), false);
                } else if (msg.contains("异常")){
                    // 批量拒绝
                    // requeue:是否重新入队列
                    // TODO 如果只有这一个消费者,requeue 为true 的时候会造成消息重复消费
                    channel.basicNack(envelope.getDeliveryTag(), true, false);
                } else {
                    // 手工应答
                    // 如果不应答,队列中的消息会一直存在,重新连接的时候会重复消费
                    channel.basicAck(envelope.getDeliveryTag(), true);
                }
            }
        };
  1. 消息过期

前面提到,生产者生产一条消息后,但此消息没有路由到任何一个队列的时候,这个消息就变成了死信~

        // 指定队列的死信交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
        arguments.put("x-expires","9000"); // 设置队列的TTL
  1. 队列达到最大长度(先入队的消息会被发送到DLX)
        // 指定队列的死信交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
        arguments.put("x-max-length", 4); // 如果设置了队列的最大长度,超过长度时,先入队的消息会被发送到DLX

那么怎么获取死信的消息呢?

可以设置一个死信队列(Dead Letter Queue)与DLX绑定,即可以存储Dead Letter,消费者可以监听这个队列取走消息。

        // 指定队列的死信交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
        // 声明队列(默认交换机AMQP default,Direct)
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare("TEST_DLX_QUEUE", false, false, false, arguments);

        // 声明死信交换机
        channel.exchangeDeclare("DLX_EXCHANGE","topic", false, false, false, null);
        // 声明死信队列
        channel.queueDeclare("DLX_QUEUE", false, false, false, null);
        // 绑定,此处 Dead letter routing key 设置为 #
        channel.queueBind("DLX_QUEUE","DLX_EXCHANGE","#");
        System.out.println(" Waiting for message....");

流程如是:
在这里插入图片描述

演示代码参考:rabbitmq-demo/rabbitmq-javaapi/com.test.ack

优先级队列

设置一个队列的最大优先级:

      Map<String, Object> headers = new HashMap<String, Object>();
        headers.put("name", "gupao");
        headers.put("level", "top");
        // 声明队列(默认交换机AMQP default,Direct)
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

发送消息时指定消息当前的优先级:

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2)   // 2代表持久化
                .contentEncoding("UTF-8")  // 编码
                .expiration("10000")  // TTL,过期时间
                .headers(headers) // 自定义属性
                .priority(5) // 优先级,默认为5,配合队列的 x-max-priority 属性使用
                .messageId(String.valueOf(UUID.randomUUID()))
                .build();
      // 发送消息
        // String exchange, String routingKey, BasicProperties props, byte[] body
        channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());

优先级高的消息可以优先被消费,但是:只有消息堆积(消息的发送速度大于消费者的消费速度)的情况下优先级才有意义。

演示代码参考:rabbitmq-demo/rabbitmq-javaapi/com.test.message

延迟队列

RabbitMQ本身不支持延迟队列。可以使用TTL结合DLX的方式来实现消息的延迟投递,即把DLX跟某个队列绑定,到了指定时间,消息过期后,就会从DLX路由到这个队列,消费者可以从这个队列取走消息。

另一种方式是使用rabbitmq-delayed-message-exchange插件。

当然,将需要发送的信息保存在数据库,使用任务调度系统扫描然后发送也是可以实现的。

演示代码参考:rabbitmq-demo/rabbitmq-javaapi/com.test.dlx

PRC

RabbitMQ实现RPC的原理:服务端处理消息后,把响应消息发送到一个响应队列,客户端再从响应队列取到结果。

其中的问题:Client收到消息后,怎么知道应答消息是回复哪一条消息的?所以必须有一个唯一ID来关联,就是correlationId。
在这里插入图片描述

演示代码参考:rabbitmq-demo/rabbitmq-javaapi/com.test.rpc

服务端流控(Flow Control)

RabbitMQ 会在启动时检测机器的物理内存数值。默认当 MQ 占用 40% 以上内存时,MQ 会主动抛出一个内存警告并阻塞所有连接(Connections)。可以通过修改 rabbitmq.config 文件来调整内存阈值,默认值是 0.4,如下所示: [{rabbit, [{vm_memory_high_watermark, 0.4}]}].

默认情况,如果剩余磁盘空间在 1GB 以下,RabbitMQ 主动阻塞所有的生产者。这个阈值也是可调的。

注意队列长度只在消息堆积的情况下有意义,而且会删除先入队的消息,不能实现服务端限流。

消费端限流

在AutoACK为false的情况下,如果一定数目的消息(通过基于consumer或者channel设置Qos的值)未被确认前,不进行消费新的消息。

        //非自动确认消息的前提下,如果一定数目的消息(通过基于consume或者channel设置Qos的值)未被确认前,不进行消费新的消息。
        // 因为Consumer2的处理速率很慢,收到两条消息后都没有发送ACK,队列不会再发送消息给Consumer2
        channel.basicQos(2);
        channel.basicConsume(QUEUE_NAME, false, consumer);

演示代码参考:rabbitmq-demo/rabbitmq-javaapi/com.test.limit

UI管理界面的使用

管理插件提供了更简单的管理方式。

启用管理插件

  • Windows启用管理插件

cd C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.6\sbin
rabbitmq-plugins.bat enable rabbitmq_management

  • Linux启用管理插件

cd /data/program/rabbitmq_server-3.7.16/sbin
./rabbitmq-plugins enable rabbitmq_management

在这里插入图片描述

管理界面访问端口
在这里插入图片描述

默认端口是15672,默认用户guest,密码guest。guest用户默认只能在本机访问。

在这里插入图片描述
登陆后:
在这里插入图片描述

Linux 创建RabbitMQ用户

例如创建用户admin,密码admin,授权访问所有的Vhost

firewall-cmd --permanent --add-port=15672/tcp
firewall-cmd --reload
rabbitmqctl add_user admin admin
rabbitmqctl set_user_tags admin administrator rabbitmqctl set_permissions -p / admin “." ".” “.*”

Spring配置方式集成RabbitMQ

步骤

1、创建Maven工程,pom.xml引入依赖

        <!--rabbitmq依赖 -->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>

2、src/main/resouces目录,创建rabbitMQ.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/rabbit
     http://www.springframework.org/schema/rabbit/spring-rabbit-1.2.xsd">

    <!--配置connection-factory,指定连接rabbit server参数 -->
    <rabbit:connection-factory id="connectionFactory" virtual-host="/" username="guest" password="guest" host="192.168.200.111" port="5672" />

    <!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成 -->
    <rabbit:admin id="connectAdmin" connection-factory="connectionFactory" />

    <!--######分隔线######-->
    <!--定义queue -->
    <rabbit:queue name="MY_FIRST_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />

    <!--定义direct exchange,绑定MY_FIRST_QUEUE -->
    <rabbit:direct-exchange name="MY_DIRECT_EXCHANGE" durable="true" auto-delete="false" declared-by="connectAdmin">
        <rabbit:bindings>
            <rabbit:binding queue="MY_FIRST_QUEUE" key="FirstKey">
            </rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--定义rabbit template用于数据的接收和发送 -->
    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory" exchange="MY_DIRECT_EXCHANGE" />

    <!--消息接收者 -->
    <bean id="messageReceiver" class="com.test.consumer.FirstConsumer"></bean>

    <!--queue listener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener queues="MY_FIRST_QUEUE" ref="messageReceiver" />
    </rabbit:listener-container>

    <!--定义queue -->
    <rabbit:queue name="MY_SECOND_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />

    <!-- 将已经定义的Exchange绑定到MY_SECOND_QUEUE,注意关键词是key -->
    <rabbit:direct-exchange name="MY_DIRECT_EXCHANGE" durable="true" auto-delete="false" declared-by="connectAdmin">
        <rabbit:bindings>
            <rabbit:binding queue="MY_SECOND_QUEUE" key="SecondKey"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- 消息接收者 -->
    <bean id="receiverSecond" class="com.test.consumer.SecondConsumer"></bean>

    <!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener queues="MY_SECOND_QUEUE" ref="receiverSecond" />
    </rabbit:listener-container>

    <!--######分隔线######-->
    <!--定义queue -->
    <rabbit:queue name="MY_THIRD_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />

    <!-- 定义topic exchange,绑定MY_THIRD_QUEUE,注意关键词是pattern -->
    <rabbit:topic-exchange name="MY_TOPIC_EXCHANGE" durable="true" auto-delete="false" declared-by="connectAdmin">
        <rabbit:bindings>
            <rabbit:binding queue="MY_THIRD_QUEUE" pattern="#.Third.#"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义rabbit template用于数据的接收和发送 -->
    <rabbit:template id="amqpTemplate2" connection-factory="connectionFactory" exchange="MY_TOPIC_EXCHANGE" />

    <!-- 消息接收者 -->
    <bean id="receiverThird" class="com.test.consumer.ThirdConsumer"></bean>

    <!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener queues="MY_THIRD_QUEUE" ref="receiverThird" />
    </rabbit:listener-container>

    <!--######分隔线######-->
    <!--定义queue -->
    <rabbit:queue name="MY_FOURTH_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />

    <!-- 定义fanout exchange,绑定MY_FIRST_QUEUE 和 MY_FOURTH_QUEUE -->
    <rabbit:fanout-exchange name="MY_FANOUT_EXCHANGE" auto-delete="false" durable="true" declared-by="connectAdmin" >
        <rabbit:bindings>
            <rabbit:binding queue="MY_FIRST_QUEUE"></rabbit:binding>
            <rabbit:binding queue="MY_FOURTH_QUEUE"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:fanout-exchange>

    <!-- 消息接收者 -->
    <bean id="receiverFourth" class="com.test.consumer.FourthConsumer"></bean>

    <!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener queues="MY_FOURTH_QUEUE" ref="receiverFourth" />
    </rabbit:listener-container>
</beans>

整体流程如下:
在这里插入图片描述
3、配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <import resource="classpath*:rabbitMQ.xml" />

    <!-- 扫描指定package下所有带有如 @Controller,@Service,@Resource 并把所注释的注册为Spring Beans -->
    <context:component-scan base-package="com.test.*" />

    <!-- 激活annotation功能 -->
    <context:annotation-config />

    <!-- 激活annotation功能 -->
    <context:spring-configured />
</beans>

4、src/main/resouces目录,log4j.properties
在这里插入图片描述

5、编写生产者

@Service
public class MessageProducer {
    private Logger logger = LoggerFactory.getLogger(MessageProducer.class);

    @Autowired
    @Qualifier("amqpTemplate")
    private AmqpTemplate amqpTemplate;

    @Autowired
    @Qualifier("amqpTemplate2")
    private AmqpTemplate amqpTemplate2;

    /**
     * 演示三种交换机的使用
     *
     * @param message
     */
    public void sendMessage(Object message) {
        logger.info("Send message:" + message);

        // amqpTemplate 默认交换机 MY_DIRECT_EXCHANGE
        // amqpTemplate2 默认交换机 MY_TOPIC_EXCHANGE

        // Exchange 为 direct 模式,直接指定routingKey
        amqpTemplate.convertAndSend("FirstKey", "[Direct,FirstKey] "+message);
        amqpTemplate.convertAndSend("SecondKey", "[Direct,SecondKey] "+message);

        // Exchange模式为topic,通过topic匹配关心该主题的队列
        amqpTemplate2.convertAndSend("msg.Third.send","[Topic,msg.Third.send] "+message);

        // 广播消息,与Exchange绑定的所有队列都会收到消息,routingKey为空
        amqpTemplate2.convertAndSend("MY_FANOUT_EXCHANGE",null,"[Fanout] "+message);
    }
}

6、编写4个消费者

public class FirstConsumer implements MessageListener {
    private Logger logger = LoggerFactory.getLogger(FirstConsumer.class);

    public void onMessage(Message message) {
        logger.info("The first consumer received message : " + message.getBody());
    }
}

类比再写三个消费者,由于代码逻辑相同,这里不贴出来了,有兴趣的朋友可以看我的完整代码:

演示代码参考:rabbitmq-demo/spring-rabbitmq

代码测试:
运行测试用例,每个一秒发送一次消息,生产者指定三种交换机,四个消费者根据指定的消费者这100条数据。
在这里插入图片描述
日志打印也证实了实验的猜想。

Spring Boot集成RabbitMQ

步骤

springBoot封装了mq的操作,所以springboot集成mq不需要导入依赖,直接使用AmqpTemplate即可。
首先配置交换机与队列的关系:

@Configuration
public class RabbitConfig {

    // 两个交换机
    @Bean("topicExchange")
    public TopicExchange getTopicExchange(){
        return new TopicExchange("TOPIC_EXCHANGE");
    }

    @Bean("fanoutExchange")
    public FanoutExchange getFanoutExchange(){
        return  new FanoutExchange("FANOUT_EXCHANGE");
    }

    // 三个队列
    @Bean("firstQueue")
    public Queue getFirstQueue(){
        Map<String, Object> args = new HashMap<String, Object>();
        args.put("x-message-ttl",6000);
        Queue queue = new Queue("FIRST_QUEUE", false, false, true, args);
        return queue;
    }

    @Bean("secondQueue")
    public Queue getSecondQueue(){
        return new Queue("SECOND_QUEUE");
    }

    @Bean("thirdQueue")
    public Queue getThirdQueue(){
        return new Queue("THIRD_QUEUE");
    }

    // 两个绑定
    @Bean
    public Binding bindSecond(@Qualifier("secondQueue") Queue queue,@Qualifier("topicExchange") TopicExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("#.test.#");
    }

    @Bean
    public Binding bindThird(@Qualifier("thirdQueue") Queue queue,@Qualifier("fanoutExchange") FanoutExchange exchange){
        return BindingBuilder.bind(queue).to(exchange);
    }

}

写三个生产者类,代码类似,只贴出一个生产者,完整代码详见本文末GItlab地址:

@Component
@RabbitListener(queues = "FIRST_QUEUE")
public class FirstConsumer {

    @RabbitHandler
    public void process(String msg){
        System.out.println(" first queue received msg : " + msg);
    }
}

消费者:

@Component
public class MyProvider {

    @Autowired
    AmqpTemplate amqpTemplate;

    public void send(){
        // 发送4条消息

        amqpTemplate.convertAndSend("","FIRST_QUEUE","-------- a direct msg");

        amqpTemplate.convertAndSend("TOPIC_EXCHANGE","shanghai.test.teacher","-------- a topic msg : shanghai.test.teacher");
        amqpTemplate.convertAndSend("TOPIC_EXCHANGE","changsha.test.student","-------- a topic msg : changsha.test.student");

        amqpTemplate.convertAndSend("FANOUT_EXCHANGE","","-------- a fanout msg");

    }

}

测试用例:


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

	@Autowired
	MyProvider provider;

	@Test
	public void contextLoads() {
		provider.send();
	}

}

这样,就简单的通过springboot集成rabbitmq~

演示代码参考:rabbitmq-demo/springboot-mq

后记

参与评论 您还未登录,请先 登录 后发表或查看评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:博客之星2020 设计师:CSDN官方博客 返回首页

打赏作者

✎ℳ๓₯㎕...雲淡風輕

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值