RabbitMQ快速入门

RabbitMQ是一个流行的开源消息代理,用于处理异步任务和解耦系统。本文介绍了RabbitMQ的基本概念、特性、使用场景、安装步骤以及快速入门示例,包括点对点、发布/订阅、工作队列、路由和RPC模式。通过这些示例,读者可以了解如何在Java中使用RabbitMQ进行消息传递。
摘要由CSDN通过智能技术生成

一、RabbitMQ简介

RabbitMQ官网:https://www.rabbitmq.com/

With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers. From T-Mobile to Runtastic, RabbitMQ is used worldwide at small startups and large enterprises.

RabbitMQ拥有成千上万的用户,是最受欢迎的开源消息代理之一。从T-Mobile到Runtastic,RabbitMQ在全球范围内被用于小型初创企业和大型企业。

RabbitMQ is lightweight and easy to deploy on premises and in the cloud. It supports multiple messaging protocols. RabbitMQ can be deployed in distributed and federated configurations to meet high-scale, high-availability requirements.

RabbitMQ是轻量级的,易于在本地和云部署。它支持多种消息传递协议。RabbitMQ可以部署在分布式和联合配置中,以满足高规模、高可用性的需求。

RabbitMQ runs on many operating systems and cloud environments, and provides a wide range of developer tools for most popular languages.

RabbitMQ运行在许多操作系统和云环境上,并为大多数流行语言提供了广泛的开发工具。

1.1RabbitMQ Features

1.1.1Asynchronous Messaging异步消息传递

Supports multiple messaging protocols, message queuing, delivery acknowledgement, flexible routing to queues, multiple exchange type.

支持多种消息传递协议、消息队列、传递确认、灵活的队列路由、多种交换类型。

1.1.2Developer Experience

Deploy with BOSH, Chef, Docker and Puppet. Develop cross-language messaging with favorite programming languages such as: Java, .NET, PHP, Python, JavaScript, Ruby, Go, and many others.

1.1.3Distributed Deployment分布式部署

Deploy as clusters for high availability and throughput; federate across multiple availability zones and regions.

作为集群部署以获得高可用性和吞吐量;跨多个可用性区域和区域联合。

1.1.4Enterprise & Cloud Ready

Pluggable authenticationauthorisation, supports TLS and LDAP. Lightweight and easy to deploy in public and private clouds.

1.1.5Tools & Plugins

Diverse array of tools and plugins supporting continuous integration, operational metrics, and integration to other enterprise systems. Flexible plug-in approach for extending RabbitMQ functionality.

1.1.6Management & Monitoring管理与监控

HTTP-API, command line tool, and UI for managing and monitoring RabbitMQ.

二、RabbitMQ使用场景介绍

  • 异步处理:在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。如:用户注册成功后,发短信,发邮件...
  • 应用解藕:购物系统:下单成功后发送消息到MQ,库存系统接收MQ消息去库存;
  • 日志处理
  • 流量消峰:秒杀、发红包...
  • 延迟队列:订单超过30分钟未支付自动删除

三、RabbitMQ下载与安装

下载地址:https://www.rabbitmq.com/download.html

3.1window OS下载与安装

 

注意:WINDOWS操作系统下安装rabbitMQ前需先安装Erlang环境;下载以下两个文件:

 

先安装Erlang,即opt_win67_21.1.exe,直接“下一步”安装完成;然后安装rabbitmq-server-3.7.9.exe,直接“下一步”安装完成。安装完成后,在“开始”任务菜单可以看到rabbitmq的命令窗口:

 

然后在命令窗口输入以下命令:rabbitmq-plugins enable rabbitmq_management

 

服务已经默认开启,如果没有开启,可以输入命令手动开启:rabbitmq-server start

 

打开浏览器,在地址栏输入:http://localhost:15672,回车后进入rabbitmq的登录页面。

 

输入用户名和密码:guest/guest,登录成功!

Guest是超级管理员权限;

如果安装失败,请删除C:\Users\Administrator\AppData\Roaming\RabbitMQ这个文件夹再重新安装一次!

 

3.2Linux OS下载与安装

本博客使用的linux OS是ubuntu16.04版本;

# 安装erlang
apt-get install erlang-nox  
# 查看relang语言版本,成功执行则说明relang安装成功   
erl   
# 添加公钥
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
# 更新软件包
apt-get update
#安装成功自动启动
apt-get install rabbitmq-server  
#查看rabbitmq状态
sudo service rabbitmq-server status 
service rabbitmq-server start    # 启动
service rabbitmq-server stop     # 停止
service rabbitmq-server restart  # 重启
rabbitmq-plugins enable rabbitmq_management   # 启用插件
service rabbitmq-server restart    # 重启
# 查看用户
rabbitmqctl list_users

修改配置文件

# 复制配置文件,并重命名
cp /usr/share/doc/rabbitmq-server/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
# 修改配置文件,允许来宾账户访问rabbitmq
vim /etc/rabbitmq/rabbitmq.config

修改第61号:去掉注释符号(%%)和最后一个逗号(,);

# 重启服务
service rabbitmq-server restart   
# 开放http访问端口:15672
iptables -I INPUT -p tcp --dport 15672 -j ACCEPT
iptables-save
# 远程访问rabbitmq管理界面,用户名和密码均为guest

打开浏览器,在地址栏输入:http://ip:15672,回车后进入rabbitmq的登录页面。

用户名和密码均为guest。

四、RabbitMQ快速入门

4.1添加用户

4.2添加虚拟主机

4.3给用户授权

4.4入门实例

4.4.1几个概念

  • RabbitMQ 默认监听端口是5672
  • Broker:简单来说就是消息队列服务器实体。
  • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
  • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
  • Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
  • Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
  • vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。相当于mysql中的数据库的概念,不同的应用可以访问不同的虚拟主机;一个微服务或一个业务访问一个独立的虚拟主机;虚拟主机与特定用户绑定,该用户才有权限访问该虚拟主机;
  • producer:消息生产者,就是投递消息的程序。
  • consumer:消息消费者,就是接受消息的程序。
  • channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

五、RabbitMQ详细使用

  • RabbitMQ 生产者编码步骤

(1)客户端连接到消息队列服务器,打开一个channel。

(2)客户端声明一个exchange,并设置相关属性。

(3)客户端声明一个queue,并设置相关属性。

(4)客户端使用routing key,在exchange和queue之间建立好绑定关系。

(5)客户端投递消息到exchange。

exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。

  • RabbitMQ所需依赖

Download the client library and its dependencies (SLF4J API and SLF4J Simple).

		<! --普通项目所需依赖,此处省略了版本号 -->
        <dependency>		
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
		</dependency>
        <! --此处省略slf4j相关依赖,请自行添加 -->

        <! --Springboot项目所需依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>

官方样例:https://www.rabbitmq.com/getstarted.html

5.1、简单直连队列/点对点模式

消息生产者代码:

public class Sender {
	
	public static final String QUEUE_NAME="test_queue" ;
	public static void main(String[] args) {
		try {
			ConnectionFactory factory = new ConnectionFactory();
			factory.setHost("localhost");
			factory.setPort(5672);
			factory.setUsername("mq_test");
			factory.setPassword("mq_test");
			factory.setVirtualHost("/mq_test");
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			boolean durable = false;
			boolean exclusive = false;
			boolean autoDelete = true;
			// Declaring a queue is idempotent - it will only be created if it doesn't exist already.
			channel.queueDeclare(QUEUE_NAME, durable, exclusive, autoDelete, null);
			String message = "hello," + new Date();
			channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
			System.out.println("Send message : " + message);
			/** Note we can use a try-with-resources statement because both Connection and Channel implement java.io.Closeable. 
				This way we don't need to close them explicitly in our code.*/
			 channel.close();
			 connection.close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}
}

消息消费者代码:

public class Recv {
	
	public static final String QUEUE_NAME="test_queue" ;
	public static void main(String[] args) {
		try {
			ConnectionFactory factory = new ConnectionFactory();
			factory.setHost("localhost");
			factory.setPort(5672);
			factory.setUsername("mq_test");
			factory.setPassword("mq_test");
			factory.setVirtualHost("/mq_test");
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			/** Note this matches up with the queue that send publishes to.
			 	这里的参数配置必须与消息生产者保持一致  */
			boolean durable = false;
			boolean exclusive = false;
			boolean autoDelete = true;
			/** Note that we declare the queue here, as well. Because we might start the consumer before the publisher, 
				we want to make sure the queue exists before we try to consume messages from it.*/
			channel.queueDeclare(QUEUE_NAME, durable, exclusive, autoDelete, null);
			System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
			channel.basicConsume(QUEUE_NAME, new DefaultConsumer(channel) {
				@Override
				public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
						byte[] body) throws IOException {
					System.out.println(consumerTag);
					System.out.println(envelope);
					System.out.println("recv message is :" + new String (body));
				}
			});
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}
}

点对点模型存在的问题

若消费者消费消息太慢,会导致MQ消息的大量堆积。

 

5.2、Work queues工作队列/任务队列

 When you run many workers the tasks will be shared between them.One of the advantages of using a Task Queue is the ability to easily parallelise work. If we are building up a backlog of work, we can just add more workers and that way, scale easily.

By default, RabbitMQ will send each message to the next consumer, in sequence. On average every consumer will get the same number of messages. This way of distributing messages is called round-robin. 

默认情况下,RabbitMQ将按顺序将每条消息发送到下一个使用者。平均每个消费者将收到相同数量的消息。这种循环方式被称为消息分发。

生产者代码:

public class Sender {
	
	public static final String TASK_QUEUE_NAME = "test_task_queue";
	public static void main(String[] args) {
		try {
			ConnectionFactory factory = new ConnectionFactory();
			factory.setHost("127.0.0.1");
			factory.setPort(5672);
			factory.setUsername("mq_test");
			factory.setPassword("mq_test");
			factory.setVirtualHost("/mq_test");
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			boolean durable = false;
			boolean exclusive = false;
			boolean autoDelete = true;
			channel.queueDeclare(TASK_QUEUE_NAME, durable, exclusive, autoDelete, null);
			for(int i =0;i<10;i++) {
				String message = "hello," + i + ".";
				channel.basicPublish("", TASK_QUEUE_NAME, null, message.getBytes());
				System.out.println("Send message : " + message);
			}

			 channel.close();
			 connection.close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}

消息者代码:

public class Recv {
	
	public static final String TASK_QUEUE_NAME = "test_task_queue";
	public static void main(String[] args) {
		try {
			ConnectionFactory factory = new ConnectionFactory();
			factory.setHost("127.0.0.1");
			factory.setPort(5672);
			factory.setUsername("mq_test");
			factory.setPassword("mq_test");
			factory.setVirtualHost("/mq_test");
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			boolean durable = false;
			boolean exclusive = false;
			boolean autoDelete = true;
			channel.queueDeclare(TASK_QUEUE_NAME, durable, exclusive, autoDelete, null);
			System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
			channel.basicConsume(TASK_QUEUE_NAME, new DefaultConsumer(channel) {
				@Override
				public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
						byte[] body) throws IOException {
					System.out.println("Recv message is :" + new String (body));
					// Thread.sleep模拟处理消息
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
	}
}

处处启动三个消费模拟三个worker,通过控制台输出可以看出,每个worker轮流收到不一样的消息,且每条消息只会给一个消费者消费;

    

Message acknowledgment

在我们当前的代码中,一旦RabbitMQ向消费者传递了一条消息,它就会立即将其标记为删除。在这种情况下,如果您杀死worker,我们将丢失它正在处理的消息。我们还将丢失已发送给此特定工作线程但尚未处理的所有消息。但我们不想失去任何任务。如果一个worker死了,我们希望把任务交给另一个worker。为了确保消息不会丢失,RabbitMQ支持消息确认。使用者会发回一个确认,告诉RabbitMQ已经收到、处理了特定的消息,并且RabbitMQ可以自由删除它。如果一个使用者在没有发送ack的情况下死亡(其通道关闭、连接关闭或TCP连接丢失),RabbitMQ将理解消息未被完全处理,并将对其重新排队。如果有其他消费者同时在线,它会很快将其重新发送给另一个消费者。这样你就可以确保没有信息丢失,即使worker偶尔死亡。没有任何消息超时;RabbitMQ将在消费者死亡时重新传递消息。即使处理一条消息需要很长时间也没关系。默认情况下,手动消息确认处于启用状态(autoAck=true)。确认必须在接收传递的同一通道上发送。尝试使用不同的通道进行确认将导致通道级协议异常。

It's a common mistake to miss the basicAck. It's an easy error, but the consequences are serious. Messages will be redelivered when your client quits (which may look like random redelivery), but RabbitMQ will eat more and more memory as it won't be able to release any unacked messages.

忘记确认可是一个常见的错误。这是一个简单的错误,但后果是严重的。当您的客户机退出时,消息将被重新传递(这看起来像随机重新传递),但是RabbitMQ将消耗越来越多的内存,因为它无法释放任何未确认的消息。

Message durability

当RabbitMQ退出或崩溃时,它将忘记队列和消息,除非您告诉它不要这样做。要确保消息不会丢失,需要做两件事:我们需要将队列和消息都标记为持久的。

boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);

此queueDeclare需要同时应用于生产者代码和消费者代码。RabbitMQ不允许您使用不同的参数重新定义现有队列,并将向任何尝试重新定义的程序返回错误。

Now we need to mark our messages as persistent - by setting MessageProperties (which implements BasicProperties) to the value PERSISTENT_TEXT_PLAIN.

channel.basicPublish("", "task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN,  message.getBytes());

Note on message persistence关于消息持久性的说明

Marking messages as persistent doesn't fully guarantee that a message won't be lost. Although it tells RabbitMQ to save the message to disk, there is still a short time window when RabbitMQ has accepted a message and hasn't saved it yet. Also, RabbitMQ doesn't do fsync(2) for every message -- it may be just saved to cache and not really written to the disk. The persistence guarantees aren't strong, but it's more than enough for our simple task queue. If you need a stronger guarantee then you can use publisher confirms.

将消息标记为持久性并不能完全保证消息不会丢失。尽管它告诉RabbitMQ将消息保存到磁盘上,但是RabbitMQ接受了一条消息并且还没有保存它时,仍然有一个很短的时间窗口。而且,RabbitMQ并不是对每个消息都执行fsync(2)——它可能只是保存到缓存中,而不是真正写入磁盘。持久性保证不是很强,但是对于我们的简单任务队列来说已经足够了。如果您需要更强有力的担保,那么您可以使用publisher confirms。

Fair dispatch

你可能已经注意到上面案例中的均匀地发送消息仍然不能完全按照我们的要求工作。例如,有两个消费者,当所有的奇数消息都是重消息(heavy )而偶数消息都是轻消息(light)时,一个worker将持续忙碌,而另一worker几乎不做任何工作,而RabbitMQ对此一无所知,仍然会均匀地发送消息。这是因为RabbitMQ只是在消息进入队列时发送消息。它不考虑消费者未确认消息的数量。它只是盲目地将第n条消息发送给第n个消费者。为了避免这种情况,我们可以使用basicQos方法,并设置prefetchCount=1。这告诉RabbitMQ不要一次向一个worker发出多个消息。或者,换句话说,在处理并确认前一条消息之前,不要向worker发送新消息。相反,它将把它发送给下一个不忙的worker。

int prefetchCount = 1;
channel.basicQos(prefetchCount);

Note about queue size关于队列大小的说明

If all the workers are busy, your queue can fill up. You will want to keep an eye on that, and maybe add more workers, or have some other strategy.

如果所有的workers 都很忙,你的queue 可能会满的。你会想继续关注这一点,也许会增加更多的workers,或者有一些其他的策略。

综合案例

一个包含:durable、autoAck、basicQos的综合案例

生产者代码:

            // 省略其他代码
			Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
			boolean durable = true;
			boolean exclusive = false;
			boolean autoDelete = false;
			channel.queueDeclare(TASK_QUEUE_NAME, durable, exclusive, autoDelete, null);
			for(int i =0;i<50;i++) {
				String message = "hello," + i + ".";
				channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
				System.out.println("Send message : " + message);
			}
            // 省略其他代码

消费者代码:

            // 省略其他代码            
            Connection connection = factory.newConnection();
			Channel channel = connection.createChannel();
            // maximum number of messages that the server will deliver, 0 if unlimited
            // Note the prefetch count must be between 0 and 65535 (unsigned short in AMQP 0-9-1).
			int prefetchCount = 1;
			channel.basicQos(prefetchCount);
			boolean durable = true;
			boolean exclusive = false;
			boolean autoDelete = false;
			channel.queueDeclare(TASK_QUEUE_NAME, durable, exclusive, autoDelete, null);
			System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
			boolean autoAck = false;
			channel.basicConsume(TASK_QUEUE_NAME, autoAck,new DefaultConsumer(channel) {
				@Override
				public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
						byte[] body) throws IOException {
					System.out.println("Recv message is :" + new String (body));
					// Thread.sleep模拟处理消息
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("Recv done");
					// multiple 如果为true,则确认所有消息(包括提供的传递标记);如果为false,则仅确认提供的传递标记
					boolean multiple  = true;
					channel.basicAck(envelope.getDeliveryTag(), multiple);
				}
			});

work queue总结

1、autoAck默认值为true,即消费者收到消息即自动确认(不管消息是否消息成功),消息不会在MQ中堆积,若消费者在处理消息时遇到异常,则消息会丢失;

2、在业务中要想实现公平分发、能者多劳,生产者在处理完消息后必须手动ack,即channel.basicAck(envelope.getDeliveryTag(), multiple);并且设置channel.basicQos(1),即消费者每次只能消费一条消息;

5.3、Publish/Subscribe发布/订阅模式(FANOUT)

广播模型(FANOUT)模式消息发送流程是这样的,比如:注册用户成功后发送邮件、发送短信、增加积分......再比如,订单支付成功后,扣减库存、更新购物车、增加订单...

  • 可以有多个消费者;
  • 每个消费者有自己的队列;
  • 每个队列都要绑定到exchange;
  • 生产者将消息发送到exchange,交换机来决定要发送到哪个队列;生产者无法决定 ;
  • 交换机将消息发送给绑定过的所有队列;
  • 队列的消费者都能拿到消息。实现一条消费被多个消费者消费;

The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we'll do something completely different -- we'll deliver a message to multiple consumers. This pattern is known as "publish/subscribe".

为了说明这种模式,我们将构建一个简单的日志系统。它将由两个程序组成:第一个程序将发出日志消息,第二个程序将接收并打印它们。

Exchanges

The core idea in the messaging model in RabbitMQ is that the producer never sends any messages directly to a queue. Actually, quite often the producer doesn't even know if a message will be delivered to any queue at all.

RabbitMQ中消息传递模型的核心思想是,生产者从不直接向队列发送任何消息。实际上,生产者常常根本不知道消息是否会被传递到任何队列。

Instead, the producer can only send messages to an exchange. An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type.

相反,生产者只能向交换机发送消息。交换机是一件很简单的事。一方面它接收来自生产者的消息,另一方面它将消息推送(push)到队列中。交换必须确切知道如何处理它接收到的消息。是否应该将其附加到特定队列?是否应该将其附加到多个队列中?或者应该被丢弃。其规则由交换类型定义。

There are a few exchange types available: direct, topic, headers and fanout. We'll focus on the last one -- the fanout. Let's create an exchange of this type, and call it logs:

The fanout exchange is very simple. As you can probably guess from the name, it just broadcasts all the messages it receives to all the queues it knows. And that's exactly what we need for our logger.

The fanout exchange将它接收到的所有消息广播到它知道的所有队列。

# To list the exchanges on the server you can run the ever useful rabbitmqctl:

sudo rabbitmqctl list_exchanges

# 在此列表中,将有一些amq.*交换和默认(未命名)交换。这些都是默认创建的

Nameless exchange

In previous parts of the tutorial we knew nothing about exchanges, but still were able to send messages to queues. That was possible because we were using a default exchange, which we identify by the empty string ("").

Now, we can publish to our named exchange instead:

channel.basicPublish( "logs", "", null, message.getBytes());

Temporary queues

在这个收集日志的需求中,当我们连接到Rabbit时,我们需要一个新的、空的队列。为此,我们可以创建一个随机名称的队列,或者更好的方法是让服务器为我们选择一个随机队列名称。其次,一旦我们断开消费者的连接,队列就会自动删除。在Java客户机中,当我们不向queueDeclare()提供参数时,我们将创建一个非持久的、排他的、自动删除的队列,并生成一个名称:

String queueName = channel.queueDeclare().getQueue();

Bindings

We've already created a fanout exchange and a queue. Now we need to tell the exchange to send messages to our queue. That relationship between exchange and a queue is called a binding.

综合案例

每个订阅者都会收到交换器发出的消息 因为发布者发布消息的routingkey是空  订阅者接受消息队列的routingkey也是空 发布者发布的消息 所有的订阅者都能接收到

生产者代码:

public class PublishLogs {
	public static final String EXCHANGE_NAME = "log_exchange";
	
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 声明一个fanout类型的exchange
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
		for (int i = 0; i < 10; i++) {
			String message = "info: hello," + new Date();
			/** 每个订阅者都会收到交换器发出的消息,因为发布者发布消息的routingkey是空
				订阅者接受消息队列的routingkey也是空 发布者发布的消息 所有的订阅者都能接收到 */
			String routingKey = "";
			channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
			System.out.println("[X] send message :" + message);
		}
		
		channel.close();
		connection.close();
	}
}

消息者代码:

public class ReceiveLogs {
	
	public static final String EXCHANGE_NAME = "log_exchange";
	
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
		String queue = channel.queueDeclare().getQueue();
		String routingKey = "";
		channel.queueBind(queue,EXCHANGE_NAME, routingKey);
		channel.basicConsume(queue, new DefaultConsumer(channel) {

			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("Recv:" + new String(body));
			}
		});
	}
}

5.4、Routing路由模式(DIRECT)

direct模型工作流程:

  • 队列与交换机绑定,不能是任意绑定了,而是要指定一个routingKey;
  • 消息的发送方在向exchange发送消息时,也必须指定消息的routingKey;
  • exchange不再把消息交给每一个绑定的队列,而是根据消息的routingKey进行判断 ,只有队列有的routingKey与消息的routingKey完全一致,才会接收到消息;

在上一个教程中,我们构建了一个简单的日志记录系统。我们可以向订阅者广播日志信息。在本教程中,我们将只将 error消息保存到日志文件(允许根据消息的严重性过滤消息,以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。

Bindings can take an extra routingKey parameter. To avoid the confusion with a basic_publish parameter we're going to call it a binding key. This is how we could create a binding with a key:

channel.queueBind(queueName, EXCHANGE_NAME, "black");

The meaning of a binding key depends on the exchange type. The fanout exchanges, which we used previously, simply ignored its value.

Direct exchange

The routing algorithm behind a direct exchange is simple - a message goes to the queues whose binding key exactly matches the routing key of the message.

direct exchange的路由算法很简单——消息被发送到binding key与routing key完全匹配的队列。

In such a setup a message published to the exchange with a routing key orange will be routed to queue Q1. Messages with a routing key of black or green will go to Q2. All other messages will be discarded.

It is perfectly legal to bind multiple queues with the same binding key. In our example we could add a binding between X and Q1 with binding key black. In that case, the direct exchange will behave like fanout and will broadcast the message to all the matching queues. A message with routing key black will be delivered to both Q1 and Q2.

用同一个binding key绑定多个队列是完全合法的。在我们的例子中,我们可以在X和Q1之间添加一个绑定,绑定键为black。在这种情况下,直接交换将表现为fanout 并将消息广播到所有匹配的队列。routing key为 black的消息将同时传送到Q1和Q2。

综合案例

生产者代码:

public class PublishLog {
	
	public static final String DIRECT_EXCHANGE_NAME = "log_direct_exchange";
	
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(DIRECT_EXCHANGE_NAME, BuiltinExchangeType.DIRECT,true,false,null);
		for (int i = 0; i < 10; i++) {
			String message = null;
			String routingKey = null;
			if (i % 2 == 0) {
				message = "erorr:" + i + "," + new Date();
				routingKey = "error";
				// error日志需要持久化并写入文件
				channel.basicPublish(DIRECT_EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
			} else {
				routingKey = "info";
				message = "info:" + i + "," + new Date();
				channel.basicPublish(DIRECT_EXCHANGE_NAME, routingKey, null, message.getBytes());
			}
		}
		channel.close();
		connection.close();
	}
}

消息者代码:

public class ConsoleRecv {

	public static final String DIRECT_EXCHANGE_NAME = "log_direct_exchange";
	
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(DIRECT_EXCHANGE_NAME, BuiltinExchangeType.DIRECT,true,false,null);
		String queue = channel.queueDeclare().getQueue();
        // 此处可以绑定多个routingKey
		channel.queueBind(queue, DIRECT_EXCHANGE_NAME, "error");
		channel.queueBind(queue, DIRECT_EXCHANGE_NAME, "info");
		channel.basicConsume(queue, new DefaultConsumer(channel) {

			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println(envelope.getRoutingKey());
				System.out.println("ConsoleRecv:" + new String(body));
			}
		});
//		channel.close();
//		connection.close();
	}
}
public class ErrorRecv {

	public static final String DIRECT_EXCHANGE_NAME = "log_direct_exchange";
	
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(DIRECT_EXCHANGE_NAME, BuiltinExchangeType.DIRECT,true,false,null);
		String queue = channel.queueDeclare().getQueue();
		String routingKey = "error";
		channel.queueBind(queue, DIRECT_EXCHANGE_NAME, routingKey);
		channel.basicConsume(queue, new DefaultConsumer(channel) {

			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println(envelope.getRoutingKey());
				System.out.println("ErrorRecv:" + new String(body));
				try {
					// 将日志写入文件
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
//		channel.close();
//		connection.close();
	}
}

5.5、Topics动态路由

在上一个教程中,我们改进了日志记录系统。我们没有使用只能进行虚拟广播的fanout exchange,而是使用了direct exchange,并获得了选择性接收日志的可能性。虽然使用direct exchange改进了我们的系统,但它仍然有局限性-它不能基于多个标准进行路由。

在我们的日志系统中,我们可能不仅要订阅基于error的日志,还需要知道日志的来源。为了在我们的日志系统中实现这一点,我们需要了解一个更复杂的topic exchange。

Topic exchange

消息的routingkey 是通过.隔开的多个字符组成。字符可以是任何内容,但通常它们指定与消息相关的一些特性。一些有效的routing key 实例,如:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit". 路由键中可以有任意多个字,最多不超过255个字节。订阅者的消息队列绑定的routingkey可以使用通配符通配所有满足条件的交换机消息 。匹配上则接收消息。

there are two important special cases for binding keys:

* (star) can substitute for exactly one word.

# (hash) can substitute for zero or more words.

接收者接收消息可以设置routingkey为表达式模糊匹配 : *匹配一个单词 #匹配零个或多个

In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe speed, second a colour and third a species:

"<speed>.<colour>.<species>".

We created three bindings: Q1 is bound with binding key "*.orange.*" and Q2 with "*.*.rabbit" and "lazy.#".

These bindings can be summarised as:

  • Q1 is interested in all the orange animals.Q1对所有橙色动物都感兴趣。
  • Q2 wants to hear everything about rabbits, and everything about lazy animals.Q2想听关于兔子的一切,以及关于懒惰动物的一切。

A message with a routing key set to "quick.orange.rabbit" will be delivered to both queues. Message "lazy.orange.elephant" also will go to both of them. On the other hand "quick.orange.fox" will only go to the first queue, and "lazy.brown.fox" only to the second. "lazy.pink.rabbit" will be delivered to the second queue only once, even though it matches two bindings. "quick.brown.fox" doesn't match any binding so it will be discarded.

What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.

如果我们违反约定,用一个或四个字,比如“orange”或"quick.orange.male.rabbit"?好吧,这些消息将与任何绑定不匹配,并且将丢失。

On the other hand "lazy.orange.male.rabbit", even though it has four words, will match the last binding and will be delivered to the second queue.

另一方面”lazy.orange.male.rabbit”,即使它有四个单词,也将匹配最后一个绑定并将被传递到第二个队列。

Topic exchange

Topic exchange is powerful and can behave like other exchanges.

When a queue is bound with "#" (hash) binding key - it will receive all the messages, regardless of the routing key - like in fanout exchange.

When special characters, "*" (star) and "#" (hash), aren't used in bindings, the topic exchange will behave just like a direct one.

案例代码

生产者代码:

public class TopicProducer {
	public static final String EXCHANGE_NAME = "test_topic_exchange";
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC,true,false,null);
		String routingKey = "#";
		for (int i = 0; i < 10; i++) {
			switch (i) {
			case 0:
				routingKey = "quick.orange.rabbit";
				break;
			case 1:
				routingKey = "lazy.orange.elephant";
				break;
			case 2:
				routingKey = "quick.orange.fox";
				break;
			case 3:
				routingKey = "lazy.brown.fox";
				break;
			case 4:
				routingKey = "lazy.pink.rabbit";
				break;
			case 5:
				routingKey = "quick.brown.fox";
				break;
			case 6:
				routingKey = "orange";
				break;
			case 7:
				routingKey = "quick.orange.male.rabbit";
				break;
			case 8:
				routingKey = "lazy.orange.male.rabbit";
				break;
			case 9:
				routingKey = "*";
				break;
			default:
				break;
			}
			channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, routingKey.getBytes());
		}
		channel.close();
		connection.close();
	}
}

消息者代码:

	public static final String EXCHANGE_NAME = "test_topic_exchange";
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC,true,false,null);
		// * (star) can substitute for exactly one word
		String routingKey = "*.orange.*";
		String queue = channel.queueDeclare().getQueue();
		channel.queueBind(queue, EXCHANGE_NAME, routingKey);
		channel.basicConsume(queue, new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				//System.out.println("[X] RoutingKey : " +  envelope.getRoutingKey());
				System.out.println("[X] Topic Recv: " + new String(body));
			}
		});
	}
}

5.6、RPC远程调用模式

在worker queue教程中,我们将学习如何使用工作队列在多个worker之间分配耗时的任务。但是如果我们需要在远程计算机上运行一个函数并等待结果呢?好吧,那是另一回事了。这种模式通常称为远程过程调用或RPC。

在本教程中,我们将使用RabbitMQ构建一个RPC系统:一个客户端和一个可伸缩的RPC服务器。由于我们没有任何值得分发的耗时任务,我们将创建一个返回Fibonacci numbers的虚拟RPC服务。

Client interface

To illustrate how an RPC service could be used we're going to create a simple client class. It's going to expose a method named call which sends an RPC request and blocks until the answer is received:

为了说明如何使用RPC服务,我们将创建a simple client class。它将暴露一个名为call的方法,该方法发送一个RPC请求并阻塞,直到收到响应。

A note on RPC

Bearing that in mind, consider the following advice:

  1. Make sure it's obvious which function call is local and which is remote.
  2. Document your system. Make the dependencies between components clear.
  3. Handle error cases. How should the client react when the RPC server is down for a long time?异常处理。当RPC服务调用超时或宕机时客户端如何处理?

When in doubt avoid RPC. If you can, you should use an asynchronous pipeline - instead of RPC-like blocking, results are asynchronously pushed to a next computation stage.

Callback queue

一般来说,在RabbitMQ上执行RPC是很容易的。客户端发送 request message,服务器用response message进行响应。我们需要在发送请求时send a 'callback' queue address。我们可以使用default queue(在Java客户机中是独占的)。

String callbackQueueName = channel.queueDeclare().getQueue();

BasicProperties props = new BasicProperties

                            .Builder()

                            .replyTo(callbackQueueName)

                            .build();

 

channel.basicPublish("", RPC_QUEUE_NAME, props, message.getBytes());

 

Message properties

The AMQP 0-9-1 protocol predefines a set of 14 properties that go with a message. Most of the properties are rarely used, with the exception of the following:

amqp0-9-1协议预先定义了14个属性,这些属性与消息一起使用。除以下情况外,大多数属性很少使用:

  1. deliveryMode: Marks a message as persistent (with a value of 2) or transient (any other value). You may remember this property from the second tutorial.

deliveryMode:将消息标记为持久(值为2)或瞬态(其他任何值)。

  1. contentType: Used to describe the mime-type of the encoding. For example for the often used JSON encoding it is a good practice to set this property to: application/json.

contentType:用于描述编码的mime-type。例如,对于经常使用的JSON编码,将该属性设置为:application/JSON。

  1. replyTo: Commonly used to name a callback queue.

replyTo:通常用于命名回调队列

  1. correlationId: Useful to correlate RPC responses with requests.

correlationId:用于将RPC响应与请求关联。

Correlation Id

在上面介绍的方法中,我们建议为每个RPC请求创建一个回调队列。这相当低效,但幸运的是有一个更好的方法-让我们为每个客户端创建一个回调队列。

这引发了一个新问题,在该队列中接收到响应后,不清楚该响应属于哪个请求。此时将使用correlationId属性。我们将为每个请求设置一个唯一的值。稍后,当我们在回调队列中收到消息时,我们将查看此属性,并基于该属性,我们将能够将响应与请求相匹配。如果我们看到一个未知的correlationId值,我们可以安全地丢弃该消息-它不属于我们的请求。

您可能会问,为什么我们应该忽略回调队列中的未知消息,而不是因为错误而失败?这是由于服务器端可能存在争用情况。虽然不太可能,但是RPC服务器有可能在向我们发送应答之后,但在发送请求的确认消息之前死亡。如果发生这种情况,重新启动的RPC服务器将再次处理该请求。这就是为什么在客户机上我们必须优雅地处理重复的响应,RPC在理想情况下应该是幂等的。

RPC调用过程

 

  • 对于一个RPC请求,客户机发送一个包含两个属性的消息:replyTo和correlationId,前者被设置为只为请求创建的匿名独占队列,后者被设置为每个请求的唯一值。
  • 请求被发送到rpc_queue队列中。
  • RPC工作线程(又名:服务器)正在等待该队列上的请求。当一个请求出现时,它执行该任务,并使用replyTo字段中的队列将结果发送回客户机。
  • 客户机等待应答队列上的数据。当消息出现时,它检查correlationId属性。如果它与请求中的值匹配,则将响应返回给应用程序。

RPC客户端代码

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

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

/**
 * RPCClient
 * 	1)建立连接、通道和声明队列;
 *	2)生成一个唯一的correlationId号并保存它-我们的使用者回调将使用这个值来匹配适当的响应。
 *	3)我们为应答创建一个专用的独占队列并订阅它。
 *	4)发布请求消息,包含两个属性:replyTo和correlationId。
 */
public class FibonacciRpcClient {

	// 客户端推送消息的队列
	public static final String RPC_REQ_QUEUE_NAME = "test_req_rpc_queue";
	// 客户端接收响应的队列
	public static final String RPC_RESP_QUEUE_NAME = "test_resp_rpc_queue";

	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.queueDeclare(RPC_REQ_QUEUE_NAME, true, false, false, null);
		channel.queueDeclare(RPC_RESP_QUEUE_NAME, true, false, false, null);
		
		// generate a unique correlationId number and save it - our consumer callback will use this value to match the appropriate response
		String correlationId = UUID.randomUUID().toString();
		// We need this new import:import com.rabbitmq.client.AMQP.BasicProperties;
		BasicProperties props = new BasicProperties.Builder()
				// .contentType("") // 编码的mime-type,例如application/JSON
				.correlationId(correlationId) // client端每个请求的唯一值,将RPC响应与请求关联
				.deliveryMode(2) // Marks a message as persistent (with a value of 2) or transient (any othervalue)
				.replyTo(RPC_RESP_QUEUE_NAME) // 回调队列
				.build();
		for(int i=0;i<10;i++) {
			String message =  i + ",how are you?";
			channel.basicPublish("", RPC_REQ_QUEUE_NAME, props, message.getBytes());
		}

		System.out.println("[X] waiting for response from RPC Server!!!!!!!!");
		channel.basicConsume(RPC_RESP_QUEUE_NAME, new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				if(properties.getCorrelationId().equals(correlationId)) {
					System.out.println( new Date() + ": Response from RPC Server : " + new String(body));
				}
			}
		});
	}
}

RPC服务端代码

/**
 * RPC服务端 
 * 1)建立连接、通道和声明队列;
 * 2)channel.basicQos设置公平分发;
 * 3)DeliverCallback执行完任务后给client响应;
 */
public class FibonacciRpcServer {
	// 服务端接收消息的队列
	public static final String RPC_REQ_QUEUE_NAME = "test_req_rpc_queue";
	// 服务端响应消息的队列
	public static final String RPC_RESP_QUEUE_NAME = "test_resp_rpc_queue";

	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		channel.queueDeclare(RPC_REQ_QUEUE_NAME, true, false, false, null);
		channel.queueDeclare(RPC_RESP_QUEUE_NAME, true, false, false, null);
		DeliverCallback deliverCallback = new DeliverCallback() {
			@Override
			public void handle(String consumerTag, Delivery delivery) throws IOException {
				String message = new String(delivery.getBody());
				System.out.println("[X] RPC Server recv :" + message);
				try {
					// 处理客户端的消息
					Thread.sleep(1000 *3);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				// 响应客户端的消息
				AMQP.BasicProperties replyProps = new AMQP.BasicProperties
						.Builder()
						.correlationId(delivery.getProperties().getCorrelationId())
						.build();
				String responseMsg = "I'm fine,thank you!";
				channel.basicPublish("", RPC_RESP_QUEUE_NAME, replyProps, responseMsg.getBytes());

				channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
			}
		};
		boolean autoAck = false;
		channel.basicConsume(RPC_REQ_QUEUE_NAME, autoAck, deliverCallback, (consumerTag -> {
		}));
	}
}

5.7、Publisher Confirms发布确认模式

官网地址:https://www.rabbitmq.com/tutorials/tutorial-seven-java.html

Publisher confirms是一个RabbitMQ扩展,用于实现可靠的发布。当在channel上开启publisher confirms时,客户端发布的消息将由broker异步确认,这意味着它们已在服务器端得到处理。

Enabling Publisher Confirms on a Channel

Publisher confirms are a RabbitMQ extension to the AMQP 0.9.1 protocol, so they are not enabled by default. Publisher confirms are enabled at the channel level with the confirmSelect method:

Channel channel = connection.createChannel();

channel.confirmSelect();

This method must be called on every channel that you expect to use publisher confirms. Confirms should be enabled just once, not for every message published.

必须在您希望使用publisher confirms的每个channel 上调用此方法。确认应该只启用一次,而不是对发布的每个消息启用一次。

Strategy #1: Publishing Messages Individually单独发布消息

消息生产者代码

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

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
 * 这种技术非常简单,但也有一个主要缺点:它显著地减慢了发布速度,因为消息的确认会阻止所有后续消息的发布。
 * 这种方法不会每秒传递超过几百条已发布消息的吞吐量。然而,这对于某些应用程序来说是足够好的。
 */
public class Sender {
	public static final String PUBCONFIRM_TEST_QUEUE = "pubconfirm_test_queue";
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 开启channel的confirm模式
		channel.confirmSelect();
		channel.queueDeclare(PUBCONFIRM_TEST_QUEUE, true, false, false, null);
		for(int i=0;i<10;i++) {
			String message = i + " hello," + new Date();
			channel.basicPublish("", PUBCONFIRM_TEST_QUEUE, null, message.getBytes());
			try {
				long timeout = 1000;
				/**如果超时过期,则抛出java.util.concurrent.TimeoutException
				 * 	如果有任何消息是nack'd,waitForConfirmsOrDie将抛出一个IOException。
				 * 	当在非确认通道上调用时,它将抛出java.lang.IllegalStateException: Confirms not selected
				 */
				channel.waitForConfirmsOrDie(timeout);
			} catch (Exception e) {
				// 异常的处理通常包括记录错误消息和/或重试发送消息。
				e.printStackTrace();
			}
		}
	}
}

消息消费者代码

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

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class Recv {

	public static final String PUBCONFIRM_TEST_QUEUE = "pubconfirm_test_queue";
	public static void main(String[] args) throws IOException, TimeoutException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("localhost");
		factory.setPort(5672);
		factory.setUsername("mq_test");
		factory.setPassword("mq_test");
		factory.setVirtualHost("/mq_test");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 开启channel的confirm模式
		channel.confirmSelect();
		channel.queueDeclare(PUBCONFIRM_TEST_QUEUE, true, false, false, null);
		channel.basicConsume(PUBCONFIRM_TEST_QUEUE,false, new DefaultConsumer(channel) {

			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("[X] Recv :" + new String(body));
				// 处理消息
				try {
					Thread.sleep(1000 * 5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		});
	}
}

Strategy #2: Publishing Messages in Batches批量发布消息

 

Strategy #3: Handling Publisher Confirms Asynchronously异步处理Publisher Confirms

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值