RabbitMQ

1. RabbitMQ介绍

1.1 引言

在这里插入图片描述

市面上比较火的几种MQ
ActiveMQ,RocketMQ,Kafka,RabbitMQ
1.语言的支持: ActiveMQ,RocketMQ只支持Java,Kafka,RabbitMQ支持多种语言
2.效率方面:ActiveMQ,RocketMQ,Kafka都是毫秒级别的,RabbitMQ是微秒级别的
3.消息丢失、消息重复问题:RabbitMQ针对消息的持久化,和重复问题都有成熟的解决方案
4.学习成本:RabbitMQ非常简单
RabbitMQ是由Rabbit公司研发和维护的,最终是在Pivotal公司
RabbitMQ严格的遵循AMQP协议,高级消息队列协议,帮助我们在进程之间传递异步信息。

2 安装RabbitMQ

在这里插入图片描述

3 RabbitMQ架构

3.1 简单的架构图

在这里插入图片描述
(1)Publisher-生产者,发布消息到RabbitMQ的Exchange中
(2)Consumer-消费者,监听Queue中的消息
(3)Exchange-交换机,和生产者建立连接,并且接收生产者的消息
(4)Queue-队列,Exchange会将消息发送到指定的Queue中,和消费者交互
(5)Routes-路由(交换机以什么样的策略将消息发布到Queue中)
在这里插入图片描述

3.2 RabbitMQ完整架构图

在这里插入图片描述

3.3 图形化界面

创建一个全新的用户和全新的Virtual Host,并且将test用户设置上可以操作/test的权限
在这里插入图片描述

4 RabbitMQ的使用

4.1 RabbitMQ的通讯方式

在这里插入图片描述
在这里插入图片描述

4.2 Java连接RabbitMQ

  1. 创建MVN项目
  2. 导入依赖
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.7.3</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

  1. 编写工具类
package com.learn.rabbitmq.config;

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

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


/**
 * 手动连接RabbitMq
 */
public class RabbitMqClient {
    public static Connection getConnection(){
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("test");
        factory.setPassword("test");
        factory.setVirtualHost("/test");

        // 1.创建Connection
        try {
            Connection connection = factory.newConnection();
            return connection;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } catch (TimeoutException e) {
            e.printStackTrace();
            return null;
        }

    }
}

在这里插入图片描述

4.3 Hello-World

一个生产者,一个默认的Exchange,一个队列,一个消费者
这里我们通过直接指定队列名称hello-world实现
在这里插入图片描述

  1. 创建生产者,发布消息到exchange,指定路由规则
package com.learn.rabbitdemo.helloworld;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;

public class Publisher {
    @Test
    public void publish()throws Exception{
        // 1.连接Rabbit、获取connection
        Connection connection = RabbitMqClient.getConnection();

        // 2.创建channel
        Channel channel = connection.createChannel();

        String msg = "Hello World";

        // 3.发布消息到exchange,同时指定路由规则
        // 参数1 指定exchange,这里使用空"",(默认)
        // 参数2 指定路由规则,使用具体的队列名称
        // 参数3 指定传递消息所携带的属性properties
        // 参数4 指定发布的具体消息byte[]
        channel.basicPublish("","hello-world",null,msg.getBytes());

        // exchange是不会将消息持久化本地的,Queue才会帮你持久化消息

        System.out.println("消息发布成功");
        // 释放资源
        channel.close();
        connection.close();

    }
}

  1. 创建消费者,常见一个channel,创建一个对流,并且去消费当前队列
package com.learn.rabbitdemo.helloworld;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();
        // 参数1:queue指定队列名称
        // 参数2:durable 当前队列是否持久化(宕机以后是否重新发布)
        // 参数3:exclusive 是否排外(conn.close()当前队列会被自动删除,当前队列只能被一个消费者消费)
        // 参数4:autoDelete 如果这个队列没有消费者,队列是否被删除
        // 参数5:arguments 指定当前队列的其他信息
        channel.queueDeclare("hello-world",true,false,false,null);

        // 开启监听
        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"));
            }
        };
        // 参数1:queue指定队列
        // 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
        // 参数3:指定消息回调
        channel.basicConsume("hello-world",true,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

4.4 Work

一个生产者,一个默认的Exchange,一个队列,两个消费者
这里我们也是同样直接指定队列名称,加上线程sleep和 channel.basicQos(1);设置消费者消费能力,演示rabbitMQ的消息分发
在这里插入图片描述

package com.learn.rabbitdemo.work;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer1 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();

        // 指定当前消费者,一次消费多少消息
        channel.basicQos(1);

        channel.queueDeclare("work",true,false,false,null);

        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }


        };
        // 参数1:queue指定队列
        // 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
        // 参数3:指定消息回调
        channel.basicConsume("work",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

package com.learn.rabbitdemo.work;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer2 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        channel.basicQos(1);
        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("work",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

package com.learn.rabbitdemo.work;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;

/**
 * work工作模式
 */
public class Publisher {
    @Test
    public void publish()throws Exception{
        // 1.连接Rabbit、获取connection
        Connection connection = RabbitMqClient.getConnection();

        // 2.创建channel
        Channel channel = connection.createChannel();


        // 3.发布消息到exchange,同时指定路由规则
        // 参数1 指定exchange,这里使用空"",(默认)
        // 参数2 指定路由规则,使用具体的队列名称
        // 参数3 指定传递消息所携带的属性properties
        // 参数4 指定发布的具体消息byte[]
        for(int i =0;i<10;i++){
            String msg = "work" + i;
            channel.basicPublish("","work",null,msg.getBytes());

        }

        // exchange是不会将消息持久化本地的,Queue才会帮你持久化消息

        System.out.println("消息发布成功");
        // 释放资源
        channel.close();
        connection.close();

    }
}

只需要在消费者端,添加Qos能力以及更改为手动ack即可让消费者,根据自己能力去消费指定的消息,而不是默认情况下由RabbitMQ平均分配了。

4.5 Publish/Subscribe

一个生产者,一个自己的Exchange,两个队列,两个消费者
这里我们通过指定exchange(FOUNT类型)的,然后queueBind(“队列名”,“交换机名”)进行绑定
在这里插入图片描述

声明一个Fanout类型的exchange,并且将exchange和queue绑定在一起,绑定的方式是直接绑定。
1.让生产者创建一个exchange并且指定fanout类型,和一个或者多个队列绑定在一起
2.消费者还是正常监听的某一个队列即可

         channel.exchangeDeclare("publish-exchange", BuiltinExchangeType.FANOUT);
    // 绑定队列(这里绑定两个队列)
    
    channel.queueBind("publish-queue1","publish-exchange","");
    channel.queueBind("publish-queue2","publish-exchange","");
package com.learn.rabbitdemo.publish;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;

/**
 * 生产者
 */
public class Publisher {
    @Test
    public void publish()throws Exception{
        Connection connection = RabbitMqClient.getConnection();
        Channel channel = connection.createChannel();

        // 创建自己的Exchange,并且绑定某一个队列
        // 参数1:Exchange的名称
        // 参数2:指定exchange的类型,通过BuiltinExchangeType指定
        // FANOUT -> publish,DIRECT -> Routing, TOPIC -> topics
        channel.exchangeDeclare("publish-exchange", BuiltinExchangeType.FANOUT);

        // 绑定队列(这里绑定两个队列)
        channel.queueBind("publish-queue1","publish-exchange","");
        channel.queueBind("publish-queue2","publish-exchange","");
        for(int i =0;i<10;i++){
            String msg = "publish" + i;
            channel.basicPublish("publish-exchange","work",null,msg.getBytes());
        }
        System.out.println("消息发布成功");
        // 释放资源
        channel.close();
        connection.close();
    }
}

package com.learn.rabbitdemo.publish;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer1 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();

        // 指定当前消费者,一次消费多少消息
        channel.basicQos(1);

        // 创建队列
        channel.queueDeclare("publish-queue1",true,false,false,null);

        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }


        };
        // 参数1:queue指定队列
        // 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
        // 参数3:指定消息回调
        channel.basicConsume("publish-queue1",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

package com.learn.rabbitdemo.publish;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer2 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();
        channel.queueDeclare("publish-queue2",true,false,false,null);
        channel.basicQos(1);
        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("publish-queue2",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

4.6 Routing

一个生产者,一个交换机,两个队列,两个消费者
这里指定交换机(direct类型的),通过路由key绑定交换机和路由,queueBind(“queueName”,“exchangeName”,“routingName”)绑定
在这里插入图片描述

package com.learn.rabbitdemo.routing;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;

public class Publisher {
    @Test
    public void publish()throws Exception{
        // 1.连接Rabbit、获取connection
        Connection connection = RabbitMqClient.getConnection();

        // 2.创建channel
        Channel channel = connection.createChannel();

        // 3.创建exchange 绑定 routing-queue-error  routing-queue-info
        channel.exchangeDeclare("routing-exchange", BuiltinExchangeType.DIRECT);

        // 4.通过路由key绑定交换机与队列
        channel.queueBind("routing-queue-error","routing-exchange","error");
        channel.queueBind("routing-queue-info","routing-exchange","info");

        String msg = "routing";
        channel.basicPublish("routing-exchange","info",null,"info".getBytes());
        channel.basicPublish("routing-exchange","error",null,"error1".getBytes());
        channel.basicPublish("routing-exchange","error",null,"error2".getBytes());
        channel.basicPublish("routing-exchange","error",null,"error3".getBytes());
        channel.basicPublish("routing-exchange","error",null,"error4".getBytes());

        System.out.println("消息发布成功");
        // 释放资源
        channel.close();
        connection.close();

    }
}

package com.learn.rabbitdemo.routing;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer1 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();

        // 指定当前消费者,一次消费多少消息
        channel.basicQos(1);

        // 创建队列
        channel.queueDeclare("routing-queue-info",true,false,false,null);

        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }


        };
        // 参数1:queue指定队列
        // 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
        // 参数3:指定消息回调
        channel.basicConsume("routing-queue-info",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

package com.learn.rabbitdemo.routing;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer2 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();
        channel.queueDeclare("routing-queue-error",true,false,false,null);
        channel.basicQos(1);
        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("routing-queue-error",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

在这里插入图片描述
在这里插入图片描述

4.7 Topic

一个生产者,一个交换机,两个队列,两个消费者
生产者创建topic的exchange,并且绑定在队列中,这次绑定可以通过*和#关键字对指定routeKey内容
*表示一个占位符
#表示一个通配符
特别注意:在发送消息的时候必须指定具体的routeKey

package com.learn.rabbitdemo.topic;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;

public class Publisher {
    @Test
    public void publish()throws Exception{
        // 1.连接Rabbit、获取connection
        Connection connection = RabbitMqClient.getConnection();

        // 2.创建channel
        Channel channel = connection.createChannel();

        // 3.创建exchange 绑定 routing-queue-error  routing-queue-info
        channel.exchangeDeclare("topic-exchange", BuiltinExchangeType.TOPIC);

        // 4.通过路由key绑定交换机与队列
        // 动物信息<speed><color><what>
        // *.red.* 红色的动物  -》 * 占位符
        // fast.# 快速的动物 -》 # 通配符
        // #.rabbit
        channel.queueBind("topic-queue-1","topic-exchange","*.red.*");
        channel.queueBind("topic-queue-2","topic-exchange","fast.#");
        channel.queueBind("topic-queue-2","topic-exchange","#.rabbit");

        channel.basicPublish("topic-exchange","fast.red.monkey",null,"红快猴子".getBytes());
        channel.basicPublish("topic-exchange","slow.black.dog",null,"黑慢猴子".getBytes());
        channel.basicPublish("topic-exchange","fast.white.cat",null,"快白猫".getBytes());

        System.out.println("消息发布成功");
        // 释放资源
        channel.close();
        connection.close();

    }
}

package com.learn.rabbitdemo.topic;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer1 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();

        // 指定当前消费者,一次消费多少消息
        channel.basicQos(1);

        // 创建队列
        channel.queueDeclare("topic-queue-1",true,false,false,null);

        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }


        };
        // 参数1:queue指定队列
        // 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
        // 参数3:指定消息回调
        channel.basicConsume("topic-queue-1",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

package com.learn.rabbitdemo.topic;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer2 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();
        channel.queueDeclare("topic-queue-2",true,false,false,null);
        channel.basicQos(1);
        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("topic-queue-2",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

5.rabbitMQ整合Springboot

5.1 springboot整合RabbitMQ

1.创建springboot工程
2.导入依赖

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

3.配置文件

# rabbitMQ
spring.rabbitmq.host = 127.0.0.1
spring.rabbitmq.port = 5672
spring.rabbitmq.username=test
spring.rabbitmq.password=test
spring.rabbitmq.virtual-host= /test

#
server.port=8080


#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver

package com.learn.rabbitdemo.config;

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

/**
 * rabbitMQ配置类
 */
@Configuration
public class RabbitConfig {
    // 1.创建topic 类型的exchange
    @Bean
    public TopicExchange getTopicExchange(){
        return new TopicExchange("boot-topic-exchange",true,false);
    }

    // 2.创建队列
    @Bean
    public Queue getQueue(){
        return new Queue("boot-topic-queue",true,false,false,null);
    }

    // 3.绑定在一起
    @Bean
    public Binding getBinding(TopicExchange topicExchange,Queue queue){
        return BindingBuilder.bind(queue).to(topicExchange).with("*.red.*");

    }
}

  1. 发送消息
package com.learn.rabbitdemo;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class RabbitdemoApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void contextLoads() {
        // 1指定exchange
        rabbitTemplate.convertAndSend("boot-topic-exchange","slow.red.dog","红色的大狼狗!!!");
    }

}

  1. 消费消息
package com.learn.rabbitdemo.config;

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

@Component
public class Consumer {
    @RabbitListener(queues = "boot-topic-queue")
    public void getMessage(Object message){
        System.out.println(message);

    }

}

5.2 手动ACK

1.添加配置文件

spring.rabbitmq.listener.simple.acknowledge-mode=manual

2.监听消息的地方,手动ACK

@Component
public class Consumer {
    @RabbitListener(queues = "boot-topic-queue")
    public void getMessage(String msg, Channel channel, Message message) throws Exception{
        System.out.println(msg);

        // 手动ACK
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

}

6 RabbitMQ的其他操作

6.1 消息的可靠性

在这里插入图片描述

1.如果消息到了RabbitMQ,但是它宕机了,消息是不是就丢了?
不会,RabbitMQ得到Queue有持久化机制
2.消费者在消费消息时,如果执行一般,消费者宕机了,手动ACK
3.生产者发送消息时,由于网络问题,导致消息没发到RabbitMQ
两种解决方案:1RabbitMQ提供了事务操作,和Comfirm

RabbitMQ事务:事务可以保证消息的百分百传递,可以通过事务的回滚去记录日志,后面定时再发送当前消息,但是效率太低,加了事务操作以后,效率低了一百倍以上

RabbitMQ除了事务还提供了Confirm机制,这个效率比事务高很多
1.普通Confirm方式

        String msg = "Hello World";

        // 开启Confirm
        channel.confirmSelect();
        channel.basicPublish("","hello-world",null,msg.getBytes());

        // exchange是不会将消息持久化本地的,Queue才会帮你持久化消息
        // 判断消息是否发送成功
        if (channel.waitForConfirms()){
            System.out.println("消息发布成功");
        }else {
            System.out.println("消息发布失败");
        }

2.批量Confirm方式

        // 开启Confirm
        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            String msg = "Hello World" + i;
            channel.basicPublish("","hello-world",null,msg.getBytes());

        }

        // 演示批量Confirm,当你有一个失败的时候会抛出IOExpection
        channel.waitForConfirmsOrDie();

3.异步Confirm方式


        // 开启Confirm
        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            String msg = "Hello World" + i;
            channel.basicPublish("","hello-world",null,msg.getBytes());

        }

        // 异步
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("消息发送成功 标志" + l + "是否批量" + b);
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("消息发送失败 标志" + l + "是否批量" + b);

            }
        });
        // 释放资源
        System.in.read();

6.1.2Return 机制

在这里插入图片描述

confirm只能保证消息到达exchange,无法保证消息可以被exchange分发到指定的queue
而且exchange是不能持久化消息的,queue是可以持久化消息的
采用Return机制来监听消息是否从exchange送到了queue中


        // 当消息没有送到queue时,才会执行
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                // 当消息没有送到queue时,才会执行
                System.out.println(new String(bytes,"utf-8") + "没有送达queue中!!!");
            }
        });
        // 发送消息时,需要指定mandatory参数为true
                    channel.basicPublish("","publish-queue2",true,null,msg.getBytes());

6.1.3 springboot实现confirm以及Return

1.配置文件

spring.rabbitmq.publisher-confirm-type=simple
spring.rabbitmq.publisher-returns-type=true

2.指定rabbitTemplet开启Confirm和Return,并且编写回调方法

package com.learn.rabbitdemo.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class PublisherConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.setConfirmCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        if (b){
            System.out.println("消息已经到达exchange");
        }else {
            System.out.println("消息没有到达exchange");
        }
    }

    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        System.out.println("消息没有到达queue");
    }
}

6.2 重复消费问题

消费者没有给RabbitMQ一个ACK
重复消费消息,会对非幂等行操作造成问题
在这里插入图片描述
为了解决重复消费问题,可以使用redis帮忙,先将消息的id放到redis中

id-0 (正在执行业务)
id-1(执行业务成功)
如果ACK失败,在RabbitMQ将消息交给其他的消费者时,先执行setnx,如果key已经存在,获取它的值,如果是0说明此消息正被消费,当前消费者就什么都不做,如果是1,直接ACK
极端情况:第一个消费者在执行业务时,出现了死锁在setnx的基础上再设置一个生存时间。

  1. 生产者发送消息时,指定messageId
package com.learn.rabbitdemo.reply;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;

import java.util.UUID;

public class Publisher {

    @Test
    public void publish()throws Exception{
        // 1.连接Rabbit、获取connection
        Connection connection = RabbitMqClient.getConnection();

        // 2.创建channel
        Channel channel = connection.createChannel();

        // 开启Confirm
        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            // 指定消息是否需要持久化 1-需要持久化,2-不需要持久化
            AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                    .deliveryMode(1)
                    .messageId(String.valueOf(UUID.randomUUID()))
                    .build();
            String msg = "Hello World" + i;
            channel.basicPublish("","reply-info",true,properties,msg.getBytes());
            System.out.println("消息发送成功");
        }

        // 演示批量Confirm,当你有一个失败的时候会抛出IOExpection
        channel.waitForConfirmsOrDie();
        // 释放资源
        channel.close();
        connection.close();

    }
}

  1. 消费者,在消费消息时,根据具体逻辑去操作redis
package com.learn.rabbitdemo.reply;

import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import redis.clients.jedis.Jedis;

import java.io.IOException;

/**
 * 消费者
 */
public class Consumer1 {
    public static void main(String[] args) throws Exception{
        // 创建连接
        Connection connection = RabbitMqClient.getConnection();

        // 创建channel
        Channel channel = connection.createChannel();

        // 指定当前消费者,一次消费多少消息
        channel.basicQos(1);

        // 创建队列
        channel.queueDeclare("reply-info",true,false,false,null);

        // 开启监听
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                Jedis jedis = new Jedis("127.0.0.1",6379);

                String messageId = properties.getMessageId();

                // 1.setnx到Redis,默认指定0
                String result = jedis.set(messageId,"0","NX","EX",10);
                if (result != null && "OK".equalsIgnoreCase(result)){
                    // 2.消费成功,set messageid 为1
                    jedis.setex(messageId,10,"1");
                    System.out.println(properties.getMessageId());
                    System.out.println("消费者1号接收到消息" + new String(body,"utf-8"));
                    // 手动ACK
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }else {
                    // 如果1中的setnx失败,获取key对应的value,如果是0,return 如果是1,ACK
                    String s = jedis.get(messageId);
                    if ("1".equalsIgnoreCase(s)){
                        channel.basicAck(envelope.getDeliveryTag(),false);
                    }
                }
            }


        };
        // 参数1:queue指定队列
        // 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
        // 参数3:指定消息回调
        channel.basicConsume("reply-info",false,consumer);
        System.out.println("消费者开始监听队列");
        System.in.read();
        channel.close();
        connection.close();
    }
}

6.2.2springboot如何实现

1.导入依赖


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>

2.编写配置文件

#redis
spring.redis.host=localhost
spring.redis.port=6379

3.修改生产者

    @RequestMapping("/sendReply")
    public void send(){
        CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
        // 1指定exchange
        Message message = MessageBuilder
                .withBody("asddas".getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_BYTES)
                .setContentEncoding("ute-8")
                .setMessageId(UUID.randomUUID().toString())
                .build();
        rabbitTemplate.convertAndSend("boot-topic-exchange","fast.red.dog",message);
    }

4.修改消费者

package com.learn.rabbitdemo.config;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class Consumer {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @RabbitListener(queues = "boot-topic-queue")
    public void getMessage(String msg, Channel channel, Message message) throws Exception{
        String messageId = message.getMessageProperties().getMessageId();
        if (redisTemplate.opsForValue().setIfAbsent(messageId,"0",10, TimeUnit.SECONDS)){
            System.out.println(msg);
            redisTemplate.opsForValue().set(messageId,"1",10, TimeUnit.SECONDS);
            // 手动ACK
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }else {
            // 如果1中的setnx失败,获取key对应的value,如果是0,return 如果是1,ACK
            String s = redisTemplate.opsForValue().get(messageId);
            if ("1".equalsIgnoreCase(s)){
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            }
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值