RabbitMQ

一、Rabbit核心组成部分

1、核心概念

在这里插入图片描述

Server:又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server。
Connection:连接,应用程序与Broker的网络连接 TCP/IP/ 三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Queue:队列:也成为Message Queue,消息队列,保存消息并将它们转发给消费者。

2、RabbitMQ的运行流程

在这里插入图片描述

3、RabbitMQ支持消息的模式

3.1 simple模式
  • 简单模式 Simple
    在这里插入图片描述
3.2 fanout模式
  • 发布订阅模式 fanout
    • 类型:fanout
    • 特点:fanout—发布与订阅模式,是一种广播机制,它是没有路由key的模式。

在这里插入图片描述

3.3 direct模式
  • 路由模式 direct
    • 类型:direct
    • 特点:Direct模式是fanout模式上的一种叠加,增加了路由RoutingKey的模式。在这里插入图片描述
      在这里插入图片描述
      RabbitMQ中默认交换机就是direct模式
3.4 topic模式
  • 主题模式 Topic
    • 类型:topic
    • 特点:Topic模式是direct模式上的一种叠加,增加了模糊路由RoutingKey的模式。
      在这里插入图片描述
3.5 work模式
  • 工作模式 Work
    当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?
    主要有两种模式:
    1、轮询模式的分发:一个消费者一条,按均分配;
    2、公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;
    在这里插入图片描述
3.6 headers模式
  • 参数模式

二、代码讲解各种模式

1、Java整合RabbitMQ

1.1、direct模式

以Direct模式为例 (这里的交换机还有队列式通过代码生成的,当然也可以通过15672端口界面手动创建)

Java的依赖

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

定义生产者

package com.zhang.rabbitmq.all;

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

public class Producer {

    public static void main(String[] args) {
        // 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
        // ip:port

        // 1:创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();

        // 2:设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");

        Connection connection = null;
        Channel channel = null;
        try {
            // 3:创建连接Connection
            connection = connectionFactory.newConnection("生产者");

            // 4:通过连接获取Channel
            channel = connection.createChannel();

            // 5:准备消息内容
            String message = "你好,章卫军!!!";

            // 6: 准备交换机
            String exchangeName = "direct-new-exchange";

            // 7:指定交换机的类型
            String exchangeType = "direct";

            // 8:声明(创建)交换机     true代表持久化
            channel.exchangeDeclare(exchangeName, exchangeType, true);

            // 9:声明(创建)队列
            // * @params1: queue 队列的名称    @params2: durable 队列是否持久化
            //  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
            //  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
            //   @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
            channel.queueDeclare("queue5",true, false, false, null);
            channel.queueDeclare("queue6",true, false, false, null);
            channel.queueDeclare("queue7",true, false, false, null);

            // 10:交换机绑定队列
            channel.queueBind("queue5",exchangeName, "order");
            channel.queueBind("queue6",exchangeName, "order");
            channel.queueBind("queue7",exchangeName, "course");

            // 11:定义路由key
            String routerKey = "order";

            // 12:发送消息给中间件rabbitmq-server
            channel.basicPublish(exchangeName, routerKey, null, message.getBytes());
            System.out.println("消息发送成功!");


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7:先关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 8:再关闭连接
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }

    }
}

定义消费者

package com.zhang.rabbitmq.all;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {

    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();

        // 2: 设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");

        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者");

            // 4: 从连接中获取通道channel
            channel = connection.createChannel();

            // 5: 申明队列queue存储消息,如果队列不存在,则会创建
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);

            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });

            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("接收消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };

    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue5").start();
        new Thread(runnable, "queue6").start();
        new Thread(runnable, "queue7").start();

    }


}

1.2、work模式

另外再看一下Work模式,work模式有两种,第一种是轮询模式,第二种是公平模式

生产者相同(交换机采用的是默认的(direct模式),队列是在http://ip地址:15672/#/queues中实现申明好的)

package com.zhang.rabbitmq.work.fair;

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

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");

            // 4: 从连接中获取通道channel
            channel = connection.createChannel();

            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "章卫军:" + i;

                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }

            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

结果:
在这里插入图片描述
执行完毕后可以看到生产者通过交换机往queue1里面发送了20条消息,接下来申明消费者进行消费。

1.2.1、轮询模式

消费者1(work1)

package com.zhang.rabbitmq.work.polling;

import com.rabbitmq.client.*;
import java.io.IOException;

public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            
            // 5: 申明队列queue存储消息
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            // finalChannel.basicQos(1);

            // @params2:true,为开启自动应答,但是一般不这么用,一般都设置为手动应答
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

消费者2(work2)

package com.zhang.rabbitmq.work.polling;


import com.rabbitmq.client.*;
import java.io.IOException;

public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            
            // 5: 申明队列queue存储消息  这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);

            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            
            // finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

比较消费者1和消费者2可以看到,消费者1接收一条消息需要花费200ms,但是消费者2收到一条信息需要花费2000ms,按照轮询模式的概念,那么20条信息,不管他们接收速度如何,都要保证一个消费者一条,按均分配,我们看结果是否如此
结果:
在这里插入图片描述
在这里插入图片描述
果然如此!!!!

1.2.2、公平模式

消费者1 (work1)

package com.zhang.rabbitmq.work.fair;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            
            // 5: 申明队列queue存储消息
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);

            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
                
            // 同一时刻,服务器只会推送一条消息给消费者
            finalChannel.basicQos(1);

            // @params2:false,为手动应答,公平分发都要改成这种模式
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

消费者2 (work2)

package com.zhang.rabbitmq.work.fair;


import com.rabbitmq.client.*;

import java.io.IOException;

public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("120.79.155.9");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);

            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            
            // 同一时刻,服务器只会推送一条消息给消费者
            finalChannel.basicQos(1);
            
            // @params2:false,为手动应答,公平分发都要改成这种模式
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

消费者1接收一条消息需要花费200ms,但是消费者2收到一条信息需要花费2000ms,按照公平模式的概念,根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;那么20条信息,消费者1应该接收更多的信息,而消费者2应该接收较少的信息,我们看一下结果是否如此
在这里插入图片描述
在这里插入图片描述
果然如此!!!

1.2.3 轮询模式和公平模式的区别

比较公平模式和轮询模式在消费者代码的区别,我们可以发现

  • 公平模式有finalChannel.basicQos(1);,而轮询模式没有,这个是保证同一时刻,服务器只会推送一条消息给消费者
  • 公平模式的finalChannel.basicConsume("queue1", false, new DeliverCallback(){...}, new CancelCallback(){...})方法的第二个参数设置为false,但是轮询模式却设置为true,这个表示应答方式,设置为false表示为手动应答,设置为true表示为自动应答。一般情况下不会设置为自动应答。

2、SpringBoot整合RabbitMQ

2.1、direct模式范例讲解

1、实现目标

使用springboot完成rabbitmq的消费模式-direct
在这里插入图片描述

2、实现步骤

1:创建生产者工程:sspringboot-order-rabbitmq-producer
2:创建消费者工程:springboot-order-rabbitmq-consumer
3:引入spring-boot-rabbitmq的依赖
4:进行消息的分发和测试
5:查看和观察web控制台的状况

3、定义生产者

1)创建生产者工程
在这里插入图片描述
2)在pom.xml中引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

3)在application.yml进行配置

# 服务端口
server:
  port: 8080
# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 47.104.141.27
    port: 5672

4)定义订单的生产者

package com.xuexiangban.rabbitmq.springbootrabbitmqfanoutproducer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;

@Component
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    // 1: 定义交换机
    private String exchangeName = "direct_order_exchange";
    // 2: 路由key
    private String routeKey = "email";
    public void makeOrder(Long userId, Long productId, int num) {
        // 模拟用户下单
        String orderNumer = UUID.randomUUID().toString();

        System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
        // 发送订单信息给RabbitMQ direct
        rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
    }
}

4)通过配置类创建交换机和队列,并绑定关系

package com.zhang.rabbitmq.order.config;


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;

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

@Configuration
public class DirectRabbitMqConfiguration {
    //队列 起名:TestDirectQueue
    @Bean
    public Queue directemailQueue() {

        return new Queue("email.direct.queue", true);
    }
    @Bean
    public Queue directsmsQueue() {
        return new Queue("sms.direct.queue", true);
    }
    @Bean
    public Queue directweixinQueue() {
        return new Queue("weixin.direct.queue", true);
    }
    //Direct交换机 起名:TestDirectExchange
    @Bean
    public DirectExchange directOrderExchange() {
        //  return new DirectExchange("TestDirectExchange",true,true);
        return new DirectExchange("direct_order_exchange", true, false);
    }
    //绑定  将队列和交换机绑定, 并设置用于匹配键,其实前面makeOrder()将routeKey设置为空,这里去掉with语句,那就是fanout模式了
    @Bean
    public Binding bindingDirect4() {
        return BindingBuilder.bind(directweixinQueue()).to(directOrderExchange()).with("direct");
    }
    @Bean
    public Binding bindingDirect5() {
        return BindingBuilder.bind(directsmsQueue()).to(directOrderExchange()).with("sms");
    }
    @Bean
    public Binding bindingDirect6() {
        return BindingBuilder.bind(directemailQueue()).to(directOrderExchange()).with("email");
    }
}

5)进行测试

package com.zhang.rabbitmq.order;

import com.zhang.rabbitmq.order.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {



    @Autowired
    OrderService orderService;

    @Test
    public void contextLoads() throws Exception {
        orderService.makeOrder(1L, 2L, 12);
    }
}

6)结果,生产者已经通过交换机将消息发送给队列了,因为路由key是eamil,所以只有eamil.direct.queue收到了消息
在这里插入图片描述

4、定义消费者

1)创建消费者工程
在这里插入图片描述

2)在pom.xml中引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

3)在application.yml进行配置

# 服务端口,端口不能和生产者冲突
server:
  port: 8081
# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 47.104.141.27
    port: 5672

4)定义订单的消费者-邮件服务
邮件服务通过@RabbitListener(queues = {"email.direct.queue"})来监听email.direct.queue队列

package com.zhang.rabbitmq.consumer.service.direct;

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

@RabbitListener(queues = {"email.direct.queue"})
@Service
public class DirectEmailConsumer {
    
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email direct ---接收到了订单信息是:--->"+message);
    }
}

5)定义订单的消费者-SMS服务

package com.zhang.rabbitmq.consumer.service.direct;

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

@RabbitListener(queues = {"sms.direct.queue"})
@Service
public class DirectSMSConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms direct ---接收到了订单信息是:--->"+message);
    }
}

6)定义订单的消费者-微信服务

package com.zhang.rabbitmq.consumer.service.direct;


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

@RabbitListener(queues = {"weixin.direct.queue"})
@Service
public class DirectWeixinConsumer {
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("weixin direct ---接收到了订单信息是:--->"+message);
    }
}

7)启动所有服务查看效果
email.direct.queue队列内容被消费
在这里插入图片描述
而且只有邮件服务消费了,因为我们设置里路由key为eamil
在这里插入图片描述

2.2、SpringBoot注解创建交换机和队列,并绑定关系

创建交换机和队列,并绑定关系的方式有三种
1)通过http://ip地址:15672的界面进行手动创建
2)通过@Configuration配置类进行创建,上面已经说过了
3)通过注解进行创建
例如创建一个topic模式的交换机,以及队列并绑定关系,这个注解我们一般在消费者端进行,因为消费者是和我们的队列是直接联系的,而且消费者一般先提出消费需求,生产者才去生产。

邮件服务

package com.zhang.rabbitmq.consumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value="email.topic.queue",durable = "true", autoDelete = "false"),
        exchange = @Exchange(value="topic-order-exchange",type = ExchangeTypes.TOPIC),
        key = "#.email.#"
))
@Service
public class TopicEmailConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email topic ---接收到了订单信息是:--->"+message);
    }
}

SMS服务

package com.zhang.rabbitmq.consumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value="sms.topic.queue",durable = "true", autoDelete = "false"),
        exchange = @Exchange(value="topic-order-exchange",type = ExchangeTypes.TOPIC),
        key = "#.sms.#"
))
@Service
public class TopicSMSConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms topic ---接收到了订单信息是:--->"+message);
    }
}

微信服务

package com.zhang.rabbitmq.consumer.service.topic;


import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

import javax.lang.model.type.ExecutableType;

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value="weixin.topic.queue",durable = "true", autoDelete = "false"),
        exchange = @Exchange(value="topic-order-exchange",type = ExchangeTypes.TOPIC),
        key = "com.#"
))
@Service
public class TopicWeixinConsumer {
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("weixin topic ---接收到了订单信息是:--->"+message);
    }
}

三、死信队列

3.1 过期时间TTL

过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置。

  • 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
  • 第二种方法是对消息进行单独设置,每条消息TTL可以不同。

如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列, 消费者将无法再收到该消息。

设置队列TTL

配置类中在创建队列的时候进行设置参数

package com.zhang.rabbitmq.order.config;


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

import java.util.HashMap;
import java.util.Map;

@Configuration
public class TTLRabbitMqConfiguration {

    @Bean
    public DirectExchange ttlDirectOrderExchange() {
        return new DirectExchange("ttl-direct-exchange", true, false);
    }


    @Bean
    public Queue directTTLQueue() {
        // 设置队列的过期时间
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl",5000); // 设置队列的过期时间
        return new Queue("ttl.direct.queue", true,false,false,args);
    }

    @Bean
    public Queue directTTLMessageQueue() {
        return new Queue("ttl.message.direct.queue", true);
    }

    @Bean
    public Binding bindingDirect() {
        return BindingBuilder.bind(directTTLQueue()).to(ttlDirectOrderExchange()).with("ttl");
    }

    @Bean
    public Binding ttlmsgBindings() {
        return BindingBuilder.bind(directTTLMessageQueue()).to(ttlDirectOrderExchange()).with("ttlmessage");
    }


}

Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl",5000); // 设置队列的过期时间 5秒内如果消息没有被消费者接收,则送入死信队列。
参数 x-message-ttl 的值 必须是非负 32 位整数 (0 <= n <= 2^32-1) ,以毫秒为单位表示 TTL 的值。这样,值 6000 表示存在于 队列 中的当前 消息 将最多只存活 6 秒钟。
在这里插入图片描述

设置消息TTL

消息的过期时间;只需要在发送消息(可以发送到任何队列,不管该队列是否属于某个交换机)的时候设置过期时间即可。在测试类中编写如下方法发送消息并设置过期时间到队列:

public void makeOrderTTLMessage(Long userId, Long productId, int num) {
        // 1: 定义交换机
        String exchangeName = "ttl-direct-exchange";

        // 2: 路由key
        String routeKey = "ttlmessage";

        // 模拟用户下单
        String orderNumer = UUID.randomUUID().toString();
        System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);

        // 给消息设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 这里就是字符串
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };
        // 发送订单信息给RabbitMQ
        rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer,messagePostProcessor);
    }

expiration 字段以微秒为单位表示 TTL 值。且与 x-message-ttl 具有相同的约束条件。因为 expiration 字段必须为字符串类型,broker 将只会接受以字符串形式表达的数字。
当同时指定了 queue 和 message 的 TTL 值,则两者中较小的那个才会起作用。

3.2、死信队列

DLX,全称为Dead-Letter-Exchange , 可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下的原因:

  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度

DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange 指定交换机即可。

在这里插入图片描述

设置死信交换机和私信队列,并绑定关系

package com.zhang.rabbitmq.order.config;


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

import java.util.HashMap;
import java.util.Map;

@Configuration
public class DeadRabbitMqConfiguration {

    @Bean
    public DirectExchange deadDirectOrderExchange() {
        return new DirectExchange("dead-direct-exchange", true, false);
    }


    @Bean
    public Queue deadQueue() {
        return new Queue("dead.direct.queue", true);
    }


    @Bean
    public Binding deadBinds() {
        return BindingBuilder.bind(deadQueue()).to(deadDirectOrderExchange()).with("dead");
    }
}

设置一个队列的过期策略(超时以及最大长度),并绑定死信交换机和设置路由key

package com.zhang.rabbitmq.order.config;


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

import java.util.HashMap;
import java.util.Map;

@Configuration
public class TTLRabbitMqConfiguration {

    @Bean
    public DirectExchange ttlDirectOrderExchange() {
        return new DirectExchange("ttl-direct-exchange", true, false);
    }


    @Bean
    public Queue directTTLQueue() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl",5000); // 设置队列的过期时间
        args.put("x-max-length",5); // 设置队列的最大长度
        args.put("x-dead-letter-exchange","dead-direct-exchange");  // 设置死信队列的交换机
        args.put("x-dead-letter-routing-key","dead");  // 设置死信队列的路由key
        return new Queue("ttl.direct.queue", true,false,false,args);
    }


    @Bean
    public Binding bindingDirect() {
        return BindingBuilder.bind(directTTLQueue()).to(ttlDirectOrderExchange()).with("ttl");
    }



}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值