十、RPC(远程过程调用)
相关概念
RPC,是Remote Procedure Call的简称,即远程过程调用。它是一种通过网络从远程计算机上请求服务,而不需要了解底层网络的技术。RPC的主要功用是让构建分布式计算更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。
通俗点来说,假设有两台服务器A和B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数或者方法,由于不在同一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
一般在RabbitMQ中进行RPC是很简单。客户端发送请求消息,服务端回复响应的消息。为了接收响应的消息,我们需要在请求消息中发送一个回调队列,直接用默认队列即可,如下面代码:
String replyQueueName = channel.queueDeclare().getQueue();
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("", QUEUENAME, props, message.getBytes("UTF-8"));
代码中的BasicProperties类,包含了14个属性,这里用到了两个属性:
replyTo:用来设置一个回调队列。
如果像上面代码为每个RPC请求创建一个回调队列会很低效,可以为每个客户端创建一个单一的回调队列。
对于回调队列,在接收到一条回复消息后,并不知道这条消息应该与哪个请求匹配,这时就用到
correlationId属性了,只要为每个请求设置一个correlationId,在回调队列接收到回复时,匹配correlationId属性从而匹配到相应请求。correlationId:用来关联请求(request)和其调用RPC之后的回复(response)匹配。
RPC工作流程:

- 客户端启动时,创建了一个匿名的回调队列。
- 在一个RPC请求中,客户端发送一个消息,它有两个属性:
- replyTo:用来设置回调队列;
- correlationId,对于每个请求都被设置唯一的关联ID。
- 请求被发送到rpc_queue队列.
- RPC服务器等待接收该队列的请求。当收到一个请求,它就会处理并把结果发送给客户端,使用的队列是replyTo字段指定队列。
- 客户端等待接收回调队列中的数据。当接到一个消息,它会检查它的correlationId属性。如果它和设置的相匹配,就会把响应返回给应用程序。
客户端代码:
public class RPCClient {
private Connection connection;
private Channel channel;
private String requestQueueName = "rpc_queue";
public static void main(String[] argv) {
RPCClient rpcClient = null;
String response = null;
try {
rpcClient = new RPCClient();
for (int i = 0; i < 32; i++) {
String i_str = Integer.toString(i);
System.out.println("请求数(" + i_str + ")");
response = rpcClient.call(i_str);
System.out.println("接收返回数 '" + response + "'");
}
} catch (IOException | TimeoutException | InterruptedException e) {
e.printStackTrace();
} finally {
if (rpcClient != null) {
try {
rpcClient.close();
} catch (IOException _ignore) {
}
}
}
}
public RPCClient() throws IOException, TimeoutException {
connection = ConnectionUtils.getConnection();
channel = connection.createChannel();
}
public String call(String message) throws IOException, InterruptedException {
//生成correlationId
final String corrId = UUID.randomUUID().toString();
//创建回调队列
String replyQueueName = channel.queueDeclare().getQueue();
//设置BasicProperties
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId) //设置correlationId
.replyTo(replyQueueName) //设置回调队列
.build();
//向服务端发送消息,并设置BasicProperties参数
channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
//使用阻塞队列保存返回的数据,因为服务器返回数据是在一个单独的线程中进行,
//客户端在获取到数据之前要暂停当前主线程,创建大小为1的ArrayBlockingQueue来保存一条数据
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
String ctag = channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//服务器返回的数据后,匹配correlationId后,保存数据
if (properties.getCorrelationId().equals(corrId)) {
response.offer(new String(body, "UTF-8"));
}
}
});
//从阻塞队列中获取数据
String result = response.take();
//获取到数据后,取消订阅
channel.basicCancel(ctag);
return result;
}
public void close() throws IOException {
connection.close();
}
}
服务端代码:
public class RPCServer {
private static final String RPC_QUEUE_NAME = "rpc_queue";
public static void main(String[] argv) {
Connection connection = null;
try {
connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//创建队列
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
//清空队列
channel.queuePurge(RPC_QUEUE_NAME);
//消费端接收的消息数
channel.basicQos(1);
System.out.println("等待RPC请求...");
//打开消息确认机制
boolean autoAck = false;
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取correlationId,设置BasicProperties,以便回调给客户端匹配
AMQP.BasicProperties replyProps = new AMQP.BasicProperties
.Builder()
.correlationId(properties.getCorrelationId())
.build();
String response = "";
try {
String message = new String(body, "UTF-8");
int n = Integer.parseInt(message);
System.out.println(" 接收到(" + message + ")");
response += fib(n);
System.out.println("处理后 = " + response);
} catch (RuntimeException e) {
System.out.println(" 异常: " + e.toString());
} finally {
//返回处理后结果给客户端
channel.basicPublish("", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
//发送消费端确认
channel.basicAck(envelope.getDeliveryTag(), false);
//唤醒等待线程继续等待下一个消息接收
synchronized (this) {
this.notify();
}
}
}
};
channel.basicConsume(RPC_QUEUE_NAME, autoAck, consumer);
// 等待接收来自RPC客户端的消息
while (true) {
synchronized (consumer) {
try {
consumer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (IOException _ignore) {
}
}
}
}
//斐波那契函数
private static int fib(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
}
先运行服务端代码等待客户端消息,在运行客户端代码,会在发送消息给服务端处理后再返回消息。
上面代码设计只是简单的实现,具有的一些优势:
- 如果RPC服务端太慢,可以运行多个服务端扩展
- 在客户端,RPC只需要发送和接收一条消息。不需像queueDeclare的同步调用。对于单个RPC请求,RPC客户端只需要一次网络往返。
还需要解决的更复杂的问题:
- 如果没有运行服务器,客户端如何反应
- 客户端是否应该为RPC设置超时
- 服务端故障引发异常,是否将其转发给客户端
- 在处理之前防止无效的传入消息(例如边界检查等)
十、RPC(远程过程调用)的更多相关文章
- 用C代码简要模拟实现一下RPC(远程过程调用)并谈谈它在代码调测中的重要应用【转】
转自:http://blog.csdn.net/stpeace/article/details/44947925 版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则 ...
- [node.js]RPC(远程过程调用)的实现原理
刚接触到RPC(远程过程调用),就是可以在本地调用远程机子上的程序的方法,看到一个简单的nodejs实现,用来学习RPC的原理很不错:nodejs light_rpc 使用示例: //服务端 ...
- 译:6.RabbitMQ Java Client 之 Remote procedure call (RPC,远程过程调用)
在 译:2. RabbitMQ 之Work Queues (工作队列) 我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这 ...
- RPC远程过程调用(Remote Procedure Call)
RPC,就是Remote Procedure Call,远程过程调用 远程过程调用,自然是相对于本地过程调用 本地过程调用,就好比你现在在家里,你要想洗碗,那你直接把碗放进洗碗机,打开洗碗机开关就可以 ...
- RPC远程过程调用学习之路(一):用最原始代码还原PRC框架
RPC: Remote Procedure Call 远程过程调用,即业务的具体实现不是在自己系统中,需要从其他系统中进行调用实现,所以在系统间进行数据交互时经常使用. rpc的实现方式有很多,可以通 ...
- RPC远程过程调用协议
最近学习Hadoop.Hbase.Spark及Storm原理,经常会出现RPC这样的传输术语,为了更好地理解,将知识点详细的整理下吧~ RPC-----它是一种通过网络从远程计算机程序上请求服务,而不 ...
- RPC(远程过程调用)的应用
接触背景 因为工作上某项目的需要设计一种分布式处理耗时的运算,每个节点然后将运算结果返回给中心服务器,而最初未了解RPC这部分之前我的设计是在每一个RPC服务器上搭建一个webserver,然后部署运 ...
- 浅析RPC远程过程调用基本原理
在校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服务消费方和服务提供方是本地调用关系. 而一旦踏入公司尤其是大型互联网公司就会发现,公司的系 ...
- RPC远程过程调用实例
什么是RPC RPC 的全称是 Remote Procedure Call 是一种进程间通信方式.它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程 ...
随机推荐
- java实现接口导出csv文件
Tomxin7 Simple, Interesting | 简单,有趣 业务介绍 项目要求从数据库中查询出相关数据后,通过表格展示给用户,如果用户需要,可以点击导出按钮,导出数据为csv格式. 开发环 ...
- 吴裕雄--天生自然 PHP开发学习:数组
<?php $cars=array("Volvo","BMW","Toyota"); echo "I like " ...
- h5-伪元素-before和after
做一个门票或者邮票:效果图 1.html就是两个div 2.具体css代码 <style> /*左侧长方体基本样式*/ div:nth-of-type(1){ width: 300px; ...
- JAVA内存分配-通俗讲解
Java的内存分配上,主要分4个块: 一块是用来装代码的,就是编译的东西. 一块是用来装静态变量的,例如用static关键字的变量,例如字符串常量. 一块是stack,也就是栈,是用来装变量和引用类型 ...
- js中要声明变量吗?
你好,js语言是弱类型语言,无需申明即可直接使用,默认是作为全局变量使用的.建议:在function里时应使用var 申明变量,这样改变量仅仅只在function的生存周期内存在,不会污染到,全局控件 ...
- P3810 【模板】三维偏序(陌上花开)(CDQ分治)
题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai.b_ibi.c_ici 三个属性,设 f(i) ...
- Java架构师笔记-你必须掌握的 21 个 Java 核心技术!(干货)
闲来无事,师长一向不(没)喜(有)欢(钱)凑热闹,倒不如趁着这时候复盘复盘.而写这篇文章的目的是想总结一下自己这么多年来使用java的一些心得体会,希望可以给大家一些经验,能让大家更好学习和使用Jav ...
- 5314跳跃游戏IV
题目:给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0).每一步,你可以从下标 i 跳到下标: i + 1 满足:i + 1 < arr.length i - 1 ...
- Java利用DES/3DES/AES这三种算法分别实现对称加密
转载地址:http://blog.csdn.net/smartbetter/article/details/54017759 有两句话是这么说的: 1)算法和数据结构就是编程的一个重要部分,你若失掉了 ...
- 学习LCA( 最近公共祖先·二)
http://poj.org/problem?id=1986 离线找u,v之间的最小距离(理解推荐) #include<iostream> #include<cstring> ...