在第二章中咱們學習瞭如何使用Work模式在多個worker之間派發時間敏感的任務。這種狀況是不涉及到返回值的,worker執行任務就好。若是涉及返回值,就要用到本章提到的RPC(Remote Procedure Call)了。java
本章咱們使用RabbitMQ來構建一個RPC系統:一個客戶端和一個可擴展的RPC服務端。咱們讓RPC服務返回一個斐波那契數組。json
咱們建立一個簡單的客戶端類來演示如何使用RPC服務。call方法發送RPC請求,並阻塞知道結果返回。數組
FibonacciRpcClient fibonacciRpc = new FibonacciRpcClient(); String result = fibonacciRpc.call("4"); System.out.println( "fib(4) is " + result);
RPC貼士
雖然RPC的使用在計算機領域很是廣泛,可是卻常常受到批評。主要問題是編碼人若是不注意使用的方法是本地仍是遠程時,每每會形成問題。每每讓系統變得不可預知,增長沒必要要的複雜性和調試的難度。對此咱們有以下幾點建議:安全
- 是本地方法仍是遠程方法要一目瞭然
- 把系統的依賴寫進文檔
- 系統要處理好超時的問題
若是能夠儘可能使用異步的pipeline來替代像RPC這種阻塞的操做。服務器
在RabbitMQ上實現RPC是很是簡單的。客戶端發送一個request message,服務端迴應一個response message。爲了接受response message咱們須要在發送request message的時候附帶上'callback' queue的地址。咱們可使用默認的queue。app
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 ...
Message的屬性
AMQP 0-9-1協議預約義了14個消息屬性,其中大部分不多使用,下面的屬性較爲經常使用dom
- deliverMode: 標記message爲持久(設置爲2)或其餘值。
- contentType:message的編碼類型,咱們常用JSON編碼,則設置爲application/json
- replyTo: 命名回調queue
- correlationId:將RPC的請求和迴應關聯起來
須要引入新的類異步
import com.rabbitmq.client.AMQP.BasicProperties;
在上面的代碼中,每次RPC請求都會建立一個用於回調的臨時queue,咱們有更好的方法,咱們爲每個client建立一個回調queue。函數
可是這樣有新的問題,從回調queue中收到response沒法和相應的request關聯起來。這時候就是correlationId屬性發揮做用的時候了。爲每一個request中設置惟一的值,在稍後的回調queue中收到的response裏也有這個屬性,基於此,咱們就能夠關聯以前的request了。若是咱們遇到一個匹配不到的correlationId,那麼丟棄的行爲是安全的。學習
你可能會問,爲何咱們忽略這些沒法匹配的message,而不是當作一個錯誤處理呢?主要是考慮服務端的競態條件,若是RPC服務器在發送response以後就宕機了,可是卻沒有發送ack消息。那麼當RPC Server重啓以後,會繼續執行這個request。這就是爲何client須要冪等處理response。
咱們的RPC向下面這樣進行工做:
斐波那契處理函數
private static int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n-1) + fib(n-2); }
這是一個簡易的實現,若是傳入一個較大的值,將會是個災難。
RPC服務器的代碼爲RPCServer.java, 代碼是很簡單明確的
RPC客戶端的代碼爲RPCClient.java,代碼略微有點複雜
RPCClient.java完整代碼
import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeoutException; public class RPCClient implements AutoCloseable { private Connection connection; private Channel channel; private String requestQueueName = "rpc_queue"; public RPCClient() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); connection = factory.newConnection(); channel = connection.createChannel(); } public static void main(String[] argv) { try (RPCClient fibonacciRpc = new RPCClient()) { for (int i = 0; i < 32; i++) { String i_str = Integer.toString(i); System.out.println(" [x] Requesting fib(" + i_str + ")"); String response = fibonacciRpc.call(i_str); System.out.println(" [.] Got '" + response + "'"); } } catch (IOException | TimeoutException | InterruptedException e) { e.printStackTrace(); } } public String call(String message) throws IOException, InterruptedException { final String corrId = UUID.randomUUID().toString(); String replyQueueName = channel.queueDeclare().getQueue(); AMQP.BasicProperties props = new AMQP.BasicProperties .Builder() .correlationId(corrId) .replyTo(replyQueueName) .build(); channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); final BlockingQueue<String> response = new ArrayBlockingQueue<>(1); String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> { if (delivery.getProperties().getCorrelationId().equals(corrId)) { response.offer(new String(delivery.getBody(), "UTF-8")); } }, consumerTag -> { }); String result = response.take(); channel.basicCancel(ctag); return result; } public void close() throws IOException { connection.close(); } }
RPCServer.java完整代碼
import com.rabbitmq.client.*; 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) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null); channel.queuePurge(RPC_QUEUE_NAME); channel.basicQos(1); System.out.println(" [x] Awaiting RPC requests"); Object monitor = new Object(); DeliverCallback deliverCallback = (consumerTag, delivery) -> { AMQP.BasicProperties replyProps = new AMQP.BasicProperties .Builder() .correlationId(delivery.getProperties().getCorrelationId()) .build(); String response = ""; try { String message = new String(delivery.getBody(), "UTF-8"); int n = Integer.parseInt(message); System.out.println(" [.] fib(" + message + ")"); response += fib(n); } catch (RuntimeException e) { System.out.println(" [.] " + e.toString()); } finally { channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes("UTF-8")); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); // RabbitMq consumer worker thread notifies the RPC server owner thread synchronized (monitor) { monitor.notify(); } } }; channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> { })); // Wait and be prepared to consume the message from RPC client. while (true) { synchronized (monitor) { try { monitor.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }