柯南君:看大数据时代下的IT架构(9)消息队列之RabbitMQ--案例(RPC起航)
二、Remote procedure call (RPC)(using the Java client)
三、Client interface(客户端接口)

- fibonacci_rpc = FibonacciRpcClient()
- result = fibonacci_rpc.call(4)
- print "fib(4) is %r" % (result,)
四、 总体来说,在RabbitMQ进行RPC远程调用是比较容易的。client发送请求的Message然后server返回响应结果。为了收到响应client在publish message时需要提供一个”callback“(回调)的queue地址。code如下:
- result = channel.queue_declare(exclusive=True)
- callback_queue = result.method.queue
- channel.basic_publish(exchange='',
- routing_key='rpc_queue',
- properties=pika.BasicProperties(
- reply_to = callback_queue,
- ),
- body=request)
Message properties
AMQP 预定义了14个属性。它们中的绝大多很少会用到。以下几个是平时用的比较多的:
- delivery_mode: 持久化一个Message(通过设定值为2)。其他任意值都是非持久化。
- content_type: 描述mime-type 的encoding。比如设置为JSON编码:设置该property为application/json。
- reply_to: 一般用来指明用于回调的queue(Commonly used to name a callback queue)。
- correlation_id: 在请求中关联处理RPC响应(correlate RPC responses with requests)。
四、Correlation Id 在上个小节里,实现方法是对每个RPC请求都会创建一个callback queue。这是不高效的。幸运的是,在这里有一个解决方法:为每个client创建唯一的callback queue。
这又有其他问题了:收到响应后它无法确定是否是它的,因为所有的响应都写到同一个queue了。上一小节的correlation_id在这种情况下就派上用场了:对于每个request,都设置唯一的一个值,在收到响应后,通过这个值就可以判断是否是自己的响应。如果不是自己的响应,就不去处理。
五、(总结)
工作流程:
- 当客户端启动时,它创建了匿名的exclusive callback queue.
- 客户端的RPC请求时将同时设置两个properties: reply_to设置为callback queue;correlation_id设置为每个request一个独一无二的值.
- 请求将被发送到an rpc_queue queue.
- RPC端或者说server一直在等待那个queue的请求。当请求到达时,它将通过在reply_to指定的queue回复一个message给client。
- client一直等待callback queue的数据。当message到达时,它将检查correlation_id的值,如果值和它request发送时的一致那么就将返回响应。
六、
Putting it all together
- private static int fib(int n) throws Exception {
- if (n == 0) return 0;
- if (n == 1) return 1;
- return fib(n-1) + fib(n-2);
- }
RPCServer.java :
private static final String RPC_QUEUE_NAME = "rpc_queue";
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
channel.basicQos();
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
System.out.println(" [x] Awaiting RPC requests");
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
BasicProperties props = delivery.getProperties();
BasicProperties replyProps = new BasicProperties
.Builder()
.correlationId(props.getCorrelationId())
.build();
String message = new String(delivery.getBody());
int n = Integer.parseInt(message);
System.out.println(" [.] fib(" + message + ")");
String response = "" + fib(n);
channel.basicPublish( "", props.getReplyTo(), replyProps, response.getBytes());
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
服务器代码相当简单:
- 像往常一样,我们首先建立连接、通道和声明队列。
- 我们可能想要运行多个服务器进程。为了分散负载同样在多个服务器,我们需要设置在channel.basicQos prefetchCount设置。
- 我们使用basicConsume访问队列。然后我们进入while循环,我们等待请求消息,并发送响应工作。
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 = java.util.UUID.randomUUID().toString();
BasicProperties props = new BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("", requestQueueName, props, message.getBytes());
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
if (delivery.getProperties().getCorrelationId().equals(corrId)) {
response = new String(delivery.getBody());
break;
}
}
return response;
}
public void close() throws Exception {
connection.close();
}
客户端代码部分涉及到:
- 我们建立了一个"connecttion"(连接) 和 "channel"(通道)并且为replies(回复)声明一个独一无二的"callback"(回调);
- 我们订阅了"callback"(回调)队列,这样我们就可以收到RPC的回应了;
- 我们调用的方法是实际的RPC;
- 接下来我们publish(发布)请求消息,有两个属性,分别是:replyTo 和 correlationId;
- 在这点,我们可以坐下来,直到适当的响应到达;
- while循环做了一件非常简单的工作,它会检查每一个消息响应,如果当前的最后,我们将响应给用户;
RPCClient fibonacciRpc = new RPCClient();
System.out.println(" [x] Requesting fib(30)");
String response = fibonacciRpc.call(");
System.out.println(" [.] Got '" + response + "'");
fibonacciRpc.close();
现在是时候,该看看我们的整体完整的示例源代码了:RPCClent.java(包括基本的异常处理)和RPCServer.java,像往常一样编译和设置路径(可以参考前面的教程)
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;
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());
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(30)");
response = fibonacciRpc.call(");
System.out.println(" [.] Got '" + response + "'");
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (fibonacciRpc!= null) {
try {
fibonacciRpc.close();
}
catch (Exception ignore) {}
}
}
}
}<strong>
</strong>
RPCServer.java:
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;
public class RPCServer {
private static final String RPC_QUEUE_NAME = "rpc_queue";
private static int fib(int n) {
) ;
) ;
) + fib(n-);
}
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();
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();
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) {}
}
}
}
}
$ javac -cp rabbitmq-client.jar RPCClient.java RPCServer.java
我们的RPC service现在准备好了,我们开始启动server:
$ java -cp $CP RPCServer [x] Awaiting RPC requests
发布一个fibonacci 数字,运行在client(客户端):
$ java -cp $CP RPCClient [x] Requesting fib(30)
本节提供的设计并不是唯一的RPC服务实现,但它还是有一定的优点的:
- 如果RPC server(服务器)太慢了,你仅仅需要运行另一个,就可以扩展;尝试在新的控制台,运行第二个吧;
- 在客户端,RPC需要发送和接收的消息只有一个,不需要像queueDeclare 同步调用,因为RPC客户端为了一个RPC请求,只需要一个网络往返;
我们的代码依然很简单,不试图去解决更加繁杂的问题,但是非常重要,像以下这样:
- 如果没有服务运行,客户端将怎么去做?
- 客户端应该有RPC超时么?
- 如果服务器出现故障,爆出一个异常,应该发给客户端么?
- 防止传入错误的消息(如范围检查、类型)前处理
柯南君:看大数据时代下的IT架构(9)消息队列之RabbitMQ--案例(RPC起航)的更多相关文章
- 柯南君:看大数据时代下的IT架构(5)消息队列之RabbitMQ--案例(Work Queues起航)
二.Work Queues(using the Java Client) 走起 在第上一个教程中我们写程序从一个命名队列发送和接收消息.在这一次我们将创建一个工作队列,将用于分发耗时的任务在多个工 ...
- 柯南君:看大数据时代下的IT架构(4)消息队列之RabbitMQ--案例(Helloword起航)
柯南君:看大数据时代下的IT架构(4)消息队列之RabbitMQ--案例(Helloword起航) 二.起航 本章节,柯南君将从几个层面,用官网例子讲解一下RabbitMQ的实操经典程序案例,让大家重 ...
- 柯南君:看大数据时代下的IT架构(3)消息队列之RabbitMQ-安装、配置与监控
柯南君:看大数据时代下的IT架构(3)消息队列之RabbitMQ-安装.配置与监控 一.安装 1.安装Erlang 1)系统编译环境(这里采用linux/unix 环境) ① 安装环境 虚拟机:VMw ...
- 看大数据时代下的IT架构(1)业界消息队列对比
一.MQ(Message Queue) 即 消息队列,一般用于应用系统解耦.消息异步分发,能够提高系统吞吐量.MQ的产品有很多,有开源的,也有闭源,比如ZeroMQ.RabbitMQ. ActiveM ...
- 柯南君:看大数据时代下的IT架构(2)消息队列之RabbitMQ-基础概念详细介绍
一.基础概念详细介绍 1.引言 你是否遇到过两个(多个)系统间需要通过定时任务来同步某些数据?你是否在为异构系统的不同进程间相互调用.通讯的问题而苦恼.挣扎?如果是,那么恭喜你,消息服务让你可以很轻松 ...
- 柯南君:看大数据时代下的IT架构(6)消息队列之RabbitMQ--案例(Publish/Subscribe起航)
二.Publish/Subscribe(发布/订阅)(using the Java Client) 为了说明这个模式,我们将构建一个简单的日志系统.它将包括两个项目: 第一个将发出日志消息 第二个将接 ...
- 柯南君:看大数据时代下的IT架构(8)消息队列之RabbitMQ--案例(topic起航)
二.Topic(主题) (using the Java client) 上一篇文章中,我们进步改良了我们的日志系统.我们使用direct类型转发器,使得接收者有能力进行选择性的接收日志,,而非fano ...
- 柯南君:看大数据时代下的IT架构(7)消息队列之RabbitMQ--案例(routing 起航)
二.Routing(路由) (using the Java client) 在前面的学习中,构建了一个简单的日志记录系统,能够广播所有的日志给多个接收者,在该部分学习中,将添加一个新的特点,就是可以只 ...
- 大数据时代下EDM邮件营销的变革
根据研究,今年的EDM邮件营销的邮件发送量比去年增长了63%,许多方法可以为你收集用户数据,这些数据可以帮助企业改善自己在营销中的精准度,相关性和执行力. 最近的一项研究表明,中国800强企业当中超过 ...
随机推荐
- linux 下访问mysql
1:先进到root:/# /usr/local/mysql/bin/2:root:/# mysql -u root -p Enter password:
- 2-4. BCD解密(10)
BCD数是用一个字节来表达两位十进制的数,每四个比特表示一位.所以如果一个BCD数的十六进制是0x12,它表达的就是十进制的12.但是小明没学过BCD,把所有的BCD数都当作二进制数转换成十进制输出了 ...
- document.ready()的用法
1.Jquery是优秀的Javascrīpt框架,$是jquery库的申明,它很不稳定(我就常遇上),换一种稳定的写法jQuery.noConflict(); jQuery(document).rea ...
- 安卓linearlayout布局的一个嵌套实例
XML代码如下 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andr ...
- Qt for Android遇到的几个错误解决[Win7 + Qt5.6 +jdk 8u91]
[1]SDK Manager无法更新Android SDK安装后需要运行SDK Manager下载安装包,默认从google网站下载,但被GWF和谐了,感谢一位网友提供的镜像站点.具体步骤是:运行SD ...
- Android 给Button加个监听
1.日期设置控件:DatePickerDialog 2.时间设置控件:TimePickerDialog 实例代码 1.页面添加两个Button,单击分别显示日期设置控件和时间设置控件,还是有TextV ...
- Android中Gallery显示手机中的图片
在网上找了好久似乎都没有关于这方面的(可能是自己的信息量太小吧),于是自己来填补这个漏洞吧. 常见的方法莫过于自己定义一个数组,用以存储图片的引用,如: 1 private Integer[] pic ...
- 获取枚举Name,Value,Description两种方法
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI ...
- break与continue
1.break与continue.这两个关键字一般放在循环的花括号里面使用.break——结束整个循环.continue——结束本次循环,进入下次循环. break的案例: int i = 1; fo ...
- Windows Server 2008 安装好之后的简单配置
1.禁用密码复杂度 在运行中输入GPEDIT.MSC 打开组策略,找到计算机配置->Windows设置->安全设置->账户策略中的密码策略,将“密码必须符合复杂性要求”设置为禁用即可 ...