RabbitMQ概念及使用-RabbitMQ

概述

为什么使用消息队列

  1. 异步处理:提高系统的吞吐量。
  2. 解耦:系统与系统之间通过消息队列来传递消息,减少系统之间的耦合度。
  3. 流量削峰:可以通过控制消息队列的长度来控制请求的数量,缓解端时间内系统的高并发。

使用场景

异步处理

场景说明

用户注册后,需要发注册邮件和注册短信,传统的做法有两种:

  1. 串行的方式;
  2. 并行的方式。
    其实我们也可以使用消息中间件进行异步处理的方式实现。

串行方式

将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。
这里的问题是,邮件、短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西。
在这里插入图片描述

并行方式

将注册信息落库后,发送邮件的同时、发送短信,以上三个任务完成后,返回给客户端,并行的方式能够提高处理的时间。
在这里插入图片描述
假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高了处理时间,但是邮件和短信对我们正常的使用网站没有任何影响,客户端没必要等待其发送完成才显示注册成功,应该是落库后就返回。
因此,我们使用了消息队列进行异步处理。

消息队列

引入消息队列后,把发送邮件、短信不是必须的业务逻辑异步处理。
在这里插入图片描述
由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。

应用解耦

场景说明

双11购物节,用户下单后,订单系统需要通知库存系统。
传统的做法就是订单系统调用库存系统的接口。
在这里插入图片描述

实现方式

这种做法有一个缺点:

  • 当库存系统出现故障时,订单就会失败;
  • 订单系统和库存系统高耦合;
    我们可以通过引入消息队列来进行解耦。
    在这里插入图片描述
  • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
  • 库存系统:订阅下单的消息,获取下单消息,进行库操作。
    即使库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。

流量削峰

场景说明

流量削峰一般在秒杀活动中应用广泛。
秒杀活动,一般会因为流量过大,导致应用挂断,为了解决这个问题,一般在应用前端加入消息队列。
在这里插入图片描述

实现方式

  1. 用户的请求:服务器收到请求后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面。
  2. 秒杀业务根据消息队列中的请求消息,再做后续处理。

AMQP协议

  1. AMQP(advanced message queuing protocol):高级消息队列协议。AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是和其他JMS的本质差别。AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。
  2. 高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦和通讯。
  3. AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
  4. RabbitMQ是一个开源的AMQP实现,服务器端用 Erlang 语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,具有很高的易用性和可用性。

协议模型

在这里插入图片描述

Broker

消息队列服务器实体(物理上的Server),存放交换机和队列。

Exchange

消息交换机,用来路由生产者发送的消息到具体的消息队列。
Exchange生产者将消息发送到 Exchange(交换器,下图中的X),由 Exchange 根据一定的规则将消息路由到一个或多个 Queue 中(或者丢弃)。
在这里插入图片描述

Queue(队列)

消息队列载体,用来存储消息,每个消息都会被投入到一个或多个队列。
在这里插入图片描述
RabbitMQ中的消息只能存储在 Queue 中。生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费,消费者可以是一个或者多个。
在这里插入图片描述

Binding(绑定)

作用是把exchange和queue按照路由规则绑定起来。
RabbitMQ中通过 Binding 将 Exchange 与 Queue 关联起来。
在绑定(Binding) Exchange 与 Queue 关系的同时,一般会指定一个 binding key。
在这里插入图片描述

Routing Key(路由关键字)

exchange根据这个关键字进行消息投递。
生产者在将消息发送给 Exchange 的时候,一般会指定一个 routing key,来指定这个消息的路由规则。 Exchange 会根据 routing key 和 Exchange Type(交换器类型) 以及 Binding key 的匹配情况来决定把消息路由到哪个 Queue。RabbitMQ为routing key设定的长度限制为255 bytes。

同一个队列的同一条消息只会被一个消费者消费

注意:不管是什么类型的交换机,同一个队列的同一个消息只能被一个消费者消费一次,理论上不存在一个队列的同一个消息被两个消费者消费的情况。

VHost

可以理解为虚拟broker,其内部均含有独立的queue、exchange和binding。VHost拥有独立的权限系统,可以看成一个完整的broker server。

Channel(消息通道)

每个消息通道相当于一个会话任务 exchange到queue的唯一线路,需要由exchange和queue以及routingKey来决定。

ConnectionFactory、Connection、Channel

ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。

  • ConnectionFactory:ConnectionFactory为Connection的制造工厂。
  • Connection:Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。
  • Channel:信道是建立在真实的TCP连接上的虚拟连接,在一条TCP链接上创建多少条信道是没有限制的。它是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

Message Acknowledgment(ack消息的确认)

在实际应用中,可能会发生消费者收到Queue中的消息,但没有出来完成就宕机(或出现其他意外)的情况,这种情况下就可能导致消息丢失。为了避免这种情况发送,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(ack)后才将该消息从Queue中移除;
如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。
这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送给其他消费者,除非他的RabbitMQ连接断开。
这里会产生一个另外一个问题,如果我们的开发人员在处理完业务逻辑后,忘记发送回执给RabbitMQ,这将会导致严重的bug一一Queue中堆积的消息会越来越多;消费者重启后会重复消费这些消息并重复执行业务逻辑…
另外,pub message是没有ack的。

Message durability(消息的持久化)

如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。但依然解决不了小概率丢失事件的发生(比如RabbitMQ服务器已经接收到生产者的消息,但还没来得及持久化该消息时RabbitMQ服务器就断电了),如果我们需要对这种小概率事件也要管理起来,那么我们要用到事务。由于这里仅为RabbitMQ的简单介绍,所以这里将不讲解RabbitMQ相关的事务。具体可以参考RabbitMQ之消息确认机制(事务+Confirm)

Prefetch count(每次向消费者发送消息的总数)

前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
在这里插入图片描述

Exchange Types(交换器类型)

RabbitMQ常用的Exchange Type有Fanout、Direct、Topic、Headers四种。

Fanout

这种类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,这时Routing key不起作用。
在这里插入图片描述

Direct

这种类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
在这里插入图片描述
在这个设置中,我们可以看到两个队列Q1、Q2直接绑定到了交换器X上。 第一个队列用绑定key橙色(orange)绑定,第二个队列有两个绑定,一个绑定key为黑色(black),另一个为绿色(green)。
在这种设置中,通过路由键橙色发布到交换器的消息将被路由到队列Q1。 带有黑色或绿色的路由键的消息将进入Q2。 所有其他消息将被丢弃。
在这里插入图片描述
在上面列子中,routingKey=”error”的消息发送Exchange后,Exchange会将消息路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果routingKey=”info”或routingKey=”warning”的消息发到Exchange,Exchange只会将消息路由到Queue2。 所有其他消息将被丢弃。

Topic

这种类型的Exchange的路由规则支持 binding key 和 routing key 的模糊匹配,会把消息路由到满足条件的Queue。 binding key 中可以存在两种特殊字符 *与 #,用于做模糊匹配,其中 * 用于匹配一个单词,# 用于匹配0个或多个单词,单词以符号“.”为分隔符。
在这里插入图片描述
以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

Headers

这种类型的Exchange不依赖于 routing key 与 binding key 的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。

RabbitMQ的工作模式

simple模式(即为最简单的收发模式)

在这里插入图片描述
生产者将消息发送到队列,消费者从队列中获取消息。
不足:有可能造成消息的丢失。

示例

导入RabbitMQ的客户端依赖
<dependency>
   <groupId>com.rabbitmq</groupId>
   <artifactId>amqp-client</artifactId>
   <version>3.4.1</version>
</dependency>
获取MQ的连接
package com.zpc.rabbitmq.util;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;

public class ConnectionUtil {

    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("localhost");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("testhost");
        factory.setUsername("admin");
        factory.setPassword("admin");
        // 通过工程获取连接
        Connection connection = factory.newConnection();
        return connection;
    }
}
生产者发送消息到队列
package com.zpc.rabbitmq.simple;

import com.zpc.rabbitmq.util.ConnectionUtil;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Send {

    private final static String QUEUE_NAME = "q_test_01";

    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();

        // 声明(创建)队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 消息内容
        String message = "Hello World!";
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        //关闭通道和连接
        channel.close();
        connection.close();
    }
}
管理工具中查看消息

在这里插入图片描述

消费者从队列中获取消息
package com.zpc.rabbitmq.simple;

import com.zpc.rabbitmq.util.ConnectionUtil;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

public class Recv {

    private final static String QUEUE_NAME = "q_test_01";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);

        // 监听队列
        channel.basicConsume(QUEUE_NAME, true, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
        }
    }
}

Work模式

在这里插入图片描述
消息放入队列,有多个消费者,消费者会竞争消息(理论上一个消息只能被一个消费者获取);
不足:高并发下某个消息可能会被多个消费者同时使用,可以设置一个开关(synchronized)。

示例

在这里插入图片描述
一个生产者、2个消费者。
一个消息只能被一个消费者获取。

消费者1
package com.zpc.rabbitmq.work;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.zpc.rabbitmq.util.ConnectionUtil;

public class Recv {

    private final static String QUEUE_NAME = "test_queue_work";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 同一时刻服务器只会发一条消息给消费者
        //channel.basicQos(1);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,false表示手动返回完成状态,true表示自动
        channel.basicConsume(QUEUE_NAME, true, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [y] Received '" + message + "'");
            //休眠
            Thread.sleep(10);
            // 返回确认状态,注释掉表示使用自动确认模式
            //channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}
消费者2
package com.zpc.rabbitmq.work;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.zpc.rabbitmq.util.ConnectionUtil;

public class Recv2 {

    private final static String QUEUE_NAME = "test_queue_work";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 同一时刻服务器只会发一条消息给消费者
        //channel.basicQos(1);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,false表示手动返回完成状态,true表示自动
        channel.basicConsume(QUEUE_NAME, true, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            // 休眠1秒
            Thread.sleep(1000);
            //下面这行注释掉表示使用自动确认模式
            //channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}
生产者

向队列中发送100条消息。

package com.zpc.rabbitmq.work;

import com.zpc.rabbitmq.util.ConnectionUtil;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Send {

    private final static String QUEUE_NAME = "test_queue_work";

    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        for (int i = 0; i < 100; i++) {
            // 消息内容
            String message = "" + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");

            Thread.sleep(i * 10);
        }

        channel.close();
        connection.close();
    }
}

publish/subscribe发布订阅

在这里插入图片描述

  1. 1个生产者,多个消费者;
  2. 每一个消费者都有自己的一个队列;
  3. 生产者没有将消息直接发送到队列,而是发送到了交换机,交换机将消息转发到绑定到交换机的每个队列;
  4. 每个队列都要绑定到交换机;
  5. 生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的(即每个绑定交换机的队列都将接收到消息)。
    注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费。
    使用的Fanout 类型的交换器:
    在这里插入图片描述
    Fanout 就是我们熟悉的广播模式或者订阅模式,给Fanout交换机发送消息,绑定了这个交换机的所有队列都收到这个消息。

Routing模式

使用Direct Exchange,明确指定路由key,与Topic模式的区别在于:Topic模式支持通配符,模糊匹配。
多个消息队列绑定到同一个路由Key上,Direct Exchange 交换机也会把消息分别发送到两个队列;也就是只有队列绑定的路由Key相同,都会收到消息,但是同一个队列理论上只有一个消费者可以消费到消息。
在这里插入图片描述
使用Direct 类型的交换器:
在这里插入图片描述

topic主题模式(路由模式的一种)支持通配符,模糊匹配

消息会发送到路由绑定的消息队列中,与Routing模式不同的是Topic模式支持通配符:
特殊字符 *与 #,用于做模糊匹配,其中 * 用于匹配一个单词,# 用于匹配0个或多个单词,单词以符号“.”为分隔符。
在这里插入图片描述
使用topic类型的交换器:
在这里插入图片描述

示例

首先对topic规则配置,这里使用两个队列(消费者)来演示。

  1. 配置队列,绑定交换机
package com.zpc.rabbitmq.topic;

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

@Configuration
public class TopicRabbitConfig {

    final static String message = "q_topic_message";
    final static String messages = "q_topic_messages";

    @Bean
    public Queue queueMessage() {
        return new Queue(TopicRabbitConfig.message);
    }

    @Bean
    public Queue queueMessages() {
        return new Queue(TopicRabbitConfig.messages);
    }

    /**
     * 声明一个Topic类型的交换机
     * @return
     */
    @Bean
    TopicExchange exchange() {
        return new TopicExchange("mybootexchange");
    }

    /**
     * 绑定Q到交换机,并且指定routingKey
     * @param queueMessage
     * @param exchange
     * @return
     */
    @Bean
    Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
    }

    @Bean
    Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) {
        return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");
    }
}
  1. 创建2个消费者
    q_topic_message 和q_topic_messages
package com.zpc.rabbitmq.topic;

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

@Component
@RabbitListener(queues = "q_topic_message")
public class Receiver1 {

    @RabbitHandler
    public void process(String hello) {
        System.out.println("Receiver1  : " + hello);
    }
}
package com.zpc.rabbitmq.topic;

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

@Component
@RabbitListener(queues = "q_topic_messages")
public class Receiver2 {

    @RabbitHandler
    public void process(String hello) {
        System.out.println("Receiver2 : " + hello);
    }
}
  1. 消息发送者(生产者)
package com.zpc.rabbitmq.topic;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MsgSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send1() {
        String context = "hi, i am message 1";
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("mybootexchange", "topic.message", context);
    }
    public void send2() {
        String context = "hi, i am messages 2";
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("mybootexchange", "topic.messages", context);
    }
}

send1方法会匹配到topic.#和topic.message,两个Receiver都可以收到消息,发送send2只有topic.#可以匹配所有只有Receiver2监听到消息。

  1. 测试
package com.zpc.rabbitmq.topic;

import org.junit.Test;
import org.junit.runner.RunWith;
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
public class RabbitTopicTest {

    @Autowired
    private MsgSender msgSender;

    @Test
    public void send1() throws Exception {
        msgSender.send1();
    }

    @Test
    public void send2() throws Exception {
        msgSender.send2();
    }
}

RPC模式

MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。
但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。
在这里插入图片描述
RabbitMQ中实现RPC的机制是:

  1. 客户端发送请求(消息)时,在消息的属性中(MessageProperties,在AMQP协议中定义了14中properties,这些属性会随着消息一起发送)设置两个值replyTo(一个Queue名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue中)和correlationId(此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败);
  2. 服务器端收到消息并处理;
  3. 服务器端处理完消息后,将生成一条应答消息到replyTo指定的Queue,同时带上correlationId属性
    客户端之前已订阅replyTo指定的Queue,从中收到服务器的应答消息后,根据其中的correlationId属性分析哪条请求被执行了,根据执行结果进行后续业务处理。

RabbitMQ 端口号解析

端口号解析

  1. 4369 (epmd), 25672 (Erlang distribution)
    Epmd 是 Erlang Port Mapper Daemon 的缩写,在 Erlang 集群中相当于 dns 的作用,绑定在4369端口上。
  2. 5672, 5671 (AMQP 0-9-1 without and with TLS)
    AMQP 是 Advanced Message Queuing Protocol 的缩写,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,专为面向消息的中间件设计。基于此协议的客户端与消息中间件之间可以传递消息,并不受客户端/中间件不同产品、不同的开发语言等条件的限制。Erlang 中的实现有 RabbitMQ 等。
  3. 15672 (if management plugin is enabled)
    通过 http://serverip:15672 访问 RabbitMQ 的 Web 管理界面,默认用户名密码都是 guest。(注意:RabbitMQ 3.0之前的版本默认端口是55672,下同)。
  4. 61613, 61614 (if STOMP is enabled)
    Stomp 是一个简单的消息文本协议,它的设计核心理念就是简单与可用性,官方文档,实践一下 Stomp 协议需要:
  • 一个支持 stomp 消息协议的 messaging server (譬如activemq,rabbitmq);
  • 一个终端(譬如linux shell);
  • 一些基本命令与操作(譬如nc,telnet)
  1. 1883, 8883 (if MQTT is enabled)
    MQTT 只是 IBM 推出的一个消息协议,基于 TCP/IP 的。两个 App 端发送和接收消息需要中间人,这个中间人就是消息服务器(比如ActiveMQ/RabbitMQ),三者通信协议就是 MQTT。
    可以通过配置RabbitMQ来使用其它端口。

默认用户访问

协商器创造了一个密码为guest的用户guest。未配置的客户端一般都会这些凭证。当访问localhost的时候这些凭证都会默认被使用,所以当从其它机器连接过来前你需要做点变动。访问控制文档里介绍了增加用户、删除用户、允许用户的远程访问等操作。

总结

  1. 生产者其实无需配置交换机、队列,只要知道rabbitMQ中的交换机名称、交换机类型、发送routeKey就可以发送消息了。
    @Test
    public void testTopic2() {
        rabbitTemplate.convertAndSend("testtopic1", "topic.woman", "test topic exchange 2");
    }
  1. 消费者对于需要交换机参与的接收模式,消费者需要先把消息队列和交换机绑定(如果rabbitMq中交换机、消息队列不存在还要先创建交换机和队列),根据队列名称接收消息,如果生产者把消息根据routingKey发送到了之前绑定的对应队列中,消费者即可接收到消息。
  2. 实际上,交换机、队列、绑定关系都是通过客户端对rabbitMQ进行操作,是要在rabbitMQ这个中间件建立收发关系(可以把rabbitMq看成快递公司),只要rabbitMQ已经存在收发关系(即使收到创建、绑定也一样),我们只要简单的指定发送到哪、从哪接收就可以收发消息了。

参考

RabbitMQ基础概念详细入门介绍
RabbitMQ使用教程(超详细)
RabbitMQ 端口号解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融极

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值