二、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. Mvc Webapi+Fiddler调试 (WebAPI 一)

    Fiddler Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js, ...

  2. Android_神奇的android:clipChildren属性

    正文 一.效果图 看到这个图时你可以先想想如果是你,你怎么实现这个效果.马上想到用RelativeLayout?NO,NO,NO,,, 二.实现代码 <?xml version="1. ...

  3. Linux定时任务Crontab命令详解

    linux 系统则是由 cron (crond) 这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的.另 外, 由于使用者自己也可以设置计划任务,所以, ...

  4. LinkList的实现

    public class MyLinkedList<AnyType> implements Iterable<AnyType> { @Override public Itera ...

  5. Hibernate防止SQL注入

    如果在查询字段中输入单引号"'",则会报错,这是因为输入的单引号和其他的sql组合在一起编程了一个新的sql,实际上这就是SQL注入漏洞,后来我在前台和后台都对输入的字符进行了判断 ...

  6. SSH连接不上

    网上查了 大概说,一要安装开启ssh服务 然后关掉防火墙 service sshd restart service iptables stop 可是我用了之后还是连接不上, 很郁闷. 我尝试着ping ...

  7. CC++初学者编程教程(8) VS2013配置编程助手与QT

    1. 2. 配置编程助手 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26 ...

  8. centos6.5 Eclipse C/C++开发环境及项目创建测试

    1,新建C++ project

  9. windows编程之菜单操作

    分清几个概念 <1>"主菜单" 和 "顶层菜单" 是一个意思. <2>主菜单中的项目叫做 "弹出菜单" 或者 &qu ...

  10. 【双向广搜+逆序数优化】【HDU1043】【八数码】

    HDU上的八数码 数据强的一B 首先:双向广搜 先处理正向搜索,再处理反向搜索,直至中途相遇 visit 和 队列都是独立的. 可以用一个过程来完成这2个操作,减少代码量.(一般还要个深度数组) 优化 ...