【RabbitMQ】----RabbitMQ 的7种工作模式

1.Simple(简单模式)

P:⽣产者,也就是要发送消息的程序

C:消费者,消息的接收者

Queue :消息队列,图中⻩⾊背景部分.类似⼀个邮箱,可以缓存消息;⽣产者向其中投递消息,消费者从其中取出消息.

特点:⼀个⽣产者P,⼀个消费者C,消息只能被消费⼀次.也称为点对点(Point-to-Point)模式.适⽤场景:消息只能被单个消费者处理

工作流程

1.生产者生成一条消息,并将其发送到交换机。

2.交换机根据消息属性(在简单模式下可能是固定的路由键或不需要路由键)将消息发送到指定的队列。

3.消费者监听并绑定到该队列,当队列中有消息时,消费者将其取出并处理。

4.消息被消费者处理后,会自动从队列中删除(除非设置了消息持久化或手动确认机制)。

应用场景

简单模式适用于那些需要一对一消息传递的场景,例如:

1.手机短信发送:一个生产者(如短信服务)将短信内容发送到交换机,交换机将其发送到指定的队列,消费者(如短信网关)从队列中接收短信并发送到用户的手机上。
2.邮件单发:一个生产者(如邮件服务)将邮件内容发送到交换机,交换机将其发送到指定的队列,消费者(如邮件发送器)从队列中接收邮件并发送到指定的邮箱。

优点

1.结构简单,易于理解和实现。

2.消息传递可靠,确保消息按照到达的顺序被处理。

缺点

1.一个队列只能被一个消费者监听和消费,可能无法充分利用系统资源。

2.在高并发场景下,单个消费者可能无法及时处理所有消息,导致消息堆积。

使用案例
引入依赖
复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
编写生产者代码
创建连接
java 复制代码
  //1.建立连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("182.92.242.181");
        factory.setPort(5672); //默认值 5672
        factory.setVirtualHost("bite");//虚拟机名称, 默认 /
        factory.setUsername("study");//⽤户名,默认guest
        factory.setPassword("study");//密码, 默认guest
        //3. 创建连接Connection
        Connection connection = factory.newConnection();
创建Channel

⽣产者和消费者创建的channel并不是同⼀个

java 复制代码
Channel channel = connection.createChannel();
声明一个队列
java 复制代码
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
         *                                  Map<String, Object> arguments)
         *  参数说明:
         *  queue: 队列名称
         *  durable: 可持久化
         *  exclusive: 是否独占
         *  autoDelete: 是否自动删除
         *  arguments: 参数
         */
        channel.queueDeclare("hello", true, false, false, null);
发送消息
java 复制代码
        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 参数说明:
         * exchange: 交换机名称
         * routingKey: 内置交换机, routingkey和队列名称保持一致
         * props: 属性配置
         * body: 消息
         */
        for (int i = 0; i < 10; i++) {
            String msg = "hello rabbitmq~"+i;
            channel.basicPublish("","hello", null, msg.getBytes());
        }
释放资源
java 复制代码
    channel.close();
    connection.close();

注意:一定是先关闭通道后关闭连接

编写消费者代码
创建连接+创建Channel+声明队列
java 复制代码
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("182.92.242.181");
        factory.setPort(5672);
        factory.setUsername("study");
        factory.setPassword("study");
        factory.setVirtualHost("bite");
        Connection connection = factory.newConnection();
 
        Channel channel = connection.createChannel();
 
        channel.queueDeclare("hello",true, false, false, null);
消费消息
java 复制代码
        /**
         * basicConsume(String queue, boolean autoAck, Consumer callback)
         * 参数说明:
         * queue: 队列名称
         * autoAck: 是否自动确认
         * callback: 接收到消息后, 执行的逻辑
         */
        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));
            }
        };
        channel.basicConsume("hello", true, consumer);
释放资源
java 复制代码
    channel.close();
    connection.close();

2.Work Queue(工作队列)

概念

在工作队列模式中,一个生产者(producer)将任务发布到队列中,多个消费者(consumer)从队列中获取任务并执行。这种模式的主要目标是提高任务的并行处理能力,从而提高系统的吞吐量和效率。

⼀个⽣产者P,多个消费者C1,C2.在多个消息的情况下,Work Queue 会将消息分派给不同的消费者,每个消费者都会接收到不同的消息.

特点:消息不会重复,分配给不同的消费者.

适⽤场景: 任务分发:将任务分发给多个工作者(消费者),以便并行处理。这对于需要高吞吐量和任务处理效率的应用程序非常有用。例如,图像处理、视频编码、数据转换等应用可以使用工作队列模式来并行处理大量任务。

工作原理

1.生产者发送任务:生产者将任务封装为消息,并将其发送到RabbitMQ队列中。
2.RabbitMQ分发任务:RabbitMQ根据配置的分发策略(如轮询或公平分发)将任务分发给消费者。
3.消费者处理任务:消费者从队列中获取任务并执行。在处理完任务后,消费者会向RabbitMQ发送确认消息,表示任务已完成。
4.RabbitMQ确认任务完成:在收到消费者的确认消息后,RabbitMQ会将该任务从队列中移除。

注意事项

1.消息确认:为了确保消息不会丢失,消费者在处理完任务后需要向RabbitMQ发送确认消息。如果消费者在处理任务时失败或崩溃,RabbitMQ会将该任务重新分发给其他消费者。

2.负载均衡:RabbitMQ默认采用轮询方式将消息分发给消费者。如果需要更复杂的负载均衡策略,可以考虑使用其他分发策略或自定义交换机类型。

3.错误处理:在生产者和消费者中都需要添加适当的错误处理逻辑,以处理可能出现的异常情况,如连接失败、消息发送失败等。

代码案例
引入依赖
java 复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
常量类
java 复制代码
public class Constants {
    public static final String HOST = "182.92.242.181";
    public static final int PORT = 5672;
    public static final String USER_NAME = "study";
    public static final String PASSWORD = "study";
    public static final String VIRTUAL_HOST = "bite";
    //工作队列模式
    public static final String WORK_QUEUE = "work.queue";
}
编写生产者代码
java 复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列   使用内置的交换机
        //如果队列不存在, 则创建, 如果队列存在, 则不创建
        channel.queueDeclare(Constants.WORK_QUEUE, true, false, false, null);
        //4. 发送消息
        for (int i = 0; i < 10; i++) {
            String msg = "hello work queue...."+i;
            channel.basicPublish("",Constants.WORK_QUEUE, null, msg.getBytes());
        }
 
        System.out.println("消息发送成功~");
        //6. 资源释放
        channel.close();
        connection.close();
    }
}
编写消费者1代码
java 复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列   使用内置的交换机
        //如果队列不存在, 则创建, 如果队列存在, 则不创建
        channel.queueDeclare(Constants.WORK_QUEUE, true, false, false, null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.WORK_QUEUE, true, consumer);
 
        //6. 资源释放
//        channel.close();
//        connection.close();
    }
}
编写消费者2代码
java 复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列   使用内置的交换机
        //如果队列不存在, 则创建, 如果队列存在, 则不创建
        channel.queueDeclare(Constants.WORK_QUEUE, true, false, false, null);
        //4. 消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            //从队列中收到消息, 就会执行的方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //TODO
                System.out.println("接收到消息:"+ new String(body));
            }
        };
        channel.basicConsume(Constants.WORK_QUEUE, true, consumer);
 
//        //6. 资源释放
//        channel.close();
//        connection.close();
    }
}

先运行生产者,后运行消费者

查看管理界面

我们此时会看到,先启动的消费者会消费掉队列中所有的消息。

先运行消费者,后运行生产者

此时我们能看到,两个消费者都能够消费消息。

3.Publish/Subscribe(发布/订阅)

概念

RabbitMQ的发布订阅模式(Publish/Subscribe)是一种消息传递模式,它允许消息生产者(Publisher)将消息发送到交换机(Exchange),然后交换机根据路由规则将消息广播到一个或多个队列,最后由消费者(Subscriber)从队列中接收并处理消息。

图中X表⽰交换机, 在订阅模型中,多了⼀个Exchange⻆⾊, 过程略有变化。

概念介绍

Exchange: 交换机 (X).

作⽤: ⽣产者将消息发送到Exchange, 由交换机将消息按⼀定规则路由到⼀个或多个队列中(上图中⽣产者将消息投递到队列中, 实际上这个在RabbitMQ中不会发⽣. )

RabbitMQ交换机有四种类型: fanout,direct, topic, headers, 不同类型有着不同的路由策略. AMQP协议⾥还有另外两种类型, System和⾃定义, 此处不再描述.

  1. Fanout:⼴播,将消息交给所有绑定到交换机的队列(Publish/Subscribe模式)

  2. Direct:定向,把消息交给符合指定routing key的队列(Routing模式)

  3. Topic:通配符,把消息交给符合routing pattern(路由模式)的队列(Topics模式)

Exchange(交换机)只负责转发消息, 不具备存储消息的能⼒, 因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息就会丢失

RoutingKey: 路由键.⽣产者将消息发给交换器时, 指定的⼀个字符串, ⽤来告诉交换机应该如何处理这个消息.

Binding Key:绑定. RabbitMQ中通过Binding(绑定)将交换器与队列关联起来, 在绑定的时候⼀般会指定⼀个Binding Key, 这样RabbitMQ就知道如何正确地将消息路由到队列了.

⽐如下图: 如果在发送消息时, 设置了RoutingKey 为orange, 消息就会路由到Q1

当消息的Routing key与队列绑定的Bindingkey相匹配时,消息才会被路由到这个队列.

BindingKey其实也属于路由键中的⼀种, 官⽅解释为:the routingkey to use for the binding.

可以翻译为:在绑定的时候使⽤的路由键. ⼤多数时候,包括官⽅⽂档和RabbitMQJava API 中都把

BindingKey和RoutingKey看作RoutingKey, 为了避免混淆,可以这么理解:

  1. 在使⽤绑定的时候,需要的路由键是BindingKey.

  2. 在发送消息的时候,需要的路由键是RoutingKey

特点和优势

1.解耦合:生产者和消费者之间通过交换机进行解耦。生产者无需知道消息将被传递到哪些队列,消费者也无需知道消息来自哪个生产者。这种解耦合使得系统更加灵活和可扩展。

2.多播:支持多个消费者同时处理同一条消息,实现消息的多播效果。这有助于提高系统的并行处理能力和容错性。

3.灵活性:可以根据需要使用不同类型的交换机和绑定规则,以满足不同的消息传递需求。RabbitMQ提供了多种交换机类型,如直接交换机、扇形交换机、主题交换机等。

应用场景

发布订阅模式适用于需要将消息广播给多个消费者的场景,例如:

1.实时通知:如系统状态更新、订单状态变更等实时事件的通知。通过发布订阅模式,可以将这些事件广播给所有感兴趣的消费者。

2.日志记录:将应用程序的日志信息广播到多个日志处理服务进行处理和存储。这有助于实现日志的集中管理和分析。

3.事件处理:在事件驱动架构中,将事件作为消息发布到交换机,由多个消费者订阅并处理这些事件。这有助于实现事件的异步处理和分布式处理。

注意事项

1.消息持久化:为了确保消息在RabbitMQ服务器重启后不会丢失,可以将消息和队列标记为持久性。这样,即使服务器发生故障,消息仍然可以被消费者接收和处理。
2.消息确认:RabbitMQ支持消息确认机制,确保消息在成功处理后才会从队列中删除。这有助于防止消息丢失和重复处理。

3.负载均衡:在发布订阅模式中,多个消费者可以监听同一个队列或不同的队列。为了实现负载均衡,可以配置多个消费者来处理同一个队列中的消息。

代码案例
引入依赖
复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
常量类
复制代码
public class Constants {
    public static final String HOST = "182.92.242.181";
    public static final int PORT = 5672;
    public static final String USER_NAME = "study";
    public static final String PASSWORD = "study";
    public static final String VIRTUAL_HOST = "bite";
 
    //发布订阅模式
    public static final String FANOUT_EXCHANGE = "fanout.exchange";
    public static final String FANOUT_QUEUE1 = "fanout.queue1";
    public static final String FANOUT_QUEUE2 = "fanout.queue2";
}
编写生产者代码
复制代码
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明交换机
        channel.exchangeDeclare(Constants.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT, true);
        //4. 声明队列
        channel.queueDeclare(Constants.FANOUT_QUEUE1,true,false,false,null);
        channel.queueDeclare(Constants.FANOUT_QUEUE2,true,false,false,null);
        //5. 交换机和队列绑定
        channel.queueBind(Constants.FANOUT_QUEUE1,Constants.FANOUT_EXCHANGE,"");
        channel.queueBind(Constants.FANOUT_QUEUE2,Constants.FANOUT_EXCHANGE,"");
        //6. 发布消息
        String msg = "hello fanout....";
        channel.basicPublish(Constants.FANOUT_EXCHANGE,"", null, msg.getBytes());
        System.out.println("消息发送成功");
        //7. 释放资源
        channel.close();
        connection.close();
 
    }
}
编写消费者1代码
复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列
        channel.queueDeclare(Constants.FANOUT_QUEUE1,true,false,false,null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.FANOUT_QUEUE1, true, consumer);
 
    }
}
编写消费者2代码
复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列
        channel.queueDeclare(Constants.FANOUT_QUEUE2,true,false,false,null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.FANOUT_QUEUE2, true, consumer);
 
    }
}

由此我们可以看到,交换机收到的生产者生产的一条消息被广播到了两个队列,消费者都能够分别从这两个队列中得到一条消息并消费。

4.Routing(路由模式)

路由模式是发布订阅模式的变种,在发布订阅基础上,增加路由 key 发布订阅模式是⽆条件的将所有消息分发给所有消费者,路由模式是 Exchange 根据 RoutingKey 的规则,将数据筛选后发给对应的消费者队列 适合场景:需要根据特定规则分发消息的场景.

⽐如系统打印⽇志,⽇志等级分为 error,warning,info,debug,就可以通过这种模式,把不同的⽇志发送到不同的队列,最终输出到不同的文件

工作原理

1.生产者发送消息:生产者将消息发送到RabbitMQ的交换机,并指定一个或多个路由键。

2.交换机根据路由键路由消息:交换机接收消息后,根据消息的路由键和绑定规则,将消息路由到与之匹配的队列中。匹配规则由交换机的类型和绑定规则决定。

3.消费者监听队列:消费者可以选择监听特定的队列或多个队列,以接收他们感兴趣的消息。

4.消息处理:消费者从队列中接收消息,并进行相应的处理。

特点

1.灵活路由:生产者可以根据需要指定不同的路由键来发送消息,交换机根据路由键将消息路由到不同的队列。

2.定向传递:消息只会被发送到与之匹配的队列中,消费者只需关注他们感兴趣的消息,而不需要接收所有的消息。

3.精确控制:通过定义不同的路由规则,RabbitMQ的路由模式可以实现各种复杂的消息传递需求,如日志级别过滤、消息过滤等。

实现步骤

1.创建交换机:创建一个类型为Direct的交换机。

2.创建队列:创建多个队列,用于存储不同类型的消息。

3.绑定队列和交换机:使用路由键将队列和交换机进行绑定。

4.发送消息:生产者将消息发送到交换机,并指定路由键。

5.接收消息:消费者监听特定的队列,接收并处理消息。

代码案例
引入依赖
复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
常量类
复制代码
public class Constants {
    public static final String HOST = "182.92.242.181";
    public static final int PORT = 5672;
    public static final String USER_NAME = "study";
    public static final String PASSWORD = "study";
    public static final String VIRTUAL_HOST = "bite";
 
    //路由模式
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    public static final String DIRECT_QUEUE1 = "direct.queue1";
    public static final String DIRECT_QUEUE2 = "direct.queue2";
}
编写生产者代码
java 复制代码
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
/**
 * 路由模式生产者
 */
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明交换机
        channel.exchangeDeclare(Constants.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);
        //4. 声明队列
        channel.queueDeclare(Constants.DIRECT_QUEUE1, true, false, false, null);
        channel.queueDeclare(Constants.DIRECT_QUEUE2, true, false, false, null);
        //5. 绑定交换机和队列
        channel.queueBind(Constants.DIRECT_QUEUE1, Constants.DIRECT_EXCHANGE, "a");
        channel.queueBind(Constants.DIRECT_QUEUE2, Constants.DIRECT_EXCHANGE, "a");
        channel.queueBind(Constants.DIRECT_QUEUE2, Constants.DIRECT_EXCHANGE, "b");
        channel.queueBind(Constants.DIRECT_QUEUE2, Constants.DIRECT_EXCHANGE, "c");
        //6. 发送消息
        String msg = "hello direct, my routingkey is a....";
        channel.basicPublish(Constants.DIRECT_EXCHANGE,"a", null, msg.getBytes());
 
        String msg_b = "hello direct, my routingkey is b....";
        channel.basicPublish(Constants.DIRECT_EXCHANGE,"b", null, msg_b.getBytes());
 
        String msg_c = "hello direct, my routingkey is c....";
        channel.basicPublish(Constants.DIRECT_EXCHANGE,"c", null, msg_c.getBytes());
        System.out.println("消息发送成功");
 
        //7. 释放资源
        channel.close();
        connection.close();
    }
}
编写消费者1代码
java 复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列
        channel.queueDeclare(Constants.DIRECT_QUEUE1,true,false,false,null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.DIRECT_QUEUE1, true, consumer);
 
    }
}
编写消费者2代码
java 复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列
        channel.queueDeclare(Constants.DIRECT_QUEUE2,true,false,false,null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.DIRECT_QUEUE2, true, consumer);
 
    }
}
运行代码

查看管理界面,可以看到一个队列消息数目为1,另一条队列消息数目为3

5.Topics(通配符模式)

路由模式的升级版, 在routingKey的基础上,增加了通配符的功能, 使之更加灵活.

Topics和Routing的基本原理相同,即:⽣产者将消息发给交换机,交换机根据RoutingKey将消息转发给与RoutingKey匹配的队列. 类似于正则表达式的⽅式来定义Routingkey的模式.

不同之处是:routingKey的匹配⽅式不同,Routing模式是相等匹配,topics模式是通配符匹配.

应用场景

RabbitMQ的通配符模式在需要根据消息的特定属性进行路由和过滤的场景中非常有用。例如,在一个日志系统中,可以使用通配符模式来将不同级别的日志消息路由到不同的队列中,以便进行不同的处理和分析。

优势

通配符模式的优势在于它可以灵活地匹配消息,使得消息可以根据不同的条件进行过滤和选择。通过合理地定义绑定和路由键,可以实现复杂的消息过滤和路由策略,提高系统的灵活性和性能。

代码案例
引入依赖
复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
常量类
复制代码
public class Constants {
    public static final String HOST = "182.92.242.181";
    public static final int PORT = 5672;
    public static final String USER_NAME = "study";
    public static final String PASSWORD = "study";
    public static final String VIRTUAL_HOST = "bite";
 
    //通配符模式
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    public static final String TOPIC_QUEUE1 = "topic_queue1";
    public static final String TOPIC_QUEUE2 = "topic_queue2";
}
编写生产者代码
复制代码
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
/**
 * 通配符模式生产者
 */
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明交换机
        channel.exchangeDeclare(Constants.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC, true);
        //4. 声明队列
        channel.queueDeclare(Constants.TOPIC_QUEUE1, true, false, false, null);
        channel.queueDeclare(Constants.TOPIC_QUEUE2, true, false, false, null);
        //5. 绑定交换机和队列
        channel.queueBind(Constants.TOPIC_QUEUE1, Constants.TOPIC_EXCHANGE, "*.a.*");
        channel.queueBind(Constants.TOPIC_QUEUE2, Constants.TOPIC_EXCHANGE, "*.*.b");
        channel.queueBind(Constants.TOPIC_QUEUE2, Constants.TOPIC_EXCHANGE, "c.#");
 
        //6. 发送消息
        String msg = "hello topic, my routingkey is ae.a.f....";
        channel.basicPublish(Constants.TOPIC_EXCHANGE,"ae.a.f", null, msg.getBytes());  //转发到Q1
 
        String msg_b = "hello topic, my routingkey is ef.a.b....";
        channel.basicPublish(Constants.TOPIC_EXCHANGE,"ef.a.b", null, msg_b.getBytes()); //转发到Q1和Q2
 
        String msg_c = "hello topic, my routingkey is c.ef.d....";
        channel.basicPublish(Constants.TOPIC_EXCHANGE,"c.ef.d", null, msg_c.getBytes());//转发Q2
        System.out.println("消息发送成功");
 
        //7. 释放资源
        channel.close();
        connection.close();
    }
}
编写消费者1代码
复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列
        channel.queueDeclare(Constants.TOPIC_QUEUE1,true,false,false,null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.TOPIC_QUEUE1, true, consumer);
 
    }
}
编写消费者2代码
复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 声明队列
        channel.queueDeclare(Constants.TOPIC_QUEUE2,true,false,false,null);
        //4. 消费消息
        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));
            }
        };
        channel.basicConsume(Constants.TOPIC_QUEUE2, true, consumer);
 
    }
}
运行代码

观察管理界面可以看到两个队列都各自收到了2条消息,与预期符合。

6.RPC(RPC通信)

在 RPC 通信的过程中,没有⽣产者和消费者,⽐较像咱们 RPC 远程调⽤,⼤概就是通过两个队列实现了⼀个可回调的过程

1.客户端发送请求:

  • 客户端连接到RabbitMQ服务器。
  • 客户端声明一个用于发送RPC请求的队列(通常是固定的,如rpc_queue)。
  • 客户端创建一个临时的回调队列,并在发送请求时,将回调队列的名称作为消息属性(reply_to)发送给交换机。
  • 客户端为每个请求生成一个唯一的correlation_id,并将其作为消息属性发送,以便在接收响应时能够匹配请求与响应。

2.交换机路由请求:

交换机接收到RPC请求后,根据路由键将请求路由到服务端监听的队列。

3.服务端处理请求:

服务端(消费者)从队列中接收请求。

服务端处理请求,并生成响应。

服务端将响应发送到客户端指定的回调队列,并在消息属性中设置相同的correlation_id。

4.客户端接收响应:

客户端监听其回调队列以接收响应。

当接收到响应时,客户端检查correlation_id以确定响应是否与之前的请求匹配。

如果匹配,客户端处理响应;如果不匹配,客户端可能丢弃该响应。

代码案例
引入依赖
复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
常量类
复制代码
public class Constants {
    public static final String HOST = "182.92.242.181";
    public static final int PORT = 5672;
    public static final String USER_NAME = "study";
    public static final String PASSWORD = "study";
    public static final String VIRTUAL_HOST = "bite";
 
    //rpc 模式
    public static final String RPC_REQUEST_QUEUE = "rpc.request.queue";
    public static final String RPC_RESPONSE_QUEUE = "rpc.response.queue";
}

编写客户端代码

java 复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
 
/**
 * rpc 客户端
 * 1. 发送请求
 * 2. 接收响应
 */
public class RpcClient {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        channel.queueDeclare(Constants.RPC_REQUEST_QUEUE, true, false, false, null);
        channel.queueDeclare(Constants.RPC_RESPONSE_QUEUE, true, false, false, null);
        //3. 发送请求
        String msg = "hello rpc...";
        //设置请求的唯一标识
        String correlationID = UUID.randomUUID().toString();
        //设置请求的相关属性
        AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
                .correlationId(correlationID)
                .replyTo(Constants.RPC_RESPONSE_QUEUE)
                .build();
        channel.basicPublish("", Constants.RPC_REQUEST_QUEUE, props, msg.getBytes());
 
        //4. 接收响应
        //使用阻塞队列, 来存储响应信息
        final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String respMsg = new String(body);
                System.out.println("接收到回调消息: "+ respMsg);
                if (correlationID.equals(properties.getCorrelationId())){
                    //如果correlationID校验一致
                    response.offer(respMsg);
                }
            }
        };
        channel.basicConsume(Constants.RPC_RESPONSE_QUEUE, true, consumer);
        String result = response.take();
        System.out.println("[RPC Client 响应结果]:"+ result);
 
    }
}
编写服务端代码
java 复制代码
import com.rabbitmq.client.*;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.concurrent.TimeoutException;
 
/**
 * RPC server
 * 1. 接收请求
 * 2. 发送响应
 */
public class RpcServer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        Connection connection = connectionFactory.newConnection();
        //2. 开启信道
        Channel channel = connection.createChannel();
        //3. 接收请求
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String request = new String(body,"UTF-8");
                System.out.println("接收到请求:"+ request);
                String response = "针对request:"+ request +", 响应成功";
                AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                        .correlationId(properties.getCorrelationId())
                        .build();
                channel.basicPublish("", Constants.RPC_RESPONSE_QUEUE, basicProperties, response.getBytes());
                channel.basicAck(envelope.getDeliveryTag(), false);
 
            }
        };
        channel.basicConsume(Constants.RPC_REQUEST_QUEUE, false, consumer);
    }
}
运行程序(先运行客户端,再运行服务端)

可以在管理界面看到其中一个队列中有1条消息

7.Publisher Confirms(发布确认)

发布确认模式
概述

发布确认模式用于确保消息已经被正确地发送到RabbitMQ服务器,并被成功接收和持久化。通过使用发布确认,生产者可以获得对消息的可靠性保证,避免消息丢失。这一机制基于通道(Channel)级别,通过两个阶段的确认来保证消息的可靠性。

消息丢失问题

作为消息中间件, 都会⾯临消息丢失的问题.

消息丢失⼤概分为三种情况:

  1. ⽣产者问题. 因为应⽤程序故障, ⽹络抖动等各种原因, ⽣产者没有成功向broker发送消息.

  2. 消息中间件⾃⾝问题. ⽣产者成功发送给了Broker, 但是Broker没有把消息保存好, 导致消息丢失.

  3. 消费者问题. Broker 发送消息到消费者, 消费者在消费消息时, 因为没有处理好, 导致broker将消费失败的消息从队列中删除了

RabbitMQ也对上述问题给出了相应的解决⽅案. 问题2可以通过持久化机制. 问题3可以采⽤消息应答机制.

针对问题1, 可以采⽤发布确认(Publisher Confirms)机制实现.

发布确认的三种模式

RabbitMQ的发布确认模式主要有三种形式:单条确认、批量确认和异步确认。

单条确认(Single Publisher Confirm)

特点:在发布一条消息后,等待服务器确认该消息是否成功接收。

优点:实现简单,每条消息的确认状态清晰。

缺点:性能开销较大,特别是在高并发的场景下,因为每条消息都需要等待服务器的确认。

批量确认(Batch Publisher Confirm)

特点:允许在一次性确认多个消息是否成功被服务器接收。

优点:在大量消息的场景中可以提高效率,因为可以减少确认消息的数量。

缺点:当一批消息中有一条消息发送失败时,整个批量确认失败。此时需要重新发送整批消息,但不知道是哪条消息发送失败,增加了调试和处理的难度。

异步确认(Asynchronous Confirm)

特点:通过回调函数处理消息的确认和未确认事件,更加灵活。

优点:在异步场景中能够更好地处理消息的状态,提高了系统的并发性能和响应速度。

缺点:实现相对复杂,需要处理回调函数的逻辑和状态管理。
实现步骤

1.设置通道为发布确认模式:在生产者发送消息之前,需要将通道设置为发布确认模式。这可以通过调用channel.confirmSelect()方法来实现。

2.发送消息并等待确认:生产者发送消息时,每条消息都会分配一个唯一的、递增的整数ID(DeliveryTag)。生产者可以通过调用channel.waitForConfirms()方法来等待所有已发送消息的确认,或者通过其他方式处理确认回调。

3.处理确认回调:为了处理确认回调,需要创建一个ConfirmCallback接口的实现。在实现的handleAck()方法中,可以处理成功接收到确认的消息的逻辑;在handleNack()方法中,可以处理未成功接收到确认的消息的逻辑。

应用场景

发布确认模式适用于对数据安全性要求较高的场景,如金融交易、订单处理等。在这些场景中,消息的丢失或重复都可能导致严重的业务问题。通过使用发布确认模式,可以确保消息被正确地发送到RabbitMQ服务器,并被成功接收和持久化,从而提高了系统的可靠性和稳定性。

代码案例
引入依赖
java 复制代码
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.21.0</version>
</dependency>
常量类
java 复制代码
public class Constants {
    public static final String HOST = "182.92.242.181";
    public static final int PORT = 5672;
    public static final String USER_NAME = "study";
    public static final String PASSWORD = "study";
    public static final String VIRTUAL_HOST = "bite";
 
    //publisher confirms
    public static final String PUBLISHER_CONFIRMS_QUEUE1 = "publisher.confirms.queue1";
    public static final String PUBLISHER_CONFIRMS_QUEUE2 = "publisher.confirms.queue2";
    public static final String PUBLISHER_CONFIRMS_QUEUE3 = "publisher.confirms.queue3";
}
单条确认
java 复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
 
public class PublisherConfirms {
    private static final Integer MESSAGE_COUNT = 100;
    static Connection createConnection() throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        return connectionFactory.newConnection();
    }
 
    public static void main(String[] args) throws Exception {
        //Strategy #1: Publishing Messages Individually
        //单独确认
        publishingMessagesIndividually();
    }
 
 
    /**
     * 单独确认
     */
    private static void publishingMessagesIndividually() throws Exception {
        try(Connection connection = createConnection()) {
            //1. 开启信道
            Channel channel = connection.createChannel();
            //2. 设置信道为confirm模式
            channel.confirmSelect();
            //3. 声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1, true, false, false, null);
            //4. 发送消息, 并等待确认
            long start = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE1, null, msg.getBytes());
                //等待确认
                channel.waitForConfirmsOrDie(5000);
            }
            long end = System.currentTimeMillis();
            System.out.printf("单独确认策略, 消息条数: %d, 耗时: %d ms \n",MESSAGE_COUNT, end-start);
        }
    }
}
运行代码
批量确认
java 复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
 
public class PublisherConfirms {
    private static final Integer MESSAGE_COUNT = 10000;
    static Connection createConnection() throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        return connectionFactory.newConnection();
    }
 
    public static void main(String[] args) throws Exception {
        //Strategy #2: Publishing Messages in Batches
        //批量确认
        publishingMessagesInBatches();
    }
 
 
    /**
     * 批量确认
     * @throws Exception
     */
    private static void publishingMessagesInBatches() throws Exception{
        try(Connection connection = createConnection()) {
            //1. 开启信道
            Channel channel = connection.createChannel();
            //2. 设置信道为confirm模式
            channel.confirmSelect();
            //3. 声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2, true, false, false, null);
            //4. 发送消息, 并进行确认
            long start = System.currentTimeMillis();
            int batchSize = 100;
            int outstandingMessageCount = 0;
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE2, null, msg.getBytes());
                outstandingMessageCount++;
                if (outstandingMessageCount==batchSize){
                    channel.waitForConfirmsOrDie(5000);
                    outstandingMessageCount = 0;
                }
            }
            if (outstandingMessageCount>0){
                channel.waitForConfirmsOrDie(5000);
            }
            long end = System.currentTimeMillis();
            System.out.printf("批量确认策略, 消息条数: %d, 耗时: %d ms \n",MESSAGE_COUNT, end-start);
 
        }
    }
}
运行代码
异步确认
java 复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import rabbitmq.constant.Constants;
 
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
 
public class PublisherConfirms {
    private static final Integer MESSAGE_COUNT = 10000;
    static Connection createConnection() throws Exception {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(Constants.HOST);
        connectionFactory.setPort(Constants.PORT); //需要提前开放端口号
        connectionFactory.setUsername(Constants.USER_NAME);//账号
        connectionFactory.setPassword(Constants.PASSWORD);  //密码
        connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST); //虚拟主机
        return connectionFactory.newConnection();
    }
 
    public static void main(String[] args) throws Exception {
        //Strategy #3: Handling Publisher Confirms Asynchronously
        //异步确认
        handlingPublisherConfirmsAsynchronously();
    }
 
    /**
     * 异步确认
     */
    private static void handlingPublisherConfirmsAsynchronously() throws Exception{
        try (Connection connection = createConnection()){
            //1. 开启信道
            Channel channel = connection.createChannel();
            //2. 设置信道为confirm模式
            channel.confirmSelect();
            //3. 声明队列
            channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3, true, false, false, null);
            //4. 监听confirm
            //集合中存储的是未确认的消息ID
            long start = System.currentTimeMillis();
            SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());
 
            channel.addConfirmListener(new ConfirmListener() {
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    if (multiple){
                        confirmSeqNo.headSet(deliveryTag+1).clear();
                    }else {
                        confirmSeqNo.remove(deliveryTag);
                    }
                }
 
                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    if (multiple){
                        confirmSeqNo.headSet(deliveryTag+1).clear();
                    }else {
                        confirmSeqNo.remove(deliveryTag);
                    }
                    //业务需要根据实际场景进行处理, 比如重发, 此处代码省略
                }
            });
            //5. 发送消息
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "hello publisher confirms"+i;
                long seqNo = channel.getNextPublishSeqNo();
                channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE3, null, msg.getBytes());
                confirmSeqNo.add(seqNo);
            }
            while (!confirmSeqNo.isEmpty()){
                Thread.sleep(10);
            }
            long end = System.currentTimeMillis();
            System.out.printf("异步确认策略, 消息条数: %d, 耗时: %d ms \n",MESSAGE_COUNT, end-start);
        }
    }
}

综上,速度上:异步>批量>单条

相关推荐
励志成为糕手8 小时前
Hadoop进程:深入理解分布式计算引擎的核心机制
大数据·hadoop·分布式·mapreduce·yarn
掘金-我是哪吒8 小时前
分布式微服务系统架构第170集:Kafka消费者并发-多节点消费-可扩展性
分布式·微服务·架构·kafka·系统架构
何双新8 小时前
第 3 讲:KAFKA生产者(Producer)详解
分布式·kafka·linq
Heliotrope_Sun9 小时前
RabbitMQ
分布式·rabbitmq
KIDAKN9 小时前
Redis 分布式锁
数据库·redis·分布式
KIDAKN10 小时前
RabbitMQ 工作模式
分布式·rabbitmq
百思可瑞教育10 小时前
ActiveMQ、RocketMQ、RabbitMQ、Kafka 的全面对比分析
vue.js·分布式·rabbitmq·rocketmq·activemq·北京百思可瑞教育·百思可瑞教育
KIDAKN17 小时前
RabbitMQ 初步认识
分布式·rabbitmq
pan30350747917 小时前
Kafka 和 RabbitMQ的选择
分布式·kafka·rabbitmq