消息队列——RabbitMQ
25r9n4qy8
8年前
<h2><strong>1. 写在前面</strong></h2> <p>昨天简单学习了一个 <strong>消息队列</strong> 项目—— <strong>RabbitMQ</strong> ,今天趁热打铁,将学到的东西记录下来。</p> <p>学习的资料主要是官网给出的6个基本的消息发送/接收 模型 ,或者称为6种不同的使用场景,本文便是对这6种模型加以叙述。</p> <h2><strong>2. Tutorials</strong></h2> <p>在学习6种模型之前,我们首先需要安装RabbitMQ。RabbitMQ支持多种系统平台,各平台的安装方法。安装好之后,我们使用如下命令启用Web端的管理插件: rabbitmq-plugins enable rabbitmq_management ,然后启动RabbitMQ。接着用浏览器访问 http://localhost:15672/ ,若能看到RabbitMQ相关Web页面,说明启动成功。</p> <h3><strong>2.1 Hello World</strong></h3> <p>正所谓万事开头难,我们先从最简单的 <strong>Hello World</strong> 开始。首先当然是新建一个项目,导入RabiitMQ相关jar。我采用Maven来构建项目,因此只需要在pom文件中添加如下依赖:</p> <pre> <code class="language-dust"><dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>3.6.5</version> </dependency></code></pre> <p>接下来学习最简单的消息队列模型,如下图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ec1c1ed3525c8971c26f12620578e287.png"></p> <p>在图中,P代表 producer,它是消息的 <strong>生产者</strong>;C代表 consumer ,它是消息的 <strong>消费者</strong> ;而红色的矩形正是我们所谓的 <strong>消息队列</strong> ,它位于 RabbitMQ 中( RabbitMQ 中可以有很多这样的队列,并且每个队列都有一个唯一的名字)。生产者(们)可以将消息发送到消息队列中,消费者(们)可以从消息队列中取出消息。</p> <p>这种模型是不是很简单呢?下面我们使用 <strong>Java</strong> ,借助于RabbitMQ来实现这种模型的消息通信。</p> <p>首先我们介绍如何 send 消息到消息队列。 send 之前,当然是和RabbitMQ服务器建立连接:</p> <pre> <code class="language-dust">ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection();</code></pre> <p>接下来我们创建一个 channel ,大多数API都是通过这个对象来调用的:</p> <pre> <code class="language-dust">Channel channel = connection.createChannel();</code></pre> <p>之后,我们便可以调用 channel 的如下方法去声明一个队列:</p> <pre> <code class="language-dust">channel.queueDeclare("hello", false, false, false, null);</code></pre> <p>该方法的第一个参数是队列的名称,其余的参数先不管,之后会介绍。我们可以尝试着去执行以上的5行代码,然后打开Web端,可以看到新建了一个叫作 hello 的队列:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/665fba8e97cce8c23d613ee73c687ce8.png"></p> <p>有了队列,我们便可以向其中发送消息了,同样还是调用 channel 对象的API:</p> <pre> <code class="language-dust">channel.basicPublish("", "hello", null, "Hello World".getBytes());</code></pre> <p>以上代码所做的事情就是发送了一条字符串消息“Hello World”(第4个参数)到消息队列。你可能注意到我们调用了String对象的 getBytes 方法,没错,我们发送的实际上二进制数据。因此,理论上你能够发送任何数据到消息队列中,而不仅仅是文本信息。</p> <p>第2个参数叫做 <strong>路由键(routingKey)</strong> ,在该模型下必须与队列名相同,至于为什么,和其他参数一样,之后会了解到。</p> <p>我们可以修改发送的文本,再次执行上述代码,然后打开Web端查看,便可以查看到我们发送的消息:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/5179cbeaa84d39c7b98d0d717b59f1f9.png"></p> <p>点击上图的 <strong>name</strong> 字段下的 <strong>hello</strong> ,可以查看 hello 队列中的具体信息:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/909a48393b1a4b18f5e672dc05a4421d.png"></p> <p>接下来,我们去尝试着去获取 <strong>生产者</strong> 发送的消息,和 send 方法一样,我们同样需要连接服务器,创建 channel ,声明队列:</p> <pre> <code class="language-dust">ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("hello", false, false, false, null);</code></pre> <p>之后我们可以调用 channel 的相关方法去监听队列,接收消息:</p> <pre> <code class="language-dust">channel.basicConsume("hello", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } });</code></pre> <p>以上 basicConsume 方法中,第一个参数是队列的名字;第二个参数表示是否自动确认消息的接收情况,我们使用true,自动确认;第三个参数需要传入一个实现了 Consumer 接口的对象,我们简单的 new 一个默认的 Consumer 的实现类 DefaultConsumer ,然后在 handleDelivery 方法中去处理接收到的消息( handleDelivery 方法会在接收到消息时被回调)。</p> <p>运行以上代码,我们可以打印出之前向队列中 send 的数据:</p> <pre> <code class="language-dust">Hello World Hello World2</code></pre> <p>下面是 <strong>Hello World</strong> 的完整代码:</p> <pre> <code class="language-dust">public class App { @Test public void send() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("hello", false, false, false, null); channel.basicPublish("", "hello", null, "Hello World2".getBytes()); channel.close(); connection.close(); } @Test public void receive() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("hello", false, false, false, null); channel.basicConsume("hello", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }); synchronized (this){ // 因为以上接收消息的方法是异步的(非阻塞),当采用单元测试方式执行该方法时,程序会在打印消息前结束,因此使用wait来防止程序提前终止。若使用main方法执行,则不需要担心该问题。 wait(); } } }</code></pre> <h3><strong>2.2 Work queues</strong></h3> <p>接下来我们学习第二种模型—— <strong>Work Queues</strong> 。顾名思义,这种模型描述的是一个生产者(Boss)向队列发消息(任务),多个消费者(worker)从队列接受消息(任务),如下图所示:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0fde8ffebb772c7d486921464ae55a29.png"></p> <p>下面我们用代码去实现。先是生产者 send 消息到队列,这次我们多发送些数据:</p> <pre> <code class="language-dust">@Test public void send() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("hello", false, false, false, null); for (int i = 0; i < 9; i++) { channel.basicPublish("", "hello", null, String.valueOf(i).getBytes()); } channel.close(); connection.close(); }</code></pre> <p>然后是消费者接收数据:</p> <pre> <code class="language-dust">@Test public void receive() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("hello", false, false, false, null); channel.basicConsume("hello", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); try { // Thread.sleep(1000); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }); synchronized (this) { wait(); } }</code></pre> <p>代码基本上和 Hello World 的代码一样,只是加上句 sleep 来模拟消费者(worker)处理消息所花的时间。</p> <p>我们可以先执行三次 receive 方法(修改 sleep 的时间,其中消费者1 sleep 10s,消费者2,3 sleep 1s),让三个消费者(worker)一起等待消息的到来,然后执行 send 方法发送9条消息,观察三个消费者收到的消息情况。</p> <p>若不出意外,你会看到如下的打印结果:</p> <pre> <code class="language-dust">// 消费者1 0 // 10s 后 3 // 10s 后 6 // 消费者2 1 // 1s 后 4 // 1s 后 7 // 消费者3 2 // 1s 后 5 // 1s 后 8</code></pre> <p>通过打印结果,我们可以总结出 <strong>Work queues</strong> 的几个特点:</p> <ol> <li>一条消息只会被一个消费者接收;</li> <li>消息是平均分配给消费者的;</li> <li>消费者只有在处理完某条消息后,才会收到下一条消息。</li> </ol> <p>Work queues(Task Queuess)的概念在一些Web场景的应用中是很有用的,比如我们能够用它来构建一个master-slave结构的分布式爬虫系统:系统中有一个master节点和多个slave节点,master节点负责向各个slave节点分配爬取任务。</p> <h3><strong>2.3 Publish/Subscribe</strong></h3> <p>但有些时候,我们可能希望一条消息能够被多个消费者接受到,比如一些公告信息等,这时候用 <strong>Work Queue</strong> 模型显然不合适,而 <strong>Publish/Subscribe</strong> 模型正是对应这种使用场景的。</p> <p>在介绍Publish/Subscribe之前,我们快速回顾之前的两个模型,它们好像都是生产者将消息直接发送到消息队列,但其实不是这样的,甚至有可能生产者根本就不知道消息发送到了哪一个消息队列。</p> <p>先别着急,下面我们完整地介绍RabbitMQ消息发送/接受的方式。</p> <p>事实上,生产者是把消息发送到了 <strong>交换机(exchange)</strong> 中,然后交换机负责(决定)将消息发送到(哪一个)消息队列中。其模型如下图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/78834739c797fabd64b5dbec0863752b.jpg"></p> <p>这时候你可能会疑惑:既然消息是被发送到了交换机中,那我们之前发送的消息是被发送到了哪一个交换机中了?它有没有机制能够让特定的消息发送到指定的队列?</p> <p>先回答第一个问题。还记得我们在 <strong>Hello World</strong> 中写的发送消息的代码吗?</p> <pre> <code class="language-dust">channel.basicPublish("", "hello", null, message.getBytes());</code></pre> <p>事实上第一个参数便是指定交换机的名字,即指定消息被发送到哪一个交换机。空字符串表示 <strong>默认交换机(Default Exchange)</strong> ,即我们之前发送的消息都是先发送到 <strong>默认交换机</strong> ,然后它再路由到相应的队列中。其实我们可以通过Web页面去查看所有存在的交换机:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e559a4c7b1fe908d341347bb43a1b657.png"></p> <p>接着回答第二个问题。路由的依据便是通过第二个参数—— <strong>路由键(routing key)</strong> 指定的,之前已经提到过。在之前代码中,我们指定第二个参数为"hello",便是指定消息应该被交换机路由到路由键为hello的队列中。而 <strong>默认交换机(Default Exchange)</strong> 有一个非常有用的性质:</p> <p>每一个被创建的队列都会被自动的绑定到默认交换机上,并且路由键就是队列的名字。</p> <p>交换机还有4种不同的类型,分别是 direct , fanout , topic , headers ,每种类型路由的策略不同。</p> <p>direct 类型的交换机要求和它绑定的队列带有一个路由键K,若有一个带有路由键R的消息到达了交换机,交换机会将此消息路由到路由键K = R的队列。默认交换机便是该类型。因此,在下图中,消息会沿着绿色箭头路由:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a84fa50921a68eb0a2fb6217d234340a.jpg"></p> <p>fanout 类型的交换机会路由每一条消息到所有和它绑定的队列,忽略路由键。</p> <p>剩下的两种类型之后再做介绍。</p> <p>在以上概念基础上,我们来看第3种消息模型: <strong>Publish/Subscribe</strong> 。如下图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/59a7bcc69b4bf5388ff28f0790ca5b90.png"></p> <p>该模型是要让所有的消费者都能够接收到每一条消息。显然, fanout 类型的交换机更符合我们当前的需求。为此,先创建一个 fanout 类型的交换机。</p> <pre> <code class="language-dust">channel.exchangeDeclare("notice", "fanout");</code></pre> <p>其中,第一个参数是交换机的名称;第二个参数是交换机的类型。</p> <p>然后我们可以 send 消息了:</p> <pre> <code class="language-dust">channel.basicPublish( "notice", "", null, message.getBytes());</code></pre> <p>对于消费者,我们需要为每一个消费者创建一个独立的队列,然后将队列绑定到刚才指定的交换机上即可:</p> <pre> <code class="language-dust">// 该方法会创建一个名称随机的临时队列 String queueName = channel.queueDeclare().getQueue(); // 将队列绑定到指定的交换机("notice")上 channel.queueBind(queueName, "notice", "");</code></pre> <p>以下完整的代码:</p> <pre> <code class="language-dust">@Test public void send() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("notice", "fanout"); channel.basicPublish( "notice", "", null, "Hello China".getBytes()); channel.close(); connection.close(); } @Test public void receive() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("notice", "fanout"); String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, "notice", ""); channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }); synchronized (this) { wait(); } }</code></pre> <p>首先运行两次 receive 方法,让两个消费者等待接收消息,然后可以在Web端查看此时的队列情况,如下图所示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/b4f653f0ac8d6bd90784652ecd3f906a.png"></p> <p>可以看到图中有两个名称随机的队列。接着运行 send 方法发送一条消息,最终我们会看到两个消费者都打印出了 Hello China 。然后停止虚拟机让消费者断开连接,再次在Web端查看队列情况,会发现刚才的两个队列被自动删除了。</p> <h3><strong>2.4 Routing</strong></h3> <p>以上是Publish/Subscribe模式,它已经能让我们的通知(notice)系统正常运转了。现在再考虑这样一个新需求:对于一些机密通知,我们只想让部分人看到。这就要求交换机对绑定在其上的队列进行筛选,于是引出了又一个新的模型: <strong>Routing</strong> 。</p> <p>之前我们说过,对于 direct 类型的交换机,它会根据 <strong>routing key</strong> 进行路由,因此我们可以借助它来实现我们的需求,模型结构如下图:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/05d48de4439d8e1a8d1778295a308652.jpg"></p> <p>下面用代码来实现。先看生产者。</p> <p>首先要声明一个 direct 类型的交换机:</p> <pre> <code class="language-dust">// 这里名称改为notice2 channel.exchangeDeclare("notice2", "direct");</code></pre> <p>需要注意的是, 因为我们之前声明了一个 fanout 类型的名叫 notice 的交换机,因此不能再声明一个同名的类型却不一样的交换机。</p> <p>然后可以发送消息了,我们发送10条消息,其中偶数条消息是秘密消息,只能被routing key 为s的队列接受,其余的消息所有的队列都能接受。</p> <pre> <code class="language-dust">for (int i = 0; i < 10; i++) { String routingKey = "n"; // normal if (i % 2 == 0) { routingKey = "s"; // secret } channel.basicPublish("notice2", routingKey, null, String.valueOf(i).getBytes()); }</code></pre> <p>接下来是消费者:</p> <pre> <code class="language-dust">// 声明一个名称随机的临时的队列 String queueName = channel.queueDeclare().getQueue(); // 绑定交换机,同时带上routing key channel.queueBind(queueName, "notice2", "n"); // 消费者2号运行时,打开以下注释 // channel.queueBind(queueName, "notice2", "s");</code></pre> <p>注意,我们可以多次调用队列绑定方法,调用时,队列名和交换机名都相同,而routing key不同,这样可以使一个队列带有多个routing key。</p> <p>以下是完整代码:</p> <pre> <code class="language-dust">@Test public void send() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("notice2", "direct"); for (int i = 0; i < 10; i++) { String routingKey = "n"; // normal if (i % 2 == 0) { routingKey = "s"; // secret } channel.basicPublish("notice2", routingKey, null, String.valueOf(i).getBytes()); } channel.close(); connection.close(); } @Test public void receive() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("notice2", "direct"); String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, "notice2", "n"); // channel.queueBind(queueName, "notice2", "s"); channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }); synchronized (this) { wait(); } }</code></pre> <p>测试时,我们可以先运行一个 receive ,然后打开 channel.queueBind(queueName, "notice2", "s") 注释,再运行一次 receive ,这样就有两个消费者绑定到notice2交换机上,其中消费者1只能收到normal类型的消息,而消费者2既能收到normal类型的消息,又能收到secret类型的消息。接着可以运行send方法。如不出意外,可以看到如下打印结果:</p> <pre> <code class="language-dust">// 消费者1 1 3 5 7 9 // 消费者2 0 1 2 3 4 5 6 7 8 9</code></pre> <h3>2.5 Topics</h3> <p>有了以上的改进,我们的 notice 系统基本ok了。但有些时候,我们还需要更加灵活的消息刷选方式。比如我们对于电影信息,我们可能需要对它的地区,类型,限制级进行筛选。这时候就要借助 <strong>Topics</strong> 模型了。</p> <p>在 <strong>Topics</strong> 模型中,我们“升级”了 <strong>routing key</strong> ,它可以由多个关键词组成,词与词之间由点号( . )隔开。特别地,规定 <strong> * 表示任意的一个词; # 号表示任意的0个或多个词 </strong> 。</p> <p>假设我们现在需要接收电影信息,每条电影消息附带的 <strong>routingKey</strong> 有地区、类型、限制级3个关键字,即: district.type.age 。现在想实现的功能如下图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/1d388579b2fc933cd5fa7583e45e2ad6.jpg"></p> <p>如上图所示,队列Q1只关心美国适合13岁以上的电影信息,队列Q2对动作片感兴趣,而队列Q3喜欢中国电影。</p> <p>下面用 <strong>Java</strong> 代码去实现上述功能,相较于之前基本上没有什么改变,下面直接给出代码:</p> <pre> <code class="language-dust">@Test public void send() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("movie", "topic"); channel.basicPublish("movie", "American.action.13", null, "The Bourne Ultimatum".getBytes()); channel.basicPublish("movie", "American.comedy.R", null, "The Big Lebowski".getBytes()); channel.basicPublish("movie", "American.romantic.13", null, "Titanic".getBytes()); channel.basicPublish("movie", "Chinese.action.13", null, "卧虎藏龙".getBytes()); channel.basicPublish("movie", "Chinese.comedy.13", null, "大话西游".getBytes()); channel.basicPublish("movie", "Chinese.romantic.13", null, "梁山伯与祝英台".getBytes()); channel.close(); connection.close(); } @Test public void receive() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("movie", "topic"); // 队列1 String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, "movie", "American.*.13"); // 队列2 // String queueName = channel.queueDeclare().getQueue(); // channel.queueBind(queueName, "movie", "*.action.*"); // 队列3 // String queueName = channel.queueDeclare().getQueue(); // channel.queueBind(queueName, "movie", "Chinese.#"); channel.basicConsume(queueName, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }); synchronized (this) { wait(); } }</code></pre> <p>运行3次 receive 方法,注意打开或关闭相应的注释;再运行 send 方法,可以看到控制台输出如下内容:</p> <pre> <code class="language-dust">// 消费者1 The Bourne Ultimatum Titanic // 消费者2 The Bourne Ultimatum 卧虎藏龙 // 消费者3 卧虎藏龙 大话西游 梁山伯与祝英台</code></pre> <h3>2.6 RPC</h3> <p>第6种模型是用来做RPC(Remote procedure call, 远程程序调用)的。这里直接贴上代码,就不做解释了 。代码演示的是,客户端调用服务端的 fib 方法,得到返回结果。</p> <p>RPCServer.java</p> <pre> <code class="language-dust">import com.rabbitmq.client.*; import com.rabbitmq.client.AMQP.BasicProperties; /** * Description: * * @author derker * @Time 2016-10-26 18:24 */ public class RPCServer { private static final String RPC_QUEUE_NAME = "rpc_queue"; private static int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n - 1) + fib(n - 2); } public static void main(String[] argv) { Connection connection = null; Channel channel = null; try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); connection = factory.newConnection(); channel = connection.createChannel(); channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null); channel.basicQos(1); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(RPC_QUEUE_NAME, false, consumer); System.out.println(" [x] Awaiting RPC requests"); while (true) { String response = null; QueueingConsumer.Delivery delivery = consumer.nextDelivery(); AMQP.BasicProperties props = delivery.getProperties(); BasicProperties replyProps = new BasicProperties .Builder() .correlationId(props.getCorrelationId()) .build(); try { String message = new String(delivery.getBody(), "UTF-8"); int n = Integer.parseInt(message); System.out.println(" [.] fib(" + message + ")"); response = "" + fib(n); } catch (Exception e) { System.out.println(" [.] " + e.toString()); response = ""; } finally { channel.basicPublish("", props.getReplyTo(), replyProps, response.getBytes("UTF-8")); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } } } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (Exception ignore) { } } } } }</code></pre> <p>RPCClient.java</p> <pre> <code class="language-dust">import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.Connection; import com.rabbitmq.client.Channel; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.AMQP.BasicProperties; import java.util.UUID; /** * Description: * * @author derker * @Time 2016-10-26 18:36 */ public class RPCClient { private Connection connection; private Channel channel; private String requestQueueName = "rpc_queue"; private String replyQueueName; private QueueingConsumer consumer; public RPCClient() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); connection = factory.newConnection(); channel = connection.createChannel(); replyQueueName = channel.queueDeclare().getQueue(); consumer = new QueueingConsumer(channel); channel.basicConsume(replyQueueName, true, consumer); } public String call(String message) throws Exception { String response = null; String corrId = UUID.randomUUID().toString(); BasicProperties props = new BasicProperties .Builder() .correlationId(corrId) .replyTo(replyQueueName) .build(); channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); if (delivery.getProperties().getCorrelationId().equals(corrId)) { response = new String(delivery.getBody(), "UTF-8"); break; } } return response; } public void close() throws Exception { connection.close(); } public static void main(String[] argv) { RPCClient fibonacciRpc = null; String response = null; try { fibonacciRpc = new RPCClient(); System.out.println(" [x] Requesting fib(10)"); response = fibonacciRpc.call("10"); System.out.println(" [.] Got '" + response + "'"); } catch (Exception e) { e.printStackTrace(); } finally { if (fibonacciRpc != null) { try { fibonacciRpc.close(); } catch (Exception ignore) { } } } } }</code></pre> <p> </p> <p>来自:http://www.cnblogs.com/dongkuo/p/6001791.html</p> <p> </p>