二、Remote procedure call (RPC)(using the Java client)

三、Client interface(客户端接口)
为了展示一个RPC服务是如何使用的,我们将创建一段很简单的客户端class。 它将会向外提供名字为call的函数,这个call会发送RPC请求并且阻塞,直到收到RPC运算的结果。代码如下:
  1. fibonacci_rpc = FibonacciRpcClient()
  2. result = fibonacci_rpc.call(4)
  3. print "fib(4) is %r" % (result,)
四、 总体来说,在RabbitMQ进行RPC远程调用是比较容易的。client发送请求的Message然后server返回响应结果。为了收到响应client在publish message时需要提供一个”callback“(回调)的queue地址。code如下:
  1. result = channel.queue_declare(exclusive=True)
  2. callback_queue = result.method.queue
  3. channel.basic_publish(exchange='',
  4. routing_key='rpc_queue',
  5. properties=pika.BasicProperties(
  6. reply_to = callback_queue,
  7. ),
  8. 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
  1. private static int fib(int n) throws Exception {
  2. if (n == 0) return 0;
  3. if (n == 1) return 1;
  4. return fib(n-1) + fib(n-2);
  5. }
 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循环,我们等待请求消息,并发送响应工作。

RPCClient.java:

    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,像往常一样编译和设置路径(可以参考前面的教程)

 
RPCClient.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起航)的更多相关文章

  1. 柯南君:看大数据时代下的IT架构(5)消息队列之RabbitMQ--案例(Work Queues起航)

    二.Work Queues(using the Java Client) 走起   在第上一个教程中我们写程序从一个命名队列发送和接收消息.在这一次我们将创建一个工作队列,将用于分发耗时的任务在多个工 ...

  2. 柯南君:看大数据时代下的IT架构(4)消息队列之RabbitMQ--案例(Helloword起航)

    柯南君:看大数据时代下的IT架构(4)消息队列之RabbitMQ--案例(Helloword起航) 二.起航 本章节,柯南君将从几个层面,用官网例子讲解一下RabbitMQ的实操经典程序案例,让大家重 ...

  3. 柯南君:看大数据时代下的IT架构(3)消息队列之RabbitMQ-安装、配置与监控

    柯南君:看大数据时代下的IT架构(3)消息队列之RabbitMQ-安装.配置与监控 一.安装 1.安装Erlang 1)系统编译环境(这里采用linux/unix 环境) ① 安装环境 虚拟机:VMw ...

  4. 看大数据时代下的IT架构(1)业界消息队列对比

    一.MQ(Message Queue) 即 消息队列,一般用于应用系统解耦.消息异步分发,能够提高系统吞吐量.MQ的产品有很多,有开源的,也有闭源,比如ZeroMQ.RabbitMQ. ActiveM ...

  5. 柯南君:看大数据时代下的IT架构(2)消息队列之RabbitMQ-基础概念详细介绍

    一.基础概念详细介绍 1.引言 你是否遇到过两个(多个)系统间需要通过定时任务来同步某些数据?你是否在为异构系统的不同进程间相互调用.通讯的问题而苦恼.挣扎?如果是,那么恭喜你,消息服务让你可以很轻松 ...

  6. 柯南君:看大数据时代下的IT架构(6)消息队列之RabbitMQ--案例(Publish/Subscribe起航)

    二.Publish/Subscribe(发布/订阅)(using the Java Client) 为了说明这个模式,我们将构建一个简单的日志系统.它将包括两个项目: 第一个将发出日志消息 第二个将接 ...

  7. 柯南君:看大数据时代下的IT架构(8)消息队列之RabbitMQ--案例(topic起航)

    二.Topic(主题) (using the Java client) 上一篇文章中,我们进步改良了我们的日志系统.我们使用direct类型转发器,使得接收者有能力进行选择性的接收日志,,而非fano ...

  8. 柯南君:看大数据时代下的IT架构(7)消息队列之RabbitMQ--案例(routing 起航)

    二.Routing(路由) (using the Java client) 在前面的学习中,构建了一个简单的日志记录系统,能够广播所有的日志给多个接收者,在该部分学习中,将添加一个新的特点,就是可以只 ...

  9. 大数据时代下EDM邮件营销的变革

    根据研究,今年的EDM邮件营销的邮件发送量比去年增长了63%,许多方法可以为你收集用户数据,这些数据可以帮助企业改善自己在营销中的精准度,相关性和执行力. 最近的一项研究表明,中国800强企业当中超过 ...

随机推荐

  1. 解析处理常用json数据总结

    工作中用ajax接收到接口返回的数据需要进行解析后操作,这里总结一下平时的方法,用的jquery,复制下来的页面把引入的路径改一下即可. <!DOCTYPE html> <html ...

  2. Axure 快捷方式

    基本快捷键: 打开:Ctrl + O新建:Ctrl + N保存:Ctrl + S退出:Alt + F4打印:Ctrl + P查找:Ctrl + F替换:Ctrl + H复制:Ctrl + C剪切:Ct ...

  3. [ReactiveCocoa]最简单的RAC入门操作

    当knowckout.js出来的时候,确实被其kobinding惊艳了一下,等到AngularJS出来的时候,把MVVM的模式更是向前推进了一步.所以当ReactiveCocoa出来的时候,也很感兴趣 ...

  4. Android 为应用添加数字角标

    今天在论坛上看到了一个帖子,终于搞清了我很久以来的一个困惑,android到底能不能实现ios的角标效果,QQ是怎么实现的.看了这个帖子顿时终于解除了我的困惑. 先说一个下大概的思路: 大家都知道an ...

  5. express4.0之后不会解析req.files,必须加一个插件multer

    express 4 + 用multer express4.0之后不会解析req.files,必须加一个插件multer http://www.w3school.com.cn/tags/att_form ...

  6. [Regionals 2012 :: Asia - Tokyo ]

    链接: https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&category=56 ...

  7. The MySQL C API 编程实例

    在网上找了一些MYSQL C API编程的文章,看了后认为还是写的不够充分,依据自己经验写了这篇<The MySQL C API 编程实例>,希望对须要调用到MYSQL的C的API的朋友有 ...

  8. 还是log4net的使用

    最近做个项目要用到日志系统,这这可把我给难住了,后来问了下度娘,发现只有你想不到的,没有那些找不到的开源组件,后来发现了log4net,但是我是控制台程序,没有个实例还真不好搞,想想还是看看他的运行过 ...

  9. Winform mschart 动态绑定X时间表

    效果图: 代码: using System; using System.Collections.Generic; using System.ComponentModel; using System.D ...

  10. Web 应用配置Log4Net

    1.第一步:在web.config文件添加如下代码: [sourcecode language="csharp"] <configSections> <secti ...