RabbitMQ官網中文版教程:
http://rabbitmq.mr-ping.com/tutorials_with_python/[6]RPC.html
上述教程示例為pathon版,Java版及相應解釋如下:
RPC客戶端
package com.xc.rabbitmq.rpc;
import com.rabbitmq.client.*;
import java.util.UUID;
/**
* Created by xc.
*/
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");
factory.setPort(5672);
factory.setUsername("rabbit");
factory.setPassword("carrot");
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();
AMQP.BasicProperties props = new AMQP.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[] args) {
RpcClient client = null;
String response = null;
try {
client = new RpcClient();
System.out.println("RPCClient [x] Requesting fib(30)");
response = client.call("30");
System.out.println("RPCClient [.] Got '" + response + "'");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (client != null) {
try {
client.close();
} catch (Exception e) {
;
}
}
}
}
}
RPC服務端
package com.xc.rabbitmq.rpc;
import com.rabbitmq.client.*;
/**
* Created by xc.
*/
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[] args) {
Connection connection = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("rabbit");
factory.setPassword("carrot");
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("RPCServer [x] Awaiting RPC requests");
while (true) {
String response = null;
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
BasicProperties props = delivery.getProperties();
AMQP.BasicProperties replyProps = new AMQP.BasicProperties.Builder().correlationId(props.getCorrelationId()).build();
try {
String message = new String(delivery.getBody(), "UTF-8");
int n = Integer.parseInt(message);
System.out.println("RPCServer [.] fib(" + message + ")");
response = "" + fib(n);
} catch (Exception e) {
System.out.println(" [.] " + e.toString());
response = null;
} 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 e) {
;
}
}
}
}
}
先運行服務端程序,再運行客戶端程序,結果如下:
注意:
1)消息屬性(AMQP.BasicProperties)
AMQP協議給消息預定義了一系列的14個屬性。大多數屬性很少會用到,除了以下幾個:
- delivery_mode(投遞模式):將消息標記為持久的(值為2)或暫存的(除了2之外的其他任何值)。第二篇教程里接觸過這個屬性,記得吧?
- content_type(內容類型):用來描述編碼的mime-type。例如在實際使用中常常使用application/json來描述JOSN編碼類型。
- reply_to(回復目標):通常用來命名回調隊列。
- correlation_id(關聯標識):用來將RPC的響應和請求關聯起來。
2) 如果我們接手到的消息的correlation_id是未知的,那就直接銷毀掉它,因為它不屬于我們的任何一條請求。
為什么我們接收到未知消息的時候不拋出一個錯誤,而是要將它忽略掉?這是為了解決服務器端有可能發生的競爭情況。盡管可能性不大,但RPC服務器還是有可能在已將應答發送給我們但還未將確認消息發送給請求的情況下死掉。如果這種情況發生,RPC在重啟后會重新處理請求。這就是為什么我們必須在客戶端優雅的處理重復響應,同時RPC也需要盡可能保持冪等性。
3)此處呈現的設計并不是實現RPC服務的唯一方式,但是他有一些重要的優勢:
如果RPC服務器運行的過慢的時候,你可以通過運行另外一個服務器端輕松擴展它。試試在控制臺中運行第二個 rpc_server.py 。
在客戶端,RPC請求只發送或接收一條消息。不需要像 queue_declare 這樣的異步調用。所以RPC客戶端的單個請求只需要一個網絡往返。
4)我們的代碼依舊非常簡單,而且沒有試圖去解決一些復雜(但是重要)的問題,如:
當沒有服務器運行時,客戶端如何作出反映。
客戶端是否需要實現類似RPC超時的東西。
如果服務器發生故障,并且拋出異常,應該被轉發到客戶端嗎?
在處理前,防止混入無效的信息(例如檢查邊界)