RabbitMQ之RPC实现

qyof1847 4年前
   <h2>什么是RPC?</h2>    <p>RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。</p>    <p>为什么RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用,</p>    <p>RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。</p>    <h2>RabbitMQ怎么实现RPC调用?</h2>    <h3>Callback Queue</h3>    <p>一般在RabbitMQ中做RPC是很简单的。客户端发送请求消息,服务器回复响应的消息。为了接受响应的消息,我们需要在请求消息中发送一个回调队列。可以使用默认的队列(which is exclusive in the java client.):</p>    <pre>  <code class="language-java">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...</code></pre>    <h3>Message properties</h3>    <p>AMQP协议为消息预定义了一组14个属性。</p>    <pre>  <code class="language-java">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;</code></pre>    <p>大部分的属性是很少使用的。除了以下几种(其余有兴趣可以自行查看):</p>    <ul>     <li>deliveryMode: 标记消息传递模式,2-消息持久化,其他值-瞬态。</li>     <li>contentType:内容类型,用于描述编码的mime-type. 例如经常为该属性设置JSON编码。</li>     <li>replyTo:应答,通用的回调队列名称,</li>     <li>correlationId:关联ID,方便RPC相应与请求关联。</li>    </ul>    <h3>Correlation Id</h3>    <p>在上述方法中为每个RPC请求创建一个回调队列。这是很低效的。幸运的是,一个解决方案:可以为每个客户端创建一个单一的回调队列。</p>    <p>新的问题被提出,队列收到一条回复消息,但是不清楚是那条请求的回复。这是就需要使用correlationId属性了。我们要为每个请求设置唯一的值。然后,在回调队列中获取消息,查看这个属性,关联response和request就是基于这个属性值的。如果我们看到一个未知的correlationId属性值的消息,可以放心的无视它——它不是我们发送的请求。</p>    <p>你可能问道,为什么要忽略回调队列中未知的信息,而不是当作一个失败?这是由于在服务器端竞争条件的导致的。虽然不太可能,但是如果RPC服务器在发送给我们结果后,发送请求反馈前就挂掉了,这有可能会发送未知correlationId属性值的消息。如果发生了这种情况,重启RPC服务器将会重新处理该请求。这就是为什么在客户端必须很好的处理重复响应,RPC应该是幂等的。</p>    <h3>Summary</h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0406148a76ec904d6d949812a64637de.png"></p>    <p>RPC的处理流程:</p>    <ol>     <li>当客户端启动时,创建一个匿名的回调队列。</li>     <li>客户端为RPC请求设置2个属性:replyTo,设置回调队列名字;correlationId,标记request。</li>     <li>请求被发送到rpc_queue队列中。</li>     <li>RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。</li>     <li>客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了。</li>    </ol>    <h2>Demo Code</h2>    <p>这里采用官网的一个例子来说明,RPC客户端通过RPC调用服务器来计算斐波那契额值。</p>    <p>首先是服务端的代码:</p>    <pre>  <code class="language-java">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);      }  }</code></pre>    <p>RPC客户端:</p>    <pre>  <code class="language-java">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();        }  }</code></pre>    <h2>参考资料</h2>    <ol>     <li><a href="/misc/goto?guid=4959737488896604846" rel="nofollow,noindex">Remote procedure call (RPC)</a></li>     <li><a href="/misc/goto?guid=4959737488987796727" rel="nofollow,noindex">轻松搞定RabbitMQ(七)——远程过程调用RPC</a></li>    </ol>    <p> </p>    <p>来自:http://blog.csdn.net/u013256816/article/details/55218595</p>    <p> </p>