rabbitMQ入门指南:基于Java客户端的详细使用说明

1. 引言

  在上一篇文章中,(下面的例子都会使用到该教程已经配好的配置)我们详细介绍了RabbitMQ管理页面的常用功能,通过查看和操作RabbitMQ服务中的交换机、队列等,初步理解了RabbitMQ的工作机制。现在,我们将进一步探索RabbitMQ的工作机制,这次我们将使用RabbitMQ官方提供的Java客户端来进行实践。通过使用Java客户端,我们可以更深入地了解RabbitMQ的工作原理,并在代码层面上与消息队列进行交互。

2. RabbitMQ的基础编程模型

2.1 环境配置

在使用官方提供的客户端,如果是maven工程,需要引入相关的依赖,并且jdk必须是1.8+

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

2.2 创建连接和获取channel

  在使用RabbitMQ的Java客户端与RabbitMQ服务器交互时,首先需要创建一个连接(Connection),然后通过这个连接获取一个通道(Channel)。通道是几乎所有API方法的主要工作接口,每个线程应该有自己独立的通道。

ConnectionFactory factory = new ConnectionFactory();
// 服务所在的地址,默认:localhost
factory.setHost("192.168.1.11");
// 服务的ip,可以不填,默认:5672
factory.setPort(ConnectionFactory.DEFAULT_AMQP_PORT);
// 操作用户,再管理后台的Admin->User配置
factory.setUsername("tom");
// 用户的密码
factory.setPassword("abc123");
// 虚拟机名称
factory.setVirtualHost("v-1");
// 创建连接
Connection connection = factory.newConnection();
// 获取通道
Channel channel = connection.createChannel();

  通常在一个客户端只需要创建一个Channel对象,因为只要不关闭,就可以重复使用。但如果有创建多个Channel的需求时,需要确保Channel具有唯一性,在创建时,可以在createChannel方法中传入一个分配的整数参数,该参数将作为Channel的唯一标识。

Channel createChannel(int channelNumber) throws IOException;

注释:Create a new channel, using the specified channel number if possible.Use openChannel(int) if you want to use an Optional to deal with a value.
翻译:创建一个新通道,如果可能的话,使用指定的通道号。如果要使用Optional处理值,请使用openChannel(int)。

2.3 声明exchange(非必须)

  声明交换器是设置消息路由的关键步骤。在发送消息时,可以选择声明一个交换器,并且指定它的类型(directfanoutheaderstopic),也可以不声明交换器,直接使用RabbitMQ的默认交换器。

// 声明非自动删除、非持久的交换机,名称是:e-1,类型是:direct
channel.exchangeDeclare("e-1", "direct");

完整配置的声明方法

exchangeDeclare(String exchange,String type,boolean durable,boolean autoDelete,boolean internal,Map<String, Object> arguments) throws IOException;

该方法的返回值为一个 Exchange.DeclareOk对象,表示交换机声明成功的响应。

参数:
exchange:交换机的名称
type:交换机的类型。指定交换机的类型,可选值包括:directtopicfanoutheaders。不同类型的交换机决定了消息的路由策略和匹配规则。
durable:指定交换机是否应该被持久化到磁盘上。如果设置为 true,则该交换机会在 RabbitMQ 服务器重启后仍然存在;如果设置为 false,则在 RabbitMQ 服务器重启时会被删除。需要注意的是,只有在声明交换机时和发送消息时都设置为持久化,才能确保消息的持久化。
autoDelete:指定交换机是否在没有与之绑定的队列时自动删除。如果设置为 true,则在所有与该交换机绑定的队列都解绑之后,交换机会被自动删除。
internal:指定交换机是否在没有与之绑定的队列时自动删除。如果设置为 true,则在所有与该交换机绑定的队列都解绑之后,交换机会被自动删除。
arguments:用于设置交换机的其他参数,以键值对的形式传递。这些参数可以用于特定的需求,例如设置交换机的备份交换机、消息过期时间等。参数以字符串键和对象值的形式提供。

该方法的所有配置在管理页面都可以进行配置,只有名称是必填,跟客户端的方法是相同的
在这里插入图片描述
例子:

//声明一个类型位direct,名称是e-1,持久化,不自动删除的交换机
channel.exchangeDeclare("e-1","direct", true, false, null);

执行后到管理页面查看,在v-1虚拟机下面已经多出了我们刚刚创建的e-1交换机,
在这里插入图片描述
  这里需要注意的是,在调用声明交换机方法时,如果服务上没有则会自动创建,如果有,我们调用方法的参数需要跟已经存在的交换机的参数保持一致。如果不一致就会报错。
例如我执行下面的代码,将前面已经创建好的交换机修改一下类型再次调用

channel.exchangeDeclare("e-1","topic", true, false, null);

客户端会报错:

Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg ‘type’ for exchange ‘e-1’ in vhost ‘v-1’: received ‘topic’ but current is ‘direct’, class-id=40, method-id=10)

2.4 声明queue

  队列是RabbitMQ的内部对象,用于存储消息。声明队列时,需要指定队列的名称,并且可以设置队列的一些属性,例如是否持久化(Durable)、是否排他(Exclusive)或者是否自动删除(Auto-delete)。

queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments) throws IOException;

该方法的返回值为一个 Queue.DeclareOk 对象,表示队列声明成功的响应。如果队列已经存在并且参数匹配,则返回现有队列的声明信息。

参数:
queue: 队列名称
durable:队列是否应该被持久化到磁盘上。如果设置为 true,则该队列会在RabbitMQ服务器重启后仍然存在;如果设置为 false,则在RabbitMQ服务器重启时会被删除。需要注意的是,只有在声明队列时和发送消息时都设置为持久化,才能确保消息的持久化。
exclusive: 指定队列是否为排他队列。如果设置为 true,则只允许声明它的连接使用该队列,并且在连接关闭时会自动删除该队列。排他队列适用于只有一个消费者的场景。
autoDelete: 指定队列是否在没有消费者订阅时自动删除。如果设置为 true,则在所有与该队列关联的消费者取消订阅之后,队列会被自动删除。
arguments: 用于设置队列的其他参数,以键值对的形式传递。这些参数可以用于特定的需求,例如设置队列的消息过期时间、最大长度等。参数以字符串键和对象值的形式提供。

2.4.1 声明Classic队列

在RabbitMQ中,默认的队列类型就是Classic队列。最简单的声明方式:

channel.queueDeclare("q-1", true, false, false, null);

或者添加参数申明,这种更后面申明仲裁队列和流队列在形式上更加相似:

Map<String, Object> argMap = new HashMap<>();
argMap.put("x-queue-type", "classic");
channel.queueDeclare("classic_q", true, false, false, argMap);

执行后回到管理页面的Queues标签页面,可以看到两种编程方式声明的结果是一样的
在这里插入图片描述

2.4.2 声明Quorum队列

  Quorum队列是RabbitMQ提供的一种新的持久化队列,它提供了更高的数据安全性。声明方式只需要在添加参数x-queue-type等于quorum,同时建议添加x-max-length-bytesx-stream-max-segment-size-bytes

x-stream-max-segment-size-bytes:设置Stream队列中每个数据段(segment)的最大字节数。一个Stream队列的数据是按照segment组织的,每个segment包含了一组消息。这个参数允许你控制每个segment的大小,以便优化数据存储和检索性能。

请注意,以上的设置会影响到RabbitMQ的存储和性能,应根据实际情况进行设置。

Map<String, Object> argMap = new HashMap<>();
argMap.put("x-queue-type", "stream");
//stream最大字节数1G
argMap.put("x-max-length-bytes", 10_000_000_000L);
//每个segment大小是50M
argMap.put("x-stream-max-segment-size-bytes", 50_000_000);
channel.queueDeclare("stream_q", true, false, false, argMap);

2.4.3 声明Stream队列

  Stream队列是RabbitMQ中的一种特殊类型,它适用于需要处理数据流的场景。声明方式需要在添加参数x-queue-type等于stream,同时还需要添加

Map<String, Object> argMap = new HashMap<>();
argMap.put("x-queue-type", "stream");
channel.queueDeclare("classic_q", true, false, false, argMap);

2.4.4 queue参数详解

从前面的管理面板可以看到,RabbitMq给我们提供了一些可选的Arguments配置,这些配置的大概含义如下:

  1. Auto expire: 这个参数用来设置队列的过期时间(单位是毫秒)。如果在设定的时间内,该队列没有被任何消费者订阅,也没有任何其他操作(如队列声明,清除队列等),那么该队列会被自动删除。
    例子:该队列在未使用30分钟后过期。
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-expires", 1800000);
channel.queueDeclare("myqueue", false, false, false, args);
  1. Message TTL (Time To Live): 这个参数用来设置队列中消息的生存期(单位是毫秒)。如果消息在设定的时间内没有被消费,那么该消息会被自动删除。
    例子:消息最多可以存活60秒
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl", 60000);
channel.queueDeclare("myqueue", false, false, false, args);
  1. Overflow behaviour: 这个参数用来设置当队列中的消息达到最大长度或者最大字节时,该如何处理新到达的消息。可选的值有:“drop-head”(删除队列头部的消息),“reject-publish”(拒绝新的发布请求),或者"reject-publish-dlx"(拒绝新的发布请求,并将这些消息路由到死信队列)。
    例子:声明了一个最大长度为10条消息的队列,大于10后把前面的删除
Map<String, Object> argsMap = new HashMap<String, Object>();
argsMap.put("x-overflow", "drop-head");
argsMap.put("x-max-length", 10);
channel.queueDeclare("overflow_q", true, false, false, argsMap);
  1. Single active consumer: 当这个参数被设定为true时,只有一个消费者可以从队列中消费消息。如果有多个消费者尝试订阅同一个队列,RabbitMQ会保证只有一个消费者接收到消息。当这个消费者断开连接后,RabbitMQ会选择下一个消费者来接收消息。

例子:启用单活动消费者

Map<String, Object> argsMap = new HashMap<String, Object>();
argsMap .put("x-single-active-consumer", true);
ch.queueDeclare("single_q", false, false, false, argsMap );
  1. Dead letter exchange: 这个参数用来设置死信交换机。当消息因为某些原因(如:被拒绝,达到生存期等)从队列中删除时,这些消息会被发送到这个交换机。
    例子:给队列声明一个死信交换机,dead_exc是在服务上已经创建好的死信交换机,dead_q队列符合规则的消息就会进入到交换机中
channel.exchangeDeclare("exc-1", "direct");
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "dead_exc");
channel.queueDeclare("dead_q", false, false, false, args);
  1. Dead letter routing key: 这个参数用来设置被发送到死信交换器的消息的路由键。
    例子:在5的基础上,可以通过交换机配置的路由键进行投放消息,routing-key-a 即是该路由键
args.put("x-dead-letter-routing-key", "routing-key-a");
  1. Max length: 这个参数用来设置队列可以容纳的消息的最大数量。当新的消息到达,而队列中的消息已经达到这个数量时,根据"Overflow behaviour"参数的设置,可能会删除一些消息,或者拒绝新的消息。
    例子参考前面第3点

  2. Max length bytes: 这个参数用来设置队列可以容纳的消息的最大字节。当新的消息到达,而队列中的消息已经达到这个字节时,根据"Overflow behaviour"参数的设置,可能会删除一些消息,或者拒绝新的消息。
    例子:声明了一个最大字节为32的队列,大于32字节后就不在接收消息

Map<String, Object> argsMap = new HashMap<String, Object>();
argsMap.put("x-overflow", "reject-publish");
argsMap.put("x-max-length-bytes", 32);
channel.queueDeclare("overflow_max_bytes_q", true, false, false, argsMap);

如果超过字节限制继续添加消息,会报错:rejected Unable to publish message. Check queue limits.

  1. Leader locator: 这个参数用于设置Quorum队列中的leader选择策略。
    可选的值有:
    balanced:队列的leader会在集群中进行均衡分布。这种策略可以有效地分散整个集群的负载
    client-local:默认选项,队列的leader会被选择为与当前打开通道的客户端最近的节点。这种策略可以最大限度地减少网络延迟。

2.5 queue与exchange的绑定(非必须)

  绑定是队列和交换器之间的链接。创建绑定后,交换器就知道如何根据路由键将消息路由到正确的队列。绑定是非必须的,如果不创建绑定,消息将被发送到RabbitMQ的默认交换器。
例子:将q-1队列绑定到e-1交换机上

channel.exchangeDeclare("e-1","direct", true, false, null);
channel.queueDeclare("q-1", true, false, false, null);
channel.queueBind("q-1", "e-1", "");

查看管理后台可以看到交换机上已经绑定了一个队列,按钮Unbind是解绑操作
在这里插入图片描述

2.6 生产者发送消息

  生产者发送消息时,需要指定一个交换器和一个路由键。然后,RabbitMQ将根据这个交换器和路由键决定如何路由这条消息。在Java客户端中,生产者发送消息通常使用channel.basicPublish()方法。
例子:发送100条消息到队列q-1

Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("q-1", true, false, false, null);
int count = 0;
while (count++ < 100){
    
    
    channel.basicPublish("","q-1", null, ("hello world " + count).getBytes());
}
channel.close();
ConnectionUtils.closeConnection();

查看管理后台队列中的情况,可以看到此时已经推送到服务中
在这里插入图片描述

2.7 消费者消费消息

  消费者消费消息主要通过两种方式:一种是调用channel.basicGet()方法主动拉取消息,另一种是使用channel.basicConsume()方法注册一个回调函数,然后RabbitMQ将消息推送给消费者。消费者在接收到消息后需要发送一个消息确认信号给RabbitMQ。

2.7.1 主动拉取

例子:消费100条消息

Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
int count = 0;
while(count++ < 100){
    
    
    GetResponse getResponse = channel.basicGet("q-1", true);
    byte[] body = getResponse.getBody();
    System.out.println("消息内容:" + new String(body));
}
channel.close();
ConnectionUtils.closeConnection();

2.7.2 注册回调函数方式

启动线程,等待服务端推送消息,如果监听的队列中原来有消息,也会推送。例子中DefaultConsumer官方提供的一个默认的监听器,如过需要自定义,只需实现Consumer接口即可。

new Thread(() -> {
    
    
    try {
    
    
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.basicConsume("q-1",
                true,
                new DefaultConsumer(channel){
    
    
                    @Override
                    public void handleConsumeOk(String consumerTag) {
    
    
                        System.out.println("消费者启动成功");
                    }
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                        System.out.println("消息内容:" + new String(body));
                    }
                });
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}).start();

2.8 关闭连接释放资源

  在完成所有的消息发布和接收任务后,应该关闭通道和连接,释放资源。在Java客户端中,可以通过调用channel.close()connection.close()方法来完成。

channel.close();
connection.close();

3. 总结

  本文主要详细介绍了RabbitMQ官方提供的Java客户端的使用方法。主要基于编程模型角度,详细介绍了如何声明Exchange和Queue,包括Classic队列,Quorum队列,和Stream队列,Queue和Exchange的绑定,同时也罗列了各种参数及其含义。最后,通过例子展示了生产者如何发送消息以及消费者如何消费消息。希望通过本文,能帮助你更加快速的掌握RabbitMQ Java客户端的使用。

猜你喜欢

转载自blog.csdn.net/dougsu/article/details/131775821