RabbitMQ笔记--【Foam番茄】

RabbitMQ

1.介绍

MQ全称为Message Queue,即消息队列,RabbitMQ是由erlong语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。

RabbitMQ官方地址:点击跳转

RabbitMQ应用场景

1.任务异步处理

将不需要同步处理的并且耗时长的操作由消息队列通知接收方进行异步处理。提高了应用程序的响应时间。

2.应用程序解耦

MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦。

市场上还有哪些消息队列

ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ,Redis

为什么使用RabbitMQ
  • 使用简单,功能强大
  • 基于AMQP协议
  • 社区活跃,文档完整
  • 高并发性能好,这主要得益于Erlang语言
  • Spring Boot默认已集成RabbitMQ

AMQP

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

总结:AMQP是一套公开的消息队列协议,最早在03年被提出,它旨在从协议层定义消息通信数据的标准格式,为的就是解决MQ市场协议不统一的问题.

JMS

JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

总结:JMS是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信标准,类似于java的jdbc,java中只要遵循JMS标准的应用程序都可以实现通信

它和AMQP有什么不同

JMS是java专属的应用程序消息服务标准,他是在API层定义标准,而AMQP是在协议层定义标准,是跨语言的

2.RabbitMQ的工作原理

RabbitMQ笔记--【Foam番茄】_第1张图片

上图是RabbitMQ的基本结构,主要是三部分:生产者,MQ,消费者

组成部分说明

Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue

Exchange:消息队列交换机,按一定的规则将消息转发到某个队列,对消息进行过滤

Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方

Producer:消息生产者,即生产方客户端,生产方客户端将消费发送到MQ

Consumer:消息消费者,即消费方客户端,接收MQ转发的消息

消息发布流程

1.发送消息

  • 生产者和Broker建立TCP连接
  • 生产者和Broker建立通道Channel
  • 生产者将信息发送到Broker并用Exchange进行转发
  • Exchange将消息转发到指定的Queue

2.接收消息

  • 消费者和Broker建立TCP连接
  • 消费者和Broker建立通道Channel
  • 消费者监听指定的Queue
  • 当有消息到达消费者监听的Queue时,Broker将消息推送给消费者
  • 消费者接收到消息

3.Hello World

在这里插入图片描述

导入所需包

<dependencies>
    
    
    <dependency>
        <groupId>com.rabbitmqgroupId>
        <artifactId>amqp-clientartifactId>
        <version>4.0.3version>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-loggingartifactId>
    dependency>
dependencies>

生产者

package com.foam.test.rabbitmq;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * rabbitMQ的入门程序,生产者
 */
public class Producer {
    // 队列
    private static  final  String QUEUE="HELLOWORLD";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        Channel channel = null;
        try {
            connection=connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            channel=connection.createChannel();
            // 5.声明队列 如果队列在MQ没有则要创建
            // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments
            /**
             * 参数解析
             * 1.队列名称
             * 2.是否持久化,MQ重启后队列还在
             * 3.是否独占连接,队列只允许该连接中访问,如果connection连接关闭后,这个队列直接删除了,如果将此参数设置为true可以用于临时队列创建
             * 4.自动删除,该连接不再使用就会自动删除,如果此参数和exclusive同时设置为true,就可以实现临时队列
             * 5.参数,可以设置一个队列的扩展参数,比如:设置存活时间,官方有很多扩展参数
             */
            channel.queueDeclare(QUEUE,true,false,false,null);

            // 6.发送消息
            // String exchange, String routingKey, BasicProperties props, byte[] body
            /**
             * 参数解析
             * 1.交换机,如果不指定将使用MQ默认交换机
             * 2.路由key,交换机根据路由key来将消息发送到指定队列,如果使用默认交换机,那么routeKey要设置队列名称
             * 3.消息属性,可以扩展
             * 4.消息内容
             */
            // 消息内容
            String message="hello world!I'm Foam番茄";
            channel.basicPublish("",QUEUE,null,message.getBytes());
            System.out.println("发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 关闭连接
            // 先关闭通道
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

// 消费者
public class Consumer {// 队列
    private static final String QUEUE = "HELLOWORLD";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE,true,false,false,null);
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

注意 : 生产者发送完毕后要及时关闭连接,先关闭channel再关闭connection,消费者不需要关闭链接,因为消费者要实时监听MQ以获取最新消息

4.工作模式

RabbitMQ有以下几种工作模式

1.Work queues 工作队列

在这里插入图片描述

work queues 与入门程序相比,多了一个消费者,两个消费端同时消费一个队列中的消息,特点如下

  • 一个生产者把消息发给一个队列
  • 多个消费者共同监听队列
  • 消息只会被消费一次
  • rabbit采用轮询的方式将消息平均发送给消费者

应用场景

对于复杂的运算,可以让多个服务端接不同的任务,减小单个服务器压力(资源调度),抢红包

2.Publish/Subscribe 发布订阅

在这里插入图片描述

发布订阅模式

  • 一个生产者将消息发给交换机
  • 与交换机绑定有多个队列,每个消费者监听自己各自的队列
  • 生产者发送消息给交换机,交换机将消息推送到所有队列,消费者拿取自己监听的队列消息
  • 如果消息发给没有绑定队列的交换机上,消息就会丢失

Publish/Subscribe和Work Queues有什么区别?

1.publish/subscribe可以定义一个交换机绑定多个队列,一个消息发送多个队列

2.work queues无需定义交换机,用系统默认的交换机,一个消息只能发送一个队列

3.publish/subscribe比work queues的功能更加强大,publicsh/subscribe也可以将多个消费者监听同一个队列实现work queues的功能

应用场景

邮件群发,群聊天,广播

代码实现

消费者1

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerEmail {

    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
    private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);

            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            // 交换机和队列绑定
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

消费者2

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerSMS {
    private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";
    private static final String QUEUE_INFORM_SMS="queue_inform_sms";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);

            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            // 交换机和队列绑定
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE_INFORM_SMS,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

生产者

package com.foam.test.rabbitmq;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;
// 消息订阅
public class ProducerPublish {
        // 队列
        private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
        private static final String QUEUE_INFORM_SMS="queue_inform_sms";
        private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";

        public static void main(String[] args) {
            // 1.通过一个连接工厂和MQ建立连接
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
            connectionFactory.setVirtualHost("/");
            // 3.建立新连接
            Connection connection = null;
            Channel channel = null;
            try {
                connection=connectionFactory.newConnection();
                // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
                channel=connection.createChannel();
                // 5.声明队列 如果队列在MQ没有则要创建
                // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments
                /**
                 * 参数解析
                 * 1.队列名称
                 * 2.是否持久化,MQ重启后队列还在
                 * 3.是否独占连接,队列只允许该连接中访问,如果connection连接关闭后,这个队列直接删除了,如果将此参数设置为true可以用于临时队列创建
                 * 4.自动删除,该连接不再使用就会自动删除,如果此参数和exclusive同时设置为true,就可以实现临时队列
                 * 5.参数,可以设置一个队列的扩展参数,比如:设置存活时间,官方有很多扩展参数
                 */
                channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
                channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
                // 声明一个交换机
                // String exchange, BuiltinExchangeType type
                /**
                 * 参数解析
                 * 1.交换机的名称
                 * 2.交换机的类型
                 * fanout:对应rabbit的工作模式是 publish/subscribe
                 * direct:对应的Routing工作模式(路由模式)
                 * topics:通配符模式(主题模式)
                 * headers:hearders工作模式(转发器)
                 */
                channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
                // 交换机和队列绑定
                // String destination, String source, String routingKey
                /**
                 * 参数解析
                 * 1.队列名称
                 * 2.交换机名称
                 * 3.路由key,在发布订阅模式中,设置为空字符串
                 */
                channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
                channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
                // 6.发送消息
                // String exchange, String routingKey, BasicProperties props, byte[] body
                /**
                 * 参数解析
                 * 1.交换机,如果不指定将使用MQ默认交换机
                 * 2.路由key,交换机根据路由key来将消息发送到指定队列,如果使用默认交换机,那么routeKey要设置队列名称
                 * 3.消息属性,可以扩展
                 * 4.消息内容
                 */
                for (int i = 0; i < 10; i++) {
                    // 消息内容
                    String message="Foam番茄给您发消息了"+i;
                    channel.basicPublish(EXCHANGE_FANOUT_INFORM,"",null,message.getBytes());
                }

                System.out.println("发送成功");
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            } finally {
                // 关闭连接
                // 先关闭通道
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

}

3.Routing 路由

在这里插入图片描述

路由模式:

  • 一个交换机绑定多个队列,每个队列设置routingkey,并且一个队列可以设置多个routingkey
  • 每个消费者监听自己的队列
  • 生产者发送消息给exchange,exchange根据routingkey对比将消息推送到符合的队列

Routing和Publish/Subscribe区别

1.Publish/Subscribe模式在绑定交换机的时候不需要设置Routingkey,消息会发送到每个绑定交换机的队列

2.Routing通过key比对来分别发送给相同key的队列

所以Routing比Publish/Subscribe更加强大,它包含了后者所有的功能

应用场景

error通知,EXCEPTION,错误通知的功能,客户通知,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误

代码实现

生产者

package com.foam.test.rabbitmq;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

// 路由工作模式
public class ProducerRouting {
    // 队列
    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
    private static final String QUEUE_INFORM_SMS="queue_inform_sms";
    private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
    private static final String ROUTINGKEY_EMAIL="inform_email";
    private static final String ROUTINGKEY_SMS="inform_sms";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        Channel channel = null;
        try {
            connection=connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            channel=connection.createChannel();
            // 5.声明队列 如果队列在MQ没有则要创建
            // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments
            /**
             * 参数解析
             * 1.队列名称
             * 2.是否持久化,MQ重启后队列还在
             * 3.是否独占连接,队列只允许该连接中访问,如果connection连接关闭后,这个队列直接删除了,如果将此参数设置为true可以用于临时队列创建
             * 4.自动删除,该连接不再使用就会自动删除,如果此参数和exclusive同时设置为true,就可以实现临时队列
             * 5.参数,可以设置一个队列的扩展参数,比如:设置存活时间,官方有很多扩展参数
             */
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
            channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
            // 声明一个交换机
            // String exchange, BuiltinExchangeType type
            /**
             * 参数解析
             * 1.交换机的名称
             * 2.交换机的类型
             * fanout:对应rabbit的工作模式是 publish/subscribe
             * direct:对应的Routing工作模式(路由模式)
             * topics:通配符模式(主题模式)
             * headers:hearders工作模式(转发器)
             */
            channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
            // 交换机和队列绑定
            // String destination, String source, String routingKey
            /**
             * 参数解析
             * 1.队列名称
             * 2.交换机名称
             * 3.路由key,在发布订阅模式中,设置为空字符串
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL);
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS);
            // 6.发送消息
            // String exchange, String routingKey, BasicProperties props, byte[] body
            /**
             * 参数解析
             * 1.交换机,如果不指定将使用MQ默认交换机
             * 2.路由key,交换机根据路由key来将消息发送到指定队列,如果使用默认交换机,那么routeKey要设置队列名称
             * 3.消息属性,可以扩展
             * 4.消息内容
             */
            for (int i = 0; i < 10; i++) {
                // 消息内容
                String message="Foam番茄给您发消息了"+i;
                // 发送消息的时候要指定routingkey
                channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL,null,message.getBytes());
            }

            System.out.println("发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 关闭连接
            // 先关闭通道
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者1

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerEmailRouting {

    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
    private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
    private static final String ROUTINGKEY_EMAIL="inform_email";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);

            channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
            // 交换机和队列绑定
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL);
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

消费者2

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerSMSRouting {

    private static final String QUEUE_INFORM_SMS="queue_inform_sms";
    private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
    private static final String ROUTINGKEY_SMS="inform_sms";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);

            channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
            // 交换机和队列绑定
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS);
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE_INFORM_SMS,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

4.Topics 通配符

RabbitMQ笔记--【Foam番茄】_第2张图片

路由模式:

1.一个交换机可以绑定多个队列,每个队列可以设置一个或多个带通配符的RoutingKey

2.生产者将消息发送给交换机,交换机把符合通配符的key发送到指定的队列

Topics与Routing的区别

Topics和Routing的基本原理相同,即:生产者将消息发送给交换机,交换机根据RoutingKey将消息转发与RoutingKey匹配的队列

不同之处是:RoutingKey的匹配方式,RoutingKey是全等匹配,Topics是通配符匹配

*(星号)仅代表一个单词,如inform.#,可以匹配inform.sms,inform.email,inform.email.sms
#(井号)代表任意个单词,如inform.*,可以匹配inform.sms,inform.email

代码实现

生产者

package com.foam.test.rabbitmq;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

// 通配符模式
public class ProducerTopics {
    // 队列
    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
    private static final String QUEUE_INFORM_SMS="queue_inform_sms";
    private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
    private static final String ROUTINGKEY_EMAIL="inform.#.email.#";
    private static final String ROUTINGKEY_SMS="inform.#.sms.#";
    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        Channel channel = null;
        try {
            connection=connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            channel=connection.createChannel();
            // 5.声明队列 如果队列在MQ没有则要创建
            // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments
            /**
             * 参数解析
             * 1.队列名称
             * 2.是否持久化,MQ重启后队列还在
             * 3.是否独占连接,队列只允许该连接中访问,如果connection连接关闭后,这个队列直接删除了,如果将此参数设置为true可以用于临时队列创建
             * 4.自动删除,该连接不再使用就会自动删除,如果此参数和exclusive同时设置为true,就可以实现临时队列
             * 5.参数,可以设置一个队列的扩展参数,比如:设置存活时间,官方有很多扩展参数
             */
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
            channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
            // 声明一个交换机
            // String exchange, BuiltinExchangeType type
            /**
             * 参数解析
             * 1.交换机的名称
             * 2.交换机的类型
             * fanout:对应rabbit的工作模式是 publish/subscribe
             * direct:对应的Routing工作模式(路由模式)
             * topics:通配符模式(主题模式)
             * headers:hearders工作模式(转发器)
             */
            channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
            // 交换机和队列绑定
            // String destination, String source, String routingKey
            /**
             * 参数解析
             * 1.队列名称
             * 2.交换机名称
             * 3.路由key,在发布订阅模式中,设置为空字符串
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
            // 6.发送消息
            // String exchange, String routingKey, BasicProperties props, byte[] body
            /**
             * 参数解析
             * 1.交换机,如果不指定将使用MQ默认交换机
             * 2.路由key,交换机根据路由key来将消息发送到指定队列,如果使用默认交换机,那么routeKey要设置队列名称
             * 3.消息属性,可以扩展
             * 4.消息内容
             */
            for (int i = 0; i < 10; i++) {
                // 消息内容
                String message="Foam番茄给您发消息了"+i;
                // 发送消息的时候要指定routingkey
                channel.basicPublish(EXCHANGE_TOPICS_INFORM,"inform.email.sms",null,message.getBytes());
            }

            System.out.println("发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 关闭连接
            // 先关闭通道
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者1

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerEmailTopics {

    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
    private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
    private static final String ROUTINGKEY_EMAIL="inform.#.email.#";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);

            channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
            // 交换机和队列绑定
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

消费者2

package com.foam.test.rabbit;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerSMSTopics {

    private static final String QUEUE_INFORM_SMS="queue_inform_sms";
    private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
    private static final String ROUTINGKEY_SMS="inform.#.sms.#";

    public static void main(String[] args) {
        // 1.通过一个连接工厂和MQ建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 2.设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
        connectionFactory.setVirtualHost("/");
        // 3.建立新连接
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
            // 4.创建会话通道,生产者和消费者所有的通信都在channel中完成
            Channel channel = connection.createChannel();
            // 5.声明队列
            channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);

            channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
            // 交换机和队列绑定
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
            // 6.消费者调用方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 当接收到消息后,此方法将被调用
                 * @param consumerTag 消费者标签,用来标示消费者的,可以在监听队列设置channel.basicConsume
                 * @param envelope 信封,可以获取消息id,交换机,路由key
                 * @param properties  消息参数
                 * @param body  消息内容
                 * @throws IOException
                 */
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    // 消息id,MQ在channel中用来表示消费的id,可以用于消息已接收
                    long deliveryTag = envelope.getDeliveryTag();
                    // 交换机获取
                    String exchange = envelope.getExchange();
                    // 消息内容
                    String message=new String(body,"UTF-8");
                    System.out.println("获取到"+exchange+"消息:"+message);
                }
            };
            // 7.获取结果
            // String queue, boolean autoAck, Consumer callback
            /**
             * 参数解析
             * 1.队列名称
             * 2.自动回复,当消费者接收到消息后告诉MQ消息已经接收,如果为true自动回复,如果为false要通过编程实现回复
             * 3.消费方法,当消费者接收到消息就执行的方法
             */
            channel.basicConsume(QUEUE_INFORM_SMS,true,defaultConsumer);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

    }
}

5.Header Header转发器

header模式与routing不同的地方在于,header模式取消routingkey,使用header中的key/value(键值对)匹配队列

RabbitMQ笔记--【Foam番茄】_第3张图片

RabbitMQ笔记--【Foam番茄】_第4张图片

6.RPC 远程过程调用

这里写图片描述

RPC即客户端远程调用服务端的方法,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:

1.客户端既是生产者也是消费者,向RPC请求队列发送RPC消息,同时监听RPC响应队列

2.服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果

3.服务端将RPC方法的结果发送到RPC响应队列

在RabbitMQ中RPC的实现也是很简单高效的,现在我们的客户端、服务端都是消息发布者与消息接收者。

1.首先客户端通过RPC向服务端发出请求

2.我这里有一堆东西需要你给我处理一下,correlation_id:这是我的请求标识,erply_to:你处理完过后把结果返回到这个队列中。

3.服务端拿到了请求,开始处理并返回

4.correlation_id:这是你的请求标识 ,原封不动的给你。 这时候客户端用自己的correlation_id与服务端返回的id进行对比。是我的,就接收。

你可能感兴趣的:(RabbitMQ,rabbitmq,队列,java,网络,中间件)