RabbitMQ簡單實現,exchange四種模式,持久化

RabbitMQ

目錄

一、簡介,簡單實現

二、Exchange四種類型簡單介紹

三、消息確認,交換機、隊列及消息持久化

一、簡介及簡單實現

RabbitMQ是一個消息代理:它接受並轉發消息。你可以把它當成一個郵局:當你想郵寄信件的時候,你會把信件放在投遞箱中,並確信郵遞員最終會將信件送到收件人的手裏。在這個例子中,RabbitMQ就相當與投遞箱、郵局和郵遞員。
RabbitMQ與郵局的區別在於:RabbitMQ並不處理紙質信件,而是接受、存儲並轉發二進制數據—消息。
談到RabbitMQ的消息,通常有幾個術語:

生產者:是指發送消息的程序
隊列:相當於RabbitMQ的投遞箱。儘管消息在RabbitMQ和你的應用之間傳遞,但是消息僅僅會在隊列之中存儲。隊列只能存儲在內存或磁盤中,本質上是一個大的消息緩衝區。不同的生產者可以發送消息到同一個對隊列,不同的消費者也可以從同一個隊列中獲取消息。
消費者:等待接受消息的程序。
Rabbit.png

簡單實現

生產者
1、創建連接:ConnectionFactory、Connection、Channel
2、指明隊列:channel.queueDeclare(QUEUE_NAME, false, false, false, null);

queueDeclare(String queue, //隊列名稱
            boolean durable, //是否持久化
            boolean exclusive, //是否排外
            Map<String, Object> arguments);//其他信息(自動刪除等)

3、發送消息:channel.basicPublish()

void basicPublish(String exchange,//交換機Exchange名稱
				  String routingKey,//路遊鍵
				  BasicProperties props,//其他屬性
				  byte[] body) throws IOException;//內容

4、關閉連接: channel.close();
connection.close();

package first;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 生產者
 * Created by GXR on 2019/3/9.
 */
public class Send {

    private final static String QUEUE_NAME = "first";

    public static void main(String[] args) {
        try {
            //創建連接和通道
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            //通道指明隊列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "helloworld";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
            System.out.println("[First]Send:" + message);
            //關閉連接
            channel.close();
            connection.close();
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
    }

}

消費者(接收者)
1、創建連接:ConnectionFactory、Connection、Channel
2、指明隊列:channel.queueDeclare(QUEUE_NAME, false, false, false, null);
3、獲取消息:Consumer
4、消息確認:channel.basicConsume(QUEUE_NAME, true, consumer);//隊列名稱,是否自動確認,消費者

package first;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消費者
 * Created by GXR on 2019/3/9.
 */
public class Recive {

    private final static String QUEUE_NAME = "first";

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            //指明隊列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //獲取消息
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                        throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println(" [First] Received :" + message);
                }
            };
            channel.basicConsume(QUEUE_NAME, false, consumer);
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
    }

}

Exchange四種類型簡單介紹

Exchange與隊列綁定

channel.exchangeDeclare(Exchange_name,ExchangeType);//exchange名稱及類型

在之前的介紹裏,我們都是直接往隊列裏發送消息,然後又直接從隊列裏取出消息。
RabbitMQ的消息模型中的一個核心思想是,生產者絕不會將消息直接發送到隊列中,實際上,在大部分場景中生產者根本不知道消息會發送到哪些隊列中。
相反,生產者只會將消息發送給一個Exchange(路由器/交換器)。Exchange其實很簡單,它所做的就是,接收生產者發來的消息,並將這些消息推送到隊列中。Exchange必須清楚地知道怎麼處理接收到的消息:是將消息放到一個特定的隊列中,還是放到多個隊列中,還是直接將消息丟棄。下圖示意了Exchange在消息模型中的位置:
在這裏插入圖片描述
Exchange一共有四種類型:direct、topic、headers 和fanout。
效率:fanout > direct > topic

  • Direct Exchange:將消息中的Routing key與該Exchange關聯的所有Binding中的Routing key進行比較,如果相等,則發送到該Binding對應的Queue中。
  • Topic Exchange:將消息中的Routing key與該Exchange關聯的所有Binding中的Routing key進行對比,如果匹配上了,則發送到該Binding對應的Queue中。
  • Fanout Exchange:直接將消息轉發到所有binding的對應queue中,這種exchange在路由轉發的時候,忽略Routing key。
  • Headers Exchange:將消息中的headers與該Exchange相關聯的所有Binging中的參數進行匹配,如果匹配上了,則發送到該Binding對應的Queue中。

direct、fanout類型的Exchange易於理解不做過多介紹。着重介紹topic類型的Exchange類型。

topic類型Exchange(路由器)routingKey匹配規則

  • *匹配一個單詞
  • #匹配0個或多個字符
  • *,# 只能寫在.號左右,且不能挨着字符
  • 單詞和單詞之間需要用.隔開

在這裏插入圖片描述
對於上圖的例子,我們將會發送描述動物的消息。這些消息將會以由三個單詞組成的路由鍵發送。路由鍵中的第一個單詞描述了速度,第二個描述了顏色,第三個描述了物種:..。
我們創建了三個綁定,Q1的綁定鍵爲*.orange.,Q2的綁定鍵有兩個,分別是.*.rabbit和lazy.#。
上述綁定關係可以描述爲:
①Q1關注所有顏色爲orange的動物。
②Q2關注所有的rabbit,以及所有的lazy的動物。
如果一個消息的路由鍵是quick.orange.rabbit,那麼Q1和Q2都可以接收到,路由鍵是lazy.orange.elephant的消息同樣如此。但是,路由鍵是quick.orange.fox的消息只會到達Q1,路由鍵是lazy.brown.fox的消息只會到達Q2。注意,路由鍵爲lazy.pink.rabbit的消息只會到達Q2一次,儘管它匹配了兩個綁定鍵。路由鍵爲quick.brown.fox的消息因爲不和任意的綁定鍵匹配,所以將會被丟棄。
假如我們不按常理出牌:發送一個路由鍵只有一個單詞或者四個單詞的消息,像orange或者quick.orange.male.rabbit,這樣的話,這些消息因爲不和任意綁定鍵匹配,都將會丟棄。但是,lazy.orange.male.rabbit消息因爲和lazy.#匹配,所以會到達Q2,儘管它包含四個單詞。

消息確認、持久化

消息確認
爲了確保消息永遠不會丟失,RabbitMQ支持消息確認。消費者將會發送一個確認信息來告訴RabbitMQ,我已經接收到了消息,並且處理完了,你可以隨便刪它了。
如果一個消費者在發送確認信息前死去(連接或通道關閉、TCP連接丟失等),RabbitMQ將會認爲該消息沒有被完全處理並會重新將消息加入隊列。如果此時有其他的消費者,RabbitMQ很快就會重新發送該消息到其他的消費者。通過這種方式,你完全可以保證沒有消息丟失,即使某個消費者意外死亡。
對RabbitMQ而言,沒有消息超時這一說。如果消費者死去,RabbitMQ將會重新發送消息。即使處理一個消息需要耗時很久很久也沒有關係。

package secound;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消費者
 * Created by GXR on 2019/3/9.
 */
public class Recive2 {

    private final static String QUEUE_NAME = "secound";

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            //指明隊列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //最大接收數
            channel.basicQos(1);
            //獲取消息
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                        throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println(" [Secound] Received2 :" + message);
                }
            };
            boolean autoAck = true;//是否自動確定
            channel.basicConsume(QUEUE_NAME, autoAck, consumer);
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
    }

}

queue的持久化
queue的持久化是通過durable=true來實現的。

Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("queueName", durable, false, false, null);

queueDeclare完整定義

    /**
     * Declare a queue
     * @see com.rabbitmq.client.AMQP.Queue.Declare
     * @see com.rabbitmq.client.AMQP.Queue.DeclareOk
     * @param queue the name of the queue
     * @param durable true if we are declaring a durable queue (the queue will survive a server restart)
     * @param exclusive true if we are declaring an exclusive queue (restricted to this connection)
     * @param autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use)
     * @param arguments other properties (construction arguments) for the queue
     * @return a declaration-confirm method to indicate the queue was successfully declared
     * @throws java.io.IOException if an error is encountered
     */
    Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments) throws IOException;
queue:queue的名稱

exclusive:排他隊列,如果一個隊列被聲明爲排他隊列,該隊列僅對首次申明它的連接可見,並在連接斷開時自動刪除。這裏需要注意三點:1. 排他隊列是基於連接可見的,同一連接的不同信道是可以同時訪問同一連接創建的排他隊列;2.“首次”,如果一個連接已經聲明瞭一個排他隊列,其他連接是不允許建立同名的排他隊列的,這個與普通隊列不同;3.即使該隊列是持久化的,一旦連接關閉或者客戶端退出,該排他隊列都會被自動刪除的,這種隊列適用於一個客戶端發送讀取消息的應用場景。

autoDelete:自動刪除,如果該隊列沒有任何訂閱的消費者的話,該隊列會被自動刪除。這種隊列適用於臨時隊列。

消息的持久化
如過將queue的持久化標識durable設置爲true,則代表是一個持久的隊列,那麼在服務重啓之後,也會存在,因爲服務會把持久化的queue存放在硬盤上,當服務重啓的時候,會重新什麼之前被持久化的queue。隊列是可以被持久化,但是裏面的消息是否爲持久化那還要看消息的持久化設置。也就是說,重啓之前那個queue裏面還沒有發出去的消息的話,重啓之後那隊列裏面是不是還存在原來的消息,這個就要取決於發生着在發送消息時對消息的設置了。
如果要在重啓後保持消息的持久化必須設置消息是持久化的標識。

設置消息的持久化:

channel.basicPublish("exchangeName", routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, "message".getBytes());

這裏的關鍵是:MessageProperties.PERSISTENT_TEXT_PLAIN
首先看一下basicPublish的方法:

void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
        throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
        throws IOException;

exchange表示exchange的名稱
routingKey表示routingKey的名稱
body代表發送的消息體
這裏關鍵的是BasicProperties props這個參數了,這裏看下BasicProperties的定義:

public BasicProperties(
            String contentType,//消息類型如:text/plain
            String contentEncoding,//編碼
            Map<String,Object> headers,
            Integer deliveryMode,//1:nonpersistent 2:persistent
            Integer priority,//優先級
            String correlationId,
            String replyTo,//反饋隊列
            String expiration,//expiration到期時間
            String messageId,
            Date timestamp,
            String type,
            String userId,
            String appId,
            String clusterId)

這裏的deliveryMode=1代表不持久化,deliveryMode=2代表持久化。
exchange的持久化
上面闡述了隊列的持久化和消息的持久化,如果不設置exchange的持久化對消息的可靠性來說沒有什麼影響,但是同樣如果exchange不設置持久化,那麼當broker服務重啓之後,exchange將不復存在,那麼既而發送方rabbitmq producer就無法正常發送消息。這裏博主建議,同樣設置exchange的持久化。exchange的持久化設置也特別簡單,一般只需要:

channel.exchangeDeclare(exchangeName, “direct/topic/header/fanout”, true);
//即在聲明的時候講durable字段設置爲true即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章