【译】RabbitMQ教程(一)Hello World!

本文介绍如何使用Java和RabbitMQ实现简单的消息传递,包括设置RabbitMQ环境、编写生产者和消费者程序,以及运行结果展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

PS:本教程的程序代码有些地方可能与原文的稍有不同。使用的amqp-client.jar版本为5.2.0。

原文来自:RabbitMQ Tutorials

前提

该教程假定你已经安装了RabbitMQ,并且已经在 localhost 中运行起来。没安装的可参考:RabbitMQ安装

介绍

RabbitMQ是一个消息代理,用于接受并转发消息。你可以把它想象成一个邮局:当你把想要邮寄的信件放到邮箱,邮递员最终会把信件送到收件人手中。在这个比喻中,RabbitMQ就相当于邮箱、邮局和邮递员。

RabbitMQ与邮局的区别在于RabbitMQ不处理纸质信件,而是接受、存储并转发二进制数据块──消息

RabbitMQ的消息发送过程中,通常使用如下一些术语:

  • 生产者:生产相当于发送,发送消息的程序就是生产者。

  • 队列:队列相当于RabbitMQ的邮箱。尽管消息只是在RabbitMQ和你的程序直接流转,但是消息只能存储在队列中。队列仅受主机内存和磁盘限度的约束,本质上是一个大型的消息缓冲区。多个生产者可以发送消息到同一个队列中,多个消费者也可以尝试从同一个队列中接收数据。

  • 消费者:消费和接受具有相似的意思,通常等待接受消息的程序就是消费者。

注意,生产者、消费者以及消息代理(RabbitMQ)并不一定要在同一个主机上;实际上大部分程序也是如此。一个程序也可以同时是消费者和生产者。

 

"Hello World"

在这一部分中,我们将会用Java编写两个程序:用于发送单条消息的生产者、一个用于接受并打印出消息的消费者。专注于这个简单的入门程序,我们将会忽略Java API的一些细节描述。程序中的消息是"Hello World"。

下图中,"P"代表生产者,"C"代表消费者,中间的小盒子代表队列──RabbitMQ为消费者而保持的一个消息缓冲区。

(P) -> [|||] -> (C)

程序开始前,我们需要引入Java客户端依赖amqp-client.jar,也可以通过maven的引入:

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

发送(生产者)

(P) -> [|||]

 

我们将消息的发布者(生产者)命名为Send,将消息的消费者命名为Recv。发布者将会连接到RabbitMQ,发送一条消息,然后退出。

在 Send.java,我们需要引入一些类:

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

创建class并定义队列的名称:

public class Send {
  private final static String QUEUE_NAME = "hello";
  public static void main(String[] argv) throws Exception {
      ...
  }
}    

然后创建一个连接,连接到服务器:

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
     Channel channel = connection.createChannel()) {

}

这里我们连接到的是本机机器的一个消息代理(RabbitMQ)──因此为localhost。如果我们想要连接到其他机器上的代理,只要指定它的名字或者IP即可。

接下来我们创建了一个通道(channel),大部分的处理数据的API在这里完成。注意,在这里我们可以使用  try-with-resources 语句创建Connection 以及Channel,这样我们就不用写代码对其进行关闭。

对于Send来说,我们必须声明消息要发到哪个队列:然后我们可以把消息发布到队列中,这些都在 try-with-resources 里面进行:

channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");

队列的定义是幂等(其任意多次执行所产生的影响均与一次执行的影响相同)的,只有在队列不存在时才会创建。消息的内容是一个字节数组,所以你可以随意进行编码。

完整代码:

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

public class Send {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

接收(消费者)

消费者从RabbitMQ中接收生产者推送过来的消息,所以不同于发布者只发送一条消息就退出,我们让消费者一直保持对消息的监听,并把接收到的消息打印出来。

[|||] -> (C)

 Recv.java代码需要引入的类基本和Send相同:

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

与发布者相同,我们打开一个连接和一个通道,并声明我们将要消耗的队列。注意,队列名称与Send中声明的队列名称匹配。

public class Recv {

  private final static String QUEUE_NAME = "hello";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

  }
}

注意,这里我们同样声明了队列,因为我们可能在启动生产者应用之前先启动了消费者应用,我们想确保在从一个队列消费消息前这个队列是存在的。

如果我们先启动消费者应用,并且让其监听一个不存在的队列,会报如下错误:

Exception in thread "main" java.io.IOException
	at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:126)
	at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:122)
	at com.rabbitmq.client.impl.ChannelN.basicConsume(ChannelN.java:1369)
	at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.basicConsume(AutorecoveringChannel.java:540)
	at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.basicConsume(AutorecoveringChannel.java:494)
	at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.basicConsume(AutorecoveringChannel.java:477)
	at Recv.main(Recv.java:23)
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'hel' in vhost '/', class-id=60, method-id=20)
	at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66)
	at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36)
	at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:494)
	at com.rabbitmq.client.impl.ChannelN.basicConsume(ChannelN.java:1363)
	... 4 more
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'hel' in vhost '/', class-id=60, method-id=20)
	at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:510)
	at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:346)
	at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:178)
	at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:111)
	at com.rabbitmq.client.impl.AMQConnection.readFrame(AMQConnection.java:670)
	at com.rabbitmq.client.impl.AMQConnection.access$300(AMQConnection.java:48)
	at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:597)
	at java.lang.Thread.run(Thread.java:748)

为什么这里我们不使用try-with-resource 去自动关闭channel和connection?因为我们希望在消费者异步收听消息到达时,该进程保持活动状态。

接下来我们将告诉服务器从队列中把消息传送个我们。因为它会异步地向我们发送消息,所以我们以对象的形式提供一个回调用于缓冲消息,直到我们准备好使用它们。这就是DeliverCallback子类的作用。

/**
 *
 * JDK1.8及以上
 */
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    String message = new String(delivery.getBody(), "UTF-8");
    System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
/**
 *
 *JDK版本低于1.8使用此代码
 *
 */
DeliverCallback deliverCallback = new DeliverCallback() {
    @Override
    public void handle(String s, Delivery delivery) throws IOException {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    }
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, new CancelCallback() {
    @Override
    public void handle(String s) throws IOException {

    }
});

完整代码:

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

public class Recv {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        /*JDK版本低于1.8使用此代码
        DeliverCallback deliverCallback = new DeliverCallback() {
            @Override
            public void handle(String s, Delivery delivery) throws IOException {
                String message = new String(delivery.getBody(), "UTF-8");
                System.out.println(" [x] Received '" + message + "'");
            }
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, new CancelCallback() {
            @Override
            public void handle(String s) throws IOException {

            }
        });*/

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
        });
    }
}

 

运行结果

 

当我们每次运行生产者程序时,消费者就会打印出接受到的消息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值