RabbitMQ之RPC实现
2017-02-15 20:28
441 查看
什么是RPC?
RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用,
RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。
RabbitMQ怎么实现RPC调用?
Callback Queue
一般在RabbitMQ中做RPC是很简单的。客户端发送请求消息,服务器回复响应的消息。为了接受响应的消息,我们需要在请求消息中发送一个回调队列。可以使用默认的队列(which is exclusive in the java client.):callbackQueueName = channel.queueDeclare().getQueue(); BasicProperties props = new BasicProperties.Builder().replyTo(callbackQueueName).build(); channel.basicPublish("", "rpc_queue",props,message.getBytes()); // then code to read a response message from the callback_queue...
Message properties
AMQP协议为消息预定义了一组14个属性。private String contentType; private String contentEncoding; private Map<String,Object> headers; private Integer deliveryMode; private Integer priority; private String correlationId; private String replyTo; private String expiration; private String messageId; private Date timestamp; private String type; private String userId; private String appId; private String clusterId;
大部分的属性是很少使用的。除了以下几种(其余有兴趣可以自行查看):
deliveryMode: 标记消息传递模式,2-消息持久化,其他值-瞬态。
contentType:内容类型,用于描述编码的mime-type. 例如经常为该属性设置JSON编码。
replyTo:应答,通用的回调队列名称,
correlationId:关联ID,方便RPC相应与请求关联。
Correlation Id
在上述方法中为每个RPC请求创建一个回调队列。这是很低效的。幸运的是,一个解决方案:可以为每个客户端创建一个单一的回调队列。新的问题被提出,队列收到一条回复消息,但是不清楚是那条请求的回复。这是就需要使用correlationId属性了。我们要为每个请求设置唯一的值。然后,在回调队列中获取消息,查看这个属性,关联response和request就是基于这个属性值的。如果我们看到一个未知的correlationId属性值的消息,可以放心的无视它——它不是我们发送的请求。
你可能问道,为什么要忽略回调队列中未知的信息,而不是当作一个失败?这是由于在服务器端竞争条件的导致的。虽然不太可能,但是如果RPC服务器在发送给我们结果后,发送请求反馈前就挂掉了,这有可能会发送未知correlationId属性值的消息。如果发生了这种情况,重启RPC服务器将会重新处理该请求。这就是为什么在客户端必须很好的处理重复响应,RPC应该是幂等的。
Summary
RPC的处理流程:
当客户端启动时,创建一个匿名的回调队列。
客户端为RPC请求设置2个属性:replyTo,设置回调队列名字;correlationId,标记request。
请求被发送到rpc_queue队列中。
RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。
客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了。
Demo Code
这里采用官网的一个例子来说明,RPC客户端通过RPC调用服务器来计算斐波那契额值。首先是服务端的代码:
public class RPCServer { private static final String RPC_QUEUE_NAME = "rpc_queue"; public static void main(String args[]) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost(RabbitConfig.ip); factory.setPort(RabbitConfig.port); factory.setUsername(RabbitConfig.username); factory.setPassword(RabbitConfig.password); Connection connection = factory.newConnection(); Channel 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){ 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 repsonse = ""+fib(n); channel.basicPublish("", props.getReplyTo(), replyProps, repsonse.getBytes()); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } } 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); } }
RPC客户端:
public class RPCClient { private Connection connection; private Channel channel; private String requestQueueName = "rpc_queue"; private String replyQueueName; private QueueingConsumer consumer; public RPCClient() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost(RabbitConfig.ip); factory.setPort(RabbitConfig.port); factory.setUsername(RabbitConfig.username); factory.setPassword(RabbitConfig.password); 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 IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException { 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()); break; } } return response; } public void close() throws Exception{ connection.close(); } public static void main(String args[]) throws Exception{ RPCClient fibRpc = new RPCClient(); System.out.println(" [x] Requesting fib(30)"); String response = fibRpc.call("30"); System.out.println(" [.] Got '"+response+"'"); fibRpc.close(); } }
参考资料
Remote procedure call (RPC)轻松搞定RabbitMQ(七)——远程过程调用RPC
欢迎支持《RabbitMQ实战指南》以及关注微信公众号:朱小厮的博客。
相关文章推荐
- rabbitmq RPC java实现
- RabbitMQ 实现RPC
- Spring-rabbitmq 实现RPC 风格调用实例
- springmvc 集成rabbitmq,实现RPC通信方式
- python rabbitMQ 实现RPC
- rabbitmq整合spring实现rpc技术调用
- 利用RabbitMQ实现RPC(python)
- RabbitMQ实现RPC技术
- RabbitMQ实现RPC(java)
- 中间件系列八 RabbitMQ之实现RPC模式
- RabbitMQ 实现RPC
- RabbitMQ随手笔记(九)RabbitMQ实现RPC(netCore2.0)
- Python案例-网络编程-使用RabbitMQ实现RPC简易分享
- RabbitMQ实现异步及同步RPC
- Python RabbitMQ消息队列实现rpc
- WinForm实现Rabbitmq官网6个案例-RPC
- PHPRPC ,HPROSE --- 高效RPC实现, XML-RPC的替代品
- 使用C++模板实现不需要IDL的RPC【二】
- 使用Mina实现RPC调用
- Exchange笔记之使用RPC over HTTPS实现邮件互访