RabbitMQ实例教程:RPC远程调用消息队列

简介:

 在工作队列一章中,我们学会了如何使用工作队列来处理多个工作进程间分发任务,但如果我们想要运行远程计算机上的函数来获得结果呢?这就是本章要处理的问题RPC。


  本节我们会使用RabbitMQ构建一个RPC系统:一个客户端和一个可扩展的RPC服务器。因为我们没有任何耗时的任务值得分发下去,我们构建一个虚拟的服务来返回斐波纳契数列。


  客户端接口


  我们创建一个客户端类来说明如何使用RPC服务,暴露一个call方法来发送RPC请求和数据获取结果。


1
2
3
FibonacciRpcClient fibonacciRpc =  new  FibonacciRpcClient();  
String result = fibonacciRpc.call( "4" );
System.out.println(  "fib(4) is "  + result);


  尽管RPC是编程中一种常见的模式,但其也常常饱受批评。因为程序员常常不知道调用的方法是本地方法还是一个RPC方法,这在调试中常常增加一些不必要的复杂性。我们应该简化代码,而不是滥用RPC导致代码变的臃肿。


  回调队列


  一般来说,通过RabbitMQ实现RPC非常简单,客户端发送一个请求消息,服务端响应消息就完成了。为了接收到响应内容,我们在请求中发送”callback“队列地址,也可以使用默认的队列。


1
2
3
callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props =  new  BasicProperties .Builder().replyTo(callbackQueueName) .build(); 
channel.basicPublish( "" "rpc_queue" , props, message.getBytes());

   

  AMQP协议中预定了14个消息属性,除了下面几个,其它的都很少使用:


  deliveryMode : 标识消息是持久化还是瞬态的。


  contentType : 描述 mime-type的编码类型,如JSON编码为”application/json“。


  replyTo : 通常在回调队列中使用。


  correlationId : 在请求中关联RPC响应时使用。


  关联Id(Correlation Id)


  在前面的方法中,要求在每个RPC请求创建回调队列,这可真是一件繁琐的事情,但幸运的是我们有个好方法-在每个客户端创建一个简单的回调队列。


  这样问题又来了,队列如何知道这些响应来自哪个请求呢?这时候correlationId就出场了。我们在每个请求中都设置一个唯一的值,这样我们在回调队列中接收消息的时候就能知道是哪个请求发送的。如果收到未知的correlationId,就废弃该消息,因为它不是我们发出的请求。


  你可能会问,为什么抛弃未知消息而不是抛出错误呢?这是由服务器竞争资源所导致的。尽管这不太可能,试想一下,如果RPC服务器在发送完响应后而在发送应答消息前死掉了,重启RPC服务器会重新发送请求。这就是我们在客户机上优雅地处理重复的反应,RPC应该是等同的。


wKioL1YoynDhkgSMAACbyFOQwfU789.jpg


  (1)客户端启动,创建一个匿名且唯一的回调队列。


  (2)对每个RPC请求,客户端发送一个包含replyTo和correlationId两个属性的消息。


  (3)请求发送到rpc_queue队列。


  (4)RPC服务在队列中等待请求,当请求出现时,根据replyTo字段使用队列将结果发送到客户端。


  (5)客户端在回调队列中等待数据。当消息出现时,它会检查correlationId属性,如果该值匹配的话,就会返回响应结果给应用。


  示例代码


  RPCServer.java

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package  com.favccxx.favrabbit;
 
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) {
         if  (n ==  0 )
             return  0 ;
         if  (n ==  1 )
             return  1 ;
         return  fib(n -  1 ) + fib(n -  2 );
     }
 
     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( 1 );
 
             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) {
                 }
             }
         }
     }
}

  RPCClient.java       

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package  com.favccxx.favrabbit;
 
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( "UTF-8" ));
 
         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( "30" );
             System.out.println( " [.] Got '"  + response +  "'" );
         catch  (Exception e) {
             e.printStackTrace();
         finally  {
             if  (fibonacciRpc !=  null ) {
                 try  {
                     fibonacciRpc.close();
                 catch  (Exception ignore) {
                 }
             }
         }
     }
}


  先启动RPCServer,然后运行RPCClient,控制台输出如下内容

RPCClient[x] Requesting fib(30)

RPCClient[.] Got '832040'


RPCServer[x] Awaiting RPC requests

RPCServer[.] fib(30)






本文转自 genuinecx 51CTO博客,原文链接:http://blog.51cto.com/favccxx/1705357,如需转载请自行联系原作者
相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
2月前
|
消息中间件 JSON Java
开发者如何使用轻量消息队列MNS
【10月更文挑战第19天】开发者如何使用轻量消息队列MNS
81 5
|
1月前
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
2月前
|
消息中间件 安全 Java
云消息队列RabbitMQ实践解决方案评测
一文带你详细了解云消息队列RabbitMQ实践的解决方案优与劣
76 9
|
2月前
|
消息中间件
解决方案 | 云消息队列RabbitMQ实践获奖名单公布!
云消息队列RabbitMQ实践获奖名单公布!
|
2月前
|
消息中间件 存储 弹性计算
云消息队列RabbitMQ实践
云消息队列RabbitMQ实践
|
2月前
|
消息中间件 存储 监控
解决方案 | 云消息队列RabbitMQ实践
在实际业务中,网站因消息堆积和高流量脉冲导致系统故障。为解决这些问题,云消息队列 RabbitMQ 版提供高性能的消息处理和海量消息堆积能力,确保系统在流量高峰时仍能稳定运行。迁移前需进行技术能力和成本效益评估,包括功能、性能、限制值及费用等方面。迁移步骤包括元数据迁移、创建用户、网络打通和数据迁移。
69 4
|
3月前
|
消息中间件 监控 数据处理
解决方案 | 云消息队列RabbitMQ实践
解决方案 | 云消息队列RabbitMQ实践
52 1
|
2月前
|
消息中间件 监控 测试技术
云消息队列RabbitMQ实践 - 评测
根据反馈,对本解决方案的实践原理已有一定理解,描述整体清晰但需在消息队列配置与使用上增加更多示例和说明以助理解。部署体验中获得了一定的引导和文档支持,尽管文档仍有待完善;期间出现的配置文件错误及依赖库缺失等问题已通过查阅资料解决。设计验证展示了云消息队列RabbitMQ的核心优势,包括高可用性和灵活性,未来可通过增加自动化测试来提高系统稳定性。实践后,用户对方案解决问题的能力及适用场景有了明确认识,认为其具有实际生产价值,不过仍需在性能优化、安全性增强及监控功能上进行改进以适应高并发和大数据量环境。
44 0
|
5月前
|
消息中间件 C语言 RocketMQ
消息队列 MQ操作报错合集之出现"Connection reset by peer"的错误,该如何处理
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
5月前
|
消息中间件 Java C语言
消息队列 MQ使用问题之在使用C++客户端和GBase的ESQL进行编译时出现core dump,该怎么办
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。

相关产品

  • 云消息队列 MQ