RabbitMQ

1.1 介绍

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

RabbitMQ官方地址:http://www.rabbitmq.com/

开发中消息队列通常有如下应用场景:

1、任务异步处理。

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

2、应用程序解耦合

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

3.程序削峰

市场上还有哪些消息队列?

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

为什么使用RabbitMQ呢?

1、使得简单,功能强大。

2、基于AMQP协议。

3、社区活跃,文档完善。

4、高并发性能好,这主要得益于Erlang语言。

5、Spring Boot默认已集成RabbitMQ

1.2  其它相关知识

AMQP 是什么?

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

官方:http://www.amqp.org/

2.1 下载安装

RabbitMQ由Erlang语言开发,Erlang语言用于并发及分布式系统的开发,在电信领域应用广泛,OTP(Open

Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件及工具库,安装RabbitMQ需

要安装Erlang/OTP,并保持版本匹配,如下图:

 RabbitMQ的下载地址:http://www.rabbitmq.com/download.html

1)下载erlang

地址如下:

http://erlang.org/download/otp_win64_20.3.exe

找到 otp_win64_20.3.exe,以管理员方式运行此文件,安装。

erlang安装完成需要配置erlang环境变量:

ERLANG_HOME=D:\Program Files\erl9.3 在path中添

加%ERLANG_HOME%\bin;

2)安装RabbitMQ

https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.3

找到 rabbitmq-server-3.7.3.exe,以管理员方式运行此文件,安装。

2.2 启动

注意:以管理员的身份进入

关闭360和杀毒软件

2.2.3 注意事项

1、安装erlangrabbitMQ以管理员身份运行。

2、当卸载重新安装时会出现RabbitMQ服务注册失败,此时需要进入注册表清理erlang

搜索RabbitMQErlSrv,将对应的项全部删除。

3、当前计算机时中午名称的修改登录的用户名

安装成功后会自动创建RabbitMQ服务并且启动。

1)从开始菜单启动RabbitMQ

完成在开始菜单找到RabbitMQ的菜单:

 

RabbitMQ Service-install :安装服务

RabbitMQ Service-remove 删除服务

RabbitMQ Service-start 启动

RabbitMQ Service-stop 启动

2)

  1. 如果没有开始菜单则进入安装目录下sbin目录手动启动:

 

规避了用户名为中文的问题。

最后是你会发现激活插件管理也会出现报错所以输入以下代码就可以激活

set RABBITMQ_BASE=D:\install3\rabbitmq\rabbitmq_server-3.7.8\data

1)安装并运行服务

rabbitmq-service.bat install 安装服务 rabbitmq-service.bat stop 停止服务 rabbitmq-service.bat start 启动服务

rabbitmq-plugins enable rabbitmq_management

2)安装管理插件

安装rabbitMQ的管理插件,方便在浏览器端管理RabbitMQ

管理员身份运行 rabbitmq-plugins.bat enable rabbitmq_management

3、启动成功 登录RabbitMQ

4、重新启动服务

进入浏览器,输入:http://localhost:15672

 初始账号和密码:guest/guest

 2.3 Hello World

按照官方教程(http://www.rabbitmq.com/getstarted.html) 测试hello world:

 2.2.1 搭建环境

1 )创建maven工程

创建生产者工程和消费者工程,分别加入RabbitMQ java client的依赖。

test-rabbitmq-producer:生产者工程

test-rabbitmq-consumer:消费者工程

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>4.0.3</version><!--此版本与spring boot 1.5.6版本匹配-->
</dependency>

 2.2.2 生产者

在生产者工程下的test中创建测试类如下:

public class Producer01 {
    //队列名称
    private static final String QUEUE = "helloworld";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = null;
        Channel channel = null;
        try
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器
            factory.setVirtualHost("/");            
//创建与RabbitMQ服务的TCP连接
            connection  = factory.newConnection();
            //创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
            channel = connection.createChannel();
            /**
             * 声明队列,如果Rabbit中没有此队列将自动创建
             * param1:队列名称
             * param2:是否持久化
             * param3:队列是否独占此连接
             * param4:队列不再使用时是否自动删除此队列
             * param5:队列参数
             */
            channel.queueDeclare(QUEUE, true, false, false, null);
            String message = "helloworld小明"+System.currentTimeMillis();
            /**
             * 消息发布方法
             * param1:Exchange的名称,如果没有指定,则使用Default Exchange
             * param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
             * param3:消息包含的属性
             * param4:消息体
             */
      /**
             * 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
             * 默认的交换机,routingKey等于队列名称
             */
            channel.basicPublish("", QUEUE, null, message.getBytes());
            System.out.println("Send Message is:'" + message + "'");
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            if(channel != null)
            {
                channel.close();
            }
            if(connection != null)
            {
                connection.close();
            }
        }
    }
}

2.2.3 消费者

在消费者工程下的test中创建测试类如下:

public class Consumer01 {
private static final String QUEUE = "helloworld";    
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        //设置MabbitMQ所在服务器的ip和端口
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE, true, false, false, null);
        //定义消费方法
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 消费者接收消息调用此方法
             * @param consumerTag 消费者的标签,在channel.basicConsume()去指定
             * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
             * @param properties
             * @param body
             * @throws IOException
      */
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body)
                    throws IOException {
                //交换机
                String exchange = envelope.getExchange();
                //路由key
                String routingKey = envelope.getRoutingKey();
                //消息id
                long deliveryTag = envelope.getDeliveryTag();
                //消息内容
                String msg = new String(body,"utf-8");
                System.out.println("receive message.." + msg);
            }
        };
        /**
         * 监听队列String queue, boolean autoAck,Consumer callback
         * 参数明细
         * 1、队列名称
         * 2、是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动回复
         * 3、消费消息的方法,消费者接收到消息后调用此方法
         */
        channel.basicConsume(QUEUE, true, consumer);
    }
}

2.2.4 总结

1、发送端操作流程

1)创建连接

2)创建通道

3)声明队列

4)发送消息

2、接收端

1)创建连接

2)创建通道

3)声明队列

4)监听队列

5)接收消息

6 )ack回复

3 工作模式

RabbitMQ有以下几种工作模式 :

1、Work queues

2、Publish/Subscribe

3、Routing

4、Topics

5、Header

6、RPC

3.1 Work queues

work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。

应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

测试:

1、使用入门程序,启动多个消费者。

2、生产者发送多个消息。

结果:

1、一条消息只会被一个消费者接收;

2、rabbit采用轮询的方式将消息是平均发送给消费者的;

3、消费者在处理完某条消息后,才会收到下一条消息。

3.2 Publish/subscribe 工作模式

发布订阅模式:

1、每个消费者监听自己的队列。

2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收

到消息

3.2.2 代码

案例:

用户通知,当用户充值成功或转账完成系统通知用户,通知方式有短信、邮件多种方法 。

1、生产者

声明Exchange_fanout_inform交换机。

声明两个队列并且绑定到此交换机,绑定时不需要指定routingkey

发送消息时不需要指定routingkey

 package com.zb.test.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer02_publish {
//队列名称
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) {
        Connection connection = null;
        Channel channel = null;
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            connection = factory.newConnection();
            channel = connection.createChannel();
            //声明交换机 String exchange, BuiltinExchangeType type
            /**
             * 参数明细
             * 1、交换机名称
             * 2、交换机类型,fanout、topic、direct、headers
             */
            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            //交换机和队列绑定String queue, String exchange, String routingKey
            /**
             * 参数明细
             * 1、队列名称
             * 2、交换机名称
             * 3、路由key
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
            //发送消息
            for (int i=0;i<10;i++){
                String message = "inform to user"+i;
    
                /**
             向交换机发送消息 
String exchange, String routingKey, BasicProperties props, byte[] body
                 * 参数明细
                 * 1、交换机名称,不指令使用默认交换机名称 Default Exchange
                  * 2、routingKey(路由key),根据key名称将消息转发到具体的队列,这里填写队列名称表示消息将发到此队列
                 * 3、消息属性
                 * 4、消息内容
                 */
                channel.basicPublish(EXCHANGE_FANOUT_INFORM, "", null, message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally{
            if(channel!=null){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2、邮件发送消费者

package com.zb.test.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer02_subscribe_email {
    //队列名称
    private static final String QUEUE_INFORM_EMAIL = "inform_queue_email";
    private static final String EXCHANGE_FANOUT_INFORM="inform_exchange_fanout";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
        //定义消费方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
         public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
                   long deliveryTag = envelope.getDeliveryTag();
                   String exchange = envelope.getExchange();
                   //消息内容
                   String message = new String(body, "utf-8");
                   System.out.println(message);
             }
        };
        channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
    }
}

按照上边的代码,编写邮件通知的消费代码。

3、短信发送消费者

参考上边的邮件发送消费者代码编写 

 3.2.3 测试

打开RabbitMQ的管理界面,观察交换机绑定情况:

使用生产者发送若干条消息,每条消息都转发到各各队列,每消费者都接收到了消息。 

 3.2.4 思考

1、publish/subscribe与work queues有什么区别。

区别:

1)work queues不用定义交换机,而publish/subscribe需要定义交换机。

2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。

3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑定到默认的交换机 。

相同点:

所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。

2、实质工作用什么 publish/subscribe还是work queues。

建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换

机。

 3.3 Routing 工作模式

路由模式:

1、每个消费者监听自己的队列,并且设置routingkey。

2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。

 3.3.2代码

1、生产者

声明exchange_routing_inform交换机。

声明两个队列并且绑定到此交换机,绑定时需要指定routingkey

发送消息时需要指定routingkey

package com.zb.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 Producer03_routing {
    //队列名称
    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";
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            connection = factory.newConnection();
            channel = connection.createChannel();
            channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
            channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
            //交换机和队列绑定String queue, String exchange, String routingKey
            /**
             * 参数明细
             * 1、队列名称
             * 2、交换机名称
             * 3、路由key
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL);
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_SMS);
            //发送邮件消息
            for (int i=0;i<10;i++){
                String message = "email inform to user"+i;
                 channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_EMAIL, null,
message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
            //发送短信消息
            for (int i=0;i<10;i++){
                String message = "sms inform to user"+i;
                channel.basicPublish(EXCHANGE_ROUTING_INFORM, QUEUE_INFORM_SMS, null,
message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally{
            if(channel!=null){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2、邮件发送消费者

package com.zb.test.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer03_routing_email {
    //队列名称
    private static final String QUEUE_INFORM_EMAIL = "inform_queue_email";
    private static final String EXCHANGE_ROUTING_INFORM="inform_exchange_routing";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
        channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,QUEUE_INFORM_EMAIL);
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
                long deliveryTag = envelope.getDeliveryTag();
                String exchange = envelope.getExchange();
                String message = new String(body, "utf-8");
                System.out.println(message);
            }
        };
        channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
    }
}

3 、短信发送消费者

参考邮件发送消费者的代码流程,编写短信通知的代码

3.3.3 测试

打开RabbitMQ的管理界面,观察交换机绑定情况:

 使用生产者发送若干条消息,交换机根据routingkey转发消息到指定的队列。

3.3.4 思考

1、Routing模式和Publish/subscibe有啥区别?

Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列。

3.4 Topics 工作模式

路由模式:

1、每个消费者监听自己的队列,并且设置带统配符的routingkey。

2、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。

3.4.2 代码 

案例:

根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种

通知类型都接收的则两种通知都有效。

1、生产者

声明交换机,指定topic类型:

/**
 * 声明交换机
 * param1:交换机名称
 * param2:交换机类型 四种交换机类型:direct、fanout、topic、headers
 */
 channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//Email通知
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email", null, message.getBytes());
//sms通知
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms", null, message.getBytes());
//两种都通知
channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms.email", null, message.getBytes());

完整代码:

package com.zb.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 Producer04_topics {
    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";

    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("127.0.0.1");
            factory.setPort(5672);
            factory.setUsername("guest");
            factory.setPassword("guest");
            factory.setVirtualHost("/");
            connection = factory.newConnection();
            channel = connection.createChannel();
           channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
            channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
      //发送邮件消息
            for (int i=0;i<10;i++){
                String message = "email inform to user"+i;
                channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.email", null,
message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
            //发送短信消息
            for (int i=0;i<10;i++){
                String message = "sms inform to user"+i;
                channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms", null,
message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
            //发送短信和邮件消息
            for (int i=0;i<10;i++){
                String message = "sms and email inform to user"+i;
                channel.basicPublish(EXCHANGE_TOPICS_INFORM, "inform.sms.email", null,
message.getBytes());
                System.out.println("Send Message is:'" + message + "'");
            }
} catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally{
if(channel!=null){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2、消费端

队列绑定交换机指定通配符:

统配符规则:

中间以“.”分隔。

符号#可以匹配多个词,符号*可以匹配一个词语。

//声明队列
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//声明交换机
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//绑定email通知队列
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,"inform.#.email.#");
//绑定sms通知队列
 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,"inform.#.sms.#");

3.4.3 测试

 使用生产者发送若干条消息,交换机根据routingkey统配符匹配并转发消息到指定的队列。

3.4.4思考 

1、本案例的需求使用Routing工作模式能否实现?

使用Routing模式也可以实现本案例,共设置三个 routingkey,分别是email、sms、all,email队列绑定email和

all,sms队列绑定sms和all,这样就可以实现上边案例的功能,实现过程比topics复杂。

Topic模式更多加强大,它可以实现Routing、publish/subscirbe模式的功能。

3.5 Header 模式

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

案例:

根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种

通知类型都接收的则两种通知都有效。

代码:

1)生产者

队列与交换机绑定的代码与之前不同,如下:


public class Producer {
    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_HEADER_INFORM="exchange_header_inform";

    public static void main(String[] args)throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, 
         BuiltinExchangeType.HEADERS);
        channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
        Map<String, Object> headers_email = new Hashtable<>();
        headers_email.put("inform_type", "email");
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADER_INFORM
,"",headers_email);
        for (int i = 0; i < 10; i++) {
            String msg= "email send i------"+i;
            AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
            builder.headers(headers_email);
            channel.basicPublish(EXCHANGE_HEADER_INFORM,"",
builder.build(),msg.getBytes());
        }
        channel.close();
        connection.close();
    }
}

2)发送邮件消费者

public class Consumer01 {
    private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    private static final String EXCHANGE_HEADER_INFORM="exchange_header_inform";

    public static void main(String[] args)throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_HEADER_INFORM, BuiltinExchangeType.HEADERS);
        channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
        Map<String, Object> headers_email = new Hashtable<>();
        headers_email.put("inform_type", "email");
        channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADER_INFORM,"",headers_email);
        DefaultConsumer defaultConsumer =new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body,"utf-8"));
            }
        };
        channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);

    }
}

3)测试

 

 

 

生产端:

public class Producer {
    public static void main(String[] args) throws Exception {
        String exchange = "confirm.exchange";
        String routingKey = "confirm.key";
        Connection con = MyConnection.getConnection();
        Channel channel = con.createChannel();
        channel.confirmSelect();
        String msg="hello rabbitmq send confirm send";
        channel.basicPublish(exchange,routingKey,null,msg.getBytes());
        channel.addConfirmListener(new ConfirmListener() {
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("返回确认");
            }
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("没有确认");
            }
        });
    }
}

 消费端:

public class Consumer {
    public static void main(String[] args) throws Exception {
        String exchange = "confirm.exchange";
        Connection con = MyConnection.getConnection();
        final Channel channel = con.createChannel();
        String queueName = "test_confirm_queue";
        String routingKey = "confirm.#";
        channel.exchangeDeclare(exchange, "topic", true, false, null);
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueBind(queueName, exchange, routingKey);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, "utf-8"));
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

 

 

生产者:

public class Producer {
    public static void main(String[] args) throws Exception {
        String exchange = "return.exchange";
        String routingKey = "return.key";
        String errorroutingKey = "abc.key";//错误的routingkey
        Connection con = MyConnection.getConnection();
        Channel channel = con.createChannel();
        channel.confirmSelect();
        String msg="hello rabbitmq send return send";
        channel.addReturnListener(new ReturnListener() {
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                System.err.println("replyCode"+replyCode);
                System.err.println("replyText"+replyText);
                System.err.println("exchange"+exchange);
                System.err.println("routingKey"+routingKey);
                System.err.println("basicProperties"+basicProperties);
            }
        });
//        channel.basicPublish(exchange,routingKey,true,null,msg.getBytes());
        channel.basicPublish(exchange,errorroutingKey,true,null,msg.getBytes());
    }
}

 消费者:

public class Consumer {
    public static void main(String[] args) throws Exception {
        String exchange = "return.exchange";
        Connection con = MyConnection.getConnection();
        final Channel channel = con.createChannel();
        String queueName = "test_return_queue";
        String routingKey = "return.#";
        channel.exchangeDeclare(exchange, "topic", true, false, null);
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueBind(queueName, exchange, routingKey);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, "utf-8"));
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

3.6 消费端限流

 

 

生产端:

public class Producer {
    public static void main(String[] args) throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        String exchange = "test_qos_exchange";
        String routingKey = "qos.save";

        for (int i = 0; i < 5; i++) {
            String msg = "Hello rabbitMQ Qos message:"+i;
            channel.basicPublish(exchange, routingKey, true, null, msg.getBytes());
        }
    }
}

 消费端:

public class Consumer {
    public static void main(String[] args)throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        final Channel channel = connection.createChannel();
        String exchange = "test_qos_exchange";
        String queueName="test_qos_queue";
        String routingKey="qos.#";
        channel.exchangeDeclare(exchange,"topic",true,false,null);
        channel.queueDeclare(queueName,true,false,false,null);
        channel.queueBind(queueName,exchange,routingKey);
        //1.限流方式,
        channel.basicQos(0,2,false);
        DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("===========服务端发送的信息===============");
                System.out.println("consumerTag;"+consumerTag);
                System.out.println("envelope;"+envelope);
                System.out.println("properties;"+properties);
                System.out.println("body;"+new String(body));
                //手动回复ack,告诉服务端, 以处理完当前的消息, 
//你可以继续发下一条消息了
                //测试程序演示,注释掉改代码, 检查consumer的控制台,
//只有一条消息信息,开打注释发现所有的消息一条一条的都接收到了
                //false :是否批量处理多条消息
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        //第一件事就是autoAck设置为false
        channel.basicConsume(queueName,false,defaultConsumer);
    }
}

3.7 消费端ACK与重回队列

 

生产端:

public class Producer {
    public static void main(String[] args) throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        String exchange = "test_ack_exchange";
        String routingKey = "ack.save";

        for (int i = 0; i < 5; i++) {
            String msg = "Hello rabbitMQ ACK message:" + i;
            Map<String, Object> header = new HashMap<String, Object>();
            header.put("num",i);
            AMQP.BasicProperties properties = new AMQP.BasicProperties()
                    .builder()
                    .deliveryMode(2)// 设置消息是否持久化,1: 非持久化 2:持久化
                    .contentEncoding("UTF-8")
                    .headers(header).build();

            channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
            //mandatory true时,交换器无法根据自动的类型和路由键找到一个符合条件的队列,那么RabbitMq会调用Basic.Ruturn命令将消息返回给生产都,为false时,出现上述情况消息被直接丢弃
            //mandatory 都是消息传递过程中,不可达目的地是,将消息返回给生产者的功能
        }
    }
}

 消费端:

public class Consumer {
    public static void main(String[] args)throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        final Channel channel = connection.createChannel();
        String exchange = "test_ack_exchange";
        String queueName="test_ack_queue";
        String routingKey="ack.#";
        channel.exchangeDeclare(exchange,"topic",true,false,null);
        channel.queueDeclare(queueName,true,false,false,null);
        channel.queueBind(queueName,exchange,routingKey);
        DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("===========服务端发送的信息===============");
                System.out.println("body;"+new String(body));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //检查consumer的控制台,发现map中的i=0的消息没有
//ack,consumer 一直能接收到producer的发过来的消息,
                //basicNack的第三个参数:
//true将消息继续放到队列的末尾在此发送, false 不在放到队列中
                if((Integer)properties.getHeaders().get("num")==0){
                   channel.basicNack(envelope.getDeliveryTag(),false,true);
               }else{
                   channel.basicAck(envelope.getDeliveryTag(),false);
               }
            }
        };
        //手动签收,必须要关闭autoAck= false
        channel.basicConsume(queueName,false,defaultConsumer);
    }
}

3.8 死信队列

 

 

 

 

生产者:

public class Producer {
    public static void main(String[] args) throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        String exchange = "test_dlx_exchange";
        String routingKey = "dlx.save";

        for (int i = 0; i < 1; i++) {
            //先启动producer 去,http://localhost:15672/上检查队列里面的信息,有一条消息没有消费
            //然后等10秒钟:因为下面expiration("10000")设置有效期时间为10秒,这个消息没有消费自动将消息转移到死信队列里面
            String msg = "Hello rabbitMQ DLX message:" + i;
            AMQP.BasicProperties properties = new AMQP.BasicProperties()
                    .builder()
                    .deliveryMode(2)
                    .contentEncoding("UTF-8")
                    .expiration("10000")
                    .build();
            channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
        }
    }
}

 消费者:

public class Consumer {
    public static void main(String[] args)throws Exception {
        Connection connection = MyConnectionFactory.getConnection();
        final Channel channel = connection.createChannel();
        //这就是一个普通的交换机和队列以及路由
        String exchange = "test_dlx_exchange";
        String queueName="test_dlx_queue";
        String routingKey="dlx.#";
        channel.exchangeDeclare(exchange,"topic",true,false,null);

        Map<String ,Object> agruments = new HashMap<String, Object>();
        agruments.put("x-dead-letter-exchange","dlx.exchange");
        //这个agruments属性,要设置到声明队列上
        channel.queueDeclare(queueName,true,false,false,agruments);
        channel.queueBind(queueName,exchange,routingKey);

        //要进行死信队列的声明
        channel.exchangeDeclare("dlx.exchange","topic",true,false,null);
        channel.queueDeclare("dlx.queue",true,false,false,null);
        channel.queueBind("dlx.queue","dlx.exchange","#");

        DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("===========服务端发送的信息===============");
                System.out.println("consumerTag;"+consumerTag);
                System.out.println("envelope;"+envelope);
                System.out.println("properties;"+properties);
                System.out.println("body;"+new String(body));
            }
        };
        channel.basicConsume(queueName,true,defaultConsumer);
    }
}

4 Spring 整合RibbitMQ

4.1  搭建SpringBoot环境

我们选择基于Spring-Rabbit去操作RabbitMQ

https://github.com/spring-projects/spring-amqp

使用 spring-boot-starter-amqp会自动添加spring-rabbit依赖,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zb</groupId>
    <artifactId>mq_boot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

配置类

@Configuration
public class RabbitConfig {
    public static final String QUEUE_EMAIL="boot_email";
    public static final String QUEUE_SMS="boot_sms";
    public static final String EXCHANGE_TOPIC_INFORM="exchange_topic_boot";

    @Bean(EXCHANGE_TOPIC_INFORM)
    public Exchange createExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_INFORM).durable(true).build();
    }

    @Bean(QUEUE_EMAIL)
    public Queue createEmailQueue(){
        Queue queue =new Queue(QUEUE_EMAIL);
        return queue;
    }

    @Bean(QUEUE_SMS)
    public Queue createSmsQueue(){
        Queue queue =new Queue(QUEUE_SMS);
        return queue;
    }

    @Bean
    public Binding bindingEmail(@Qualifier(EXCHANGE_TOPIC_INFORM) Exchange exchange ,@Qualifier(QUEUE_EMAIL) Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("inform.#.email.#").noargs();
    }

    @Bean
    public Binding bindingSms(@Qualifier(EXCHANGE_TOPIC_INFORM) Exchange exchange ,@Qualifier(QUEUE_SMS) Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("inform.#.sms.#").noargs();
    }
}

消息发送者:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MqBootApplicationTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSend() {
        String msg = "send email and sms message";
        rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_TOPIC_INFORM, "inform.email.sms", msg);
    }
}

消息接收者:

@Service
public class MessageService {
    @RabbitListener(queues = RabbitConfig.QUEUE_EMAIL)
    public void reviceMessageEmail(String msg, Message message, Channel channel){
        System.out.println("email------------"+msg);
    }
    @RabbitListener(queues = RabbitConfig.QUEUE_SMS)
    public void reviceMessageSMS(String msg, Message message, Channel channel){
        System.out.println("sms------------"+msg);
    }
}

 

4.2 配置 

配置连接rabbitmq的参数

1、配置application.yml

配置连接rabbitmq的参数

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true

4.3 生产端

使用RarbbitTemplate发送消息


import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class RabbitSender {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    final RabbitTemplate.ConfirmCallback confirmCallback = 
new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            System.err.println("correlationData;" + correlationData);
            System.err.println("ack:" + ack);
            if (!ack) {
                System.out.println("异常处理,进行人工补偿....");
            }else{
                System.out.println("发送成功, 执行本地的sql信息的修改");
            }
        }
    };

    final RabbitTemplate.ReturnCallback returnCallback = 
new RabbitTemplate.ReturnCallback() {
        @Override
        public void returnedMessage(org.springframework.amqp.core.Message message, 
int replyCode, String replyText, 
String exchange, String routingKey) {
            System.err.println("exchange:" + exchange);
            System.err.println("replyCode:" + replyCode);
            System.err.println("replyText:" + replyText);
            System.err.println("routingKey:" + routingKey);
        }
    };

    public void send(Object message, Map<String, Object> prop) {
        MessageHeaders mhs = new MessageHeaders(prop);
        Message msg = MessageBuilder.createMessage(message, mhs);
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setReturnCallback(returnCallback);
        CorrelationData correlationData 
=new CorrelationData(System.currentTimeMillis()+"");
        //程序测试一个不存在的routigkey ,检查控制台上 ,
//看到returnCallback的打印输出
        rabbitTemplate.
convertAndSend("exchange-1", "springboot.hello", msg,correlationData);
        try {
            //,当测试方法结束,rabbitmq相关的资源也就关闭了,
//虽然我们的消息发送出去,但异步的ConfirmCallback却
//由于资源关闭而出现了上面的问题ack一直都是false
            //所以在测试方法等待一段时间即可
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类

@SpringBootApplication
public class MqBootApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run 
= SpringApplication.run(MqBootApplication.class, args);
        RabbitSender bean = run.getBean(RabbitSender.class);
        Map<String,Object> prop = new HashMap<>();
        prop.put("number","123456");
        prop.put("send_time",new Date());
        bean.send("hello rabbit for boot ",prop);
    }
}

4.4 消费端

创建消费端工程,添加依赖:

属性配置

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=1
spring.rabbitmq.listener.simple.max-concurrency=5

使用@RabbitListener注解监听队列

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import java.io.IOException;

@Service
public class MessageService {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "queue-1", durable = "true"),
            exchange = @Exchange(value = "exchange-1", durable = "true",
                    type = "topic",
                    ignoreDeclarationExceptions = "true"),
            key = "springboot.*"
    ))
    @RabbitHandler
    public void reviceMsg( Message message, Channel channel) {
        System.out.println("消息信息:" + message.getPayload());
        Long tag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
        try {
            channel.basicAck(tag, false);
//            channel.basicNack(tag,false,true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试程序

@SpringBootApplication
public class MqBootConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(MqBootConsumerApplication.class, args);
    }
}

4.5 RabbitMQ消息确认机制之Confirm模式总结 

在RabbitMQ中,消息确认主要有生产者发送确认和消费者接收确认:

生产者发送确认:指生产者发送消息后到RabbitMQ服务器,如果RabbitMQ服务器收到消息,则会给我们生产者一个应答,用于告诉生产者该条消息已经成功到达RabbitMQ服务器中。

5 SpringBoot使用RabbitMQ延时队列

延时队列

延时队列的使用场景:

1.订单业务:在电商中,用户下单后30分钟后未付款则取消订单。

2.短信通知:用户下单并付款后,1分钟后发短信给用户。

延时队列实现思路

AMQP协议和RabbitMQ队列本身没有直接支持延迟队列功能,但是我们可以通过RabbitMQ的两个特性来曲线实现延迟队列:

特性一:Time To Live(TTL)

RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)
RabbitMQ针对队列中的消息过期时间有两种方法可以设置。
A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
B: 对消息进行单独设置,每条消息TTL可以不同。

如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter

特性二:Dead Letter Exchanges(DLX)

RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchangex-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送队列出现dead letter的情况有:
消息或者队列的TTL过期队列达到最大长度
消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false

1.具体编码实现

@Configuration
public class DelayRabbitConfig {
    /**
     * 延迟队列 TTL 名称
     */
    private static final String ORDER_DELAY_QUEUE = "user.order.delay.queue";
    /**
     * DLX,dead letter发送到的 exchange
     * 延时消息就是发送到该交换机的
     */
    public static final String ORDER_DELAY_EXCHANGE = "user.order.delay.exchange";
    /**
     * routing key 名称
     * 具体消息发送在该 routingKey 的
     */
    public static final String ORDER_DELAY_ROUTING_KEY = "order_delay";
    public static final String ORDER_QUEUE_NAME = "user.order.queue";
    public static final String ORDER_EXCHANGE_NAME = "user.order.exchange";
    public static final String ORDER_ROUTING_KEY = "order";
    /**
     * 延迟队列配置
     * <p>
     * 1、params.put("x-message-ttl", 5 * 1000);
     * 第一种方式是直接设置 Queue 延迟时间 但如果直接给队列设置过期时间,这种做法不是很灵活,(当然二者是兼容的,默认是时间小的优先)
     * 2、rabbitTemplate.convertAndSend(book, message -> {
     * message.getMessageProperties().setExpiration(2 * 1000 + "");
     * return message;
     * });
     * 第二种就是每次发送消息动态设置延迟时间,这样我们可以灵活控制
     **/
    @Bean
    public Queue delayOrderQueue() {
        Map<String, Object> params = new HashMap<>();
        // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
        params.put("x-dead-letter-exchange", ORDER_EXCHANGE_NAME);
        // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
        params.put("x-dead-letter-routing-key", ORDER_ROUTING_KEY);
        return new Queue(ORDER_DELAY_QUEUE, true, false, false, params);
    }
    /**
     * 需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。
     * 这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,
     * 不会转发dog.puppy,也不会转发dog.guard,只会转发dog。
     * @return DirectExchange
     */
    @Bean
    public DirectExchange orderDelayExchange() {
        return new DirectExchange(ORDER_DELAY_EXCHANGE);
    }
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(delayOrderQueue()).to(orderDelayExchange()).with(ORDER_DELAY_ROUTING_KEY);
    }
 
    @Bean
    public Queue orderQueue() {
        return new Queue(ORDER_QUEUE_NAME, true);
    }
    /**
     * 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。
     * 符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。
     **/
    @Bean
    public TopicExchange orderTopicExchange() {
        return new TopicExchange(ORDER_EXCHANGE_NAME);
    }
 
    @Bean
    public Binding orderBinding() {
        // TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
        return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY);
    }
}

2.创建一个Order实体类

public class Order implements Serializable {
    private static final long serialVersionUID = -2221214252163879885L;
    private String orderId; // 订单id
    private Integer orderStatus; // 订单状态 0:未支付,1:已支付,2:订单已取消
    private String orderName; // 订单名字
}

 3.接收者

@Component
public class DelayReceiver {
    @RabbitListener(queues = {DelayRabbitConfig.ORDER_QUEUE_NAME})
    public void orderDelayQueue(Order order, Message message, Channel channel) {
        System.out.println("###########################################");
        System.out.println("【orderDelayQueue 监听的消息】 - 【消费时间】 - [{}]- 【订单内容】 - [{}]" + new Date() + order.toString());
        if (order.getOrderStatus() == 0) {
            order.setOrderStatus(2);
            System.out.println("【该订单未支付,取消订单】" + order.toString());
        } else if (order.getOrderStatus() == 1) {
            System.out.println("【该订单已完成支付】");
        } else if (order.getOrderStatus() == 2) {
            System.out.println("【该订单已取消】");
        }
        System.out.println("###########################################");
    }
}

4.发送者

@Component
public class DelaySender {
 
    @Autowired
    private AmqpTemplate amqpTemplate;
 
    public void sendDelay(Order order) {
        System.out.println("【订单生成时间】" + new Date().toString() +"【1分钟后检查订单是否已经支付】" + order.toString() );
       this.amqpTemplate.convertAndSend(DelayRabbitConfig.ORDER_DELAY_EXCHANGE, DelayRabbitConfig.ORDER_DELAY_ROUTING_KEY, order, message -> {
            // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间
            message.getMessageProperties().setExpiration(1 * 1000 * 60 + "");
            return message;
        });
    }
}

测试

public static void main(String[] args) {

    ConfigurableApplicationContext run = SpringApplication.run(MqBootApplication.class, args);
    DelaySender delaySender = run.getBean(DelaySender.class);
    Order order1 = new Order();
    order1.setOrderStatus(0);
    order1.setOrderId("123456");
    order1.setOrderName("小米6");
    Order order2 = new Order();
    order2.setOrderStatus(1);
    order2.setOrderId("456789");
    order2.setOrderName("小米8");
    delaySender.sendDelay(order1);
    delaySender.sendDelay(order2);
}

 

 

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值