RabbitMQ/RPC/TTL/死信隊列

當需要調用遠端計算機的函數并等待結果,這模式通常被稱為遠程過程調用或RPC。

BasicProperties:
消息屬性 這AMQP協議預先確定了消息中的14個屬性。常用的有:

  • deliveryMode
    將一個消息標記為持久化(值為2)或者瞬態的(其他值)。之前發送隊列消息時的用法:

      channel.basicPublish("", "task_queue",MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
    
  • contentType
    用來描述媒體類型的編碼。例如常常使用的JSON編碼:application/json。

  • replyTo
    通常來命名回收隊列的名字。

  • correlationId
    對RPC加速響應請求是很有用的。

相關性ID(Correlation Id):
為每一個RPC請求創建一個回收隊列,這個效率十分低下。每一個客戶端創建一個單一的回收隊列。 用Correlation Id判斷隊列中的響應是屬于哪個請求的。
當在回收隊列中接收消息時,依據這個屬性值,刻意將每個響應匹配到對應的請求上。如果是未知的Correlation Id值,就丟棄這個消息,因為它不屬于任何一個我們的請求。

請求RPC服務:

當客戶端啟動,它會創建一個匿名的獨占的回收隊列。 對于一個RPC請求,客戶端會發送一個消息到rpc_queue隊列中,其中有兩個屬性:replyTo(回復隊列)和correlationId(每一個請求都是唯一值)。

服務端:

package testrabbitmq;
import com.rabbitmq.client.*;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * Created by zzhblh on 2016/8/29.
 */
public class RPCServer {

    private static final String RPC_QUEUE_NAME = "rpc_queue";

    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);
    }

    public static void main(String[] argv) throws Exception
    {
        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(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 response = "" + fib(n);
            channel.basicPublish( "", props.getReplyTo(), replyProps, response.getBytes());
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

客戶端:

package testrabbitmq;
import com.rabbitmq.client.*;
/**
 * Created by zzhblh on 2016/8/29.
 */
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 = java.util.UUID.randomUUID().toString();

        AMQP.BasicProperties props = new AMQP.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();
    }
}
  • contentType
    用來描述媒體類型的編碼。例如常常使用的JSON編碼,這是一個好的慣例,設置這個屬性為:application/json。
  • replyTo
    通常來命名回收隊列的名字。
  • correlationId
    對RPC加速響應請求是很有用的。

相關性ID(Correlation Id):
為每一個RPC請求創建一個回收隊列,這個效率十分低下。每一個客戶端創建一個單一的回收隊列。 用Correlation Id判斷隊列中的響應是屬于哪個請求的。
當在回收隊列中接收消息時,依據這個屬性值,刻意將每個響應匹配到對應的請求上。如果是未知的Correlation Id值,就丟棄這個消息,因為它不屬于任何一個我們的請求。

請求RPC服務:

當客戶端啟動,它會創建一個匿名的獨占的回收隊列。 對于一個RPC請求,客戶端會發送一個消息到rpc_queue隊列中,其中有兩個屬性:replyTo(回復隊列)和correlationId(每一個請求都是唯一值)。

服務端:

package testrabbitmq;
import com.rabbitmq.client.*;
import com.rabbitmq.client.AMQP.BasicProperties;

/**
 * Created by zzhblh on 2016/8/29.
 */
public class RPCServer {

    private static final String RPC_QUEUE_NAME = "rpc_queue";

    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);
    }

    public static void main(String[] argv) throws Exception
    {
        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(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 response = "" + fib(n);
            channel.basicPublish( "", props.getReplyTo(), replyProps, response.getBytes());
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

客戶端:

package testrabbitmq;
import com.rabbitmq.client.*;
/**
 * Created by zzhblh on 2016/8/29.
 */
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 = java.util.UUID.randomUUID().toString();

        AMQP.BasicProperties props = new AMQP.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();
    }
}

TTL

  • Queue Message TTL
    設置某隊列中所有消息的TTL,通過在 queue.declare 中設置 x-message-ttl 參數,可以控制被 publish 到 queue 中的 message 被丟棄前能夠存活的時間。值得注意的是,當一個 message 被路由到多個 queue 中時,他們之間不會互相影響。
Map<String, Object> args = new HashMap<String, Object>();  
args.put("x-message-ttl", 60000);//存活時間最大為 60 秒
channel.queueDeclare("myqueue", false, false, false, args);
  • Per-Message TTL
    TTL 設置可以具體到每一條 message 本身,只要在通過 basic.publish 命令發送 message 時設置 expiration 字段。expiration 字段必須為字符串類型。
AMQP.BasicProperties properties = new AMQP.BasicProperties();  
properties.setExpiration("60000");  
channel.basicPublish("myexchange", "routingkey", properties, message.getBytes());

對于第一種設置隊列TTL屬性的方法,一旦消息過期,就會從隊列中抹去。而第二種方法里,即使消息過期,也不會馬上從隊列中抹去,在過期 message 到達 queue 的頭部時被真正的丟棄。

  • Queue TTL
    queue 被自動刪除前可以處于未使用狀態的時間。未使用的意思是 queue 上沒有任何 consumer ,queue 沒有被重新聲明,并且在過期時間段內未調用過 basic.get 命令。在服務器重啟后,持久化的 queue(本來還未過期的) 的超時時間將重新計算。
Map<String, Object> args = new HashMap<String, Object>();  
args.put("x-expires", 1800000);  
channel.queueDeclare("myqueue", false, false, false, args);  

DLX(死信)
利用DLX,當消息在一個隊列中變成死信后,它能被重新publish到另一個Exchange,這個Exchange就是DLX。消息變成死信一向有以下幾種情況:

  • 消息被拒絕(basic.reject or basic.nack)并且requeue=false
  • 消息TTL過期
  • 隊列達到最大長度

DLX也是一下正常的Exchange同一般的Exchange沒有區別,它能在任何的隊列上被指定,實際上就是設置某個隊列的屬性,當這個隊列中有死信時,RabbitMQ就會自動的將這個消息重新發布到設置的Exchange中去,進而被路由到另一個隊列, publish可以監聽這個隊列中消息做相應的處理,這個特性可以彌補RabbitMQ 3.0.0以前支持的immediate參數中的向publish確認的功能。

channel.exchangeDeclare("some.exchange.name", "direct");  
Map<String, Object> args = new HashMap<String, Object>();  
args.put("x-dead-letter-exchange", "some.exchange.name");  
channel.queueDeclare("myqueue", false, false, false, args); 

你也可以為這個DLX指定routing key,如果沒有特殊指定,則使用原隊列的routing key

args.put("x-dead-letter-routing-key", "some-routing-key"); 

http://memorynotfound.com/produce-consume-rabbitmq-spring-json-message-queue/
http://blog.csdn.net/jiao_fuyou/article/details/22923935

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • 來源 RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。支持消息的持久化、事務、擁塞控...
    jiangmo閱讀 10,380評論 2 34
  • rabbitMQ是一款基于AMQP協議的消息中間件,它能夠在應用之間提供可靠的消息傳輸。在易用性,擴展性,高可用性...
    點融黑幫閱讀 3,021評論 3 41
  • 1. 歷史 RabbitMQ是一個由erlang開發的AMQP(Advanced Message Queue )的...
    高廣超閱讀 6,114評論 3 51
  • 1.什么是消息隊列 消息隊列允許應用間通過消息的發送與接收的方式進行通信,當消息接收方服務忙或不可用時,其提供了一...
    zhuke閱讀 4,495評論 0 12