RabbitMQ入門教程(概念,應用場景,安裝,使用)

RabbitMQ 簡介

RabbitMQ是一個在AMQP(Advanced Message Queuing Protocol )基礎上實現的,可復用的企業消息系統。它可以用于大型軟件系統各個模塊之間的高效通信,支持高并發,支持可擴展。

AMQP

AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,為面向消息的中間件設計。基于此協議的客戶端與消息中間件可傳遞消息,并不受客戶端/中間件不同產品,不同的開發語言等條件的限制。

消息隊列

MQ 全稱為Message Queue, 消息隊列。是一種應用程序對應用程序的通信方法。應用程序通過讀寫出入隊列的消息(針對應用程序的數據)來通信,而無需專用連接來鏈接它們。

消息傳遞指的是程序之間通過在消息中發送數據進行通信,而不是通過直接調用彼此來通信。隊列的使用除去了接收和發送應用程序同時執行的要求。

在項目中,將一些無需即時返回且耗時的操作提取出來,進行了異步處理,而這種異步處理的方式大大的節省了服務器的請求響應時間,從而提高了系統的吞吐量。

消息隊列的使用場景是怎樣的?小紅和小明讀書的例子

RabbitMQ 應用場景

對于一個大型的軟件系統來說,它會有很多的組件或者說模塊或者說子系統或者(subsystem or Component or submodule)。那么這些模塊的如何通信?這和傳統的IPC有很大的區別。傳統的IPC很多都是在單一系統上的,模塊耦合性很大,不適合擴展(Scalability);如果使用socket那么不同的模塊的確可以部署到不同的機器上,但是還是有很多問題需要解決。比如:
1)信息的發送者和接收者如何維持這個連接,如果一方的連接中斷,這期間的數據如何方式丟失?
2)如何降低發送者和接收者的耦合度?
3)如何讓Priority高的接收者先接到數據?
4)如何做到load balance?有效均衡接收者的負載?
5)如何有效的將數據發送到相關的接收者?也就是說將接收者subscribe 不同的數據,如何做有效的filter。
6)如何做到可擴展,甚至將這個通信模塊發到cluster上?
7)如何保證接收者接收到了完整,正確的數據?
AMDQ協議解決了以上的問題,而RabbitMQ實現了AMQP

概念介紹

  • Broker:簡單來說就是消息隊列服務器實體。
  • Exchange:消息交換機,它指定消息按什么規則,路由到哪個隊列。
  • Queue:消息隊列載體,每個消息都會被投入到一個或多個隊列。
  • Binding:綁定,它的作用就是把exchange和queue按照路由規則綁定起來。
  • Routing Key:路由關鍵字,exchange根據這個關鍵字進行消息投遞。
  • vhost:虛擬主機,一個broker里可以開設多個vhost,用作不同用戶的權限分離。
  • producer:消息生產者,就是投遞消息的程序。
  • consumer:消息消費者,就是接受消息的程序。
  • channel:消息通道,在客戶端的每個連接里,可建立多個channel,每個channel代表一個會話任務。

RabbitMQ使用流程

AMQP模型中,消息在producer中產生,發送到MQ的exchange上,exchange根據配置的路由方式發到相應的Queue上,Queue又將消息發送給consumer,消息從queue到consumer有push和pull兩種方式。 消息隊列的使用過程大概如下:

  1. 客戶端連接到消息隊列服務器,打開一個channel。
  2. 客戶端聲明一個exchange,并設置相關屬性。
  3. 客戶端聲明一個queue,并設置相關屬性。
  4. 客戶端使用routing key,在exchange和queue之間建立好綁定關系。
  5. 客戶端投遞消息到exchange。

exchange接收到消息后,就根據消息的key和已經設置的binding,進行消息路由,將消息投遞到一個或多個隊列里。 exchange也有幾個類型,完全根據key進行投遞的叫做Direct交換機,例如,綁定時設置了routing key為”abc”,那么客戶端提交的消息,只有設置了key為”abc”的才會投遞到隊列。

RabbitMQ安裝教程

rabbitMQ常用的命令

啟動監控管理器:rabbitmq-plugins enable rabbitmq_management
關閉監控管理器:rabbitmq-plugins disable rabbitmq_management
啟動rabbitmq:rabbitmq-service start
關閉rabbitmq:rabbitmq-service stop
查看所有的隊列:rabbitmqctl list_queues
清除所有的隊列:rabbitmqctl reset
關閉應用:rabbitmqctl stop_app
啟動應用:rabbitmqctl start_app

用戶和權限設置
添加用戶:rabbitmqctl add_user username password
分配角色:rabbitmqctl set_user_tags username administrator
新增虛擬主機:rabbitmqctl add_vhost vhost_name
將新虛擬主機授權給新用戶:rabbitmqctl set_permissions -p vhost_name username “.*” “.*” “.*”(后面三個”*”代表用戶擁有配置、寫、讀全部權限)

角色說明

  • 超級管理員(administrator)
    可登陸管理控制臺,可查看所有的信息,并且可以對用戶,策略(policy)進行操作。
  • 監控者(monitoring)
    可登陸管理控制臺,同時可以查看rabbitmq節點的相關信息(進程數,內存使用情況,磁盤使用情況等)
  • 策略制定者(policymaker)
    可登陸管理控制臺, 同時可以對policy進行管理。但無法查看節點的相關信息(上圖紅框標識的部分)。
  • 普通管理者(management)
    僅可登陸管理控制臺,無法看到節點信息,也無法對策略進行管理。
  • 其他
    無法登陸管理控制臺,通常就是普通的生產者和消費者。

Java入門實例(Helloworld)


一個producer發送消息,一個接收者接收消息,并在控制臺打印出來。如下圖:

Java客戶端配置

下面是Java客戶端的maven依賴的配置。

<dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.0.0</version>
</dependency>

發送端:Send.java 連接到RabbitMQ(此時服務需要啟動),發送一條數據,然后退出。

package cn.buyforyou;

import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.ConnectionFactory;  
  
public class Send  
{  
    //隊列名稱  
    private final static String QUEUE_NAME = "helloMQ";  
  
    public static void main(String[] argv) throws java.io.IOException, TimeoutException  
    {  
        /** 
         * 創建連接連接到MabbitMQ 
         */  
        ConnectionFactory factory = new ConnectionFactory();  
        //設置MabbitMQ所在主機ip或者主機名  
        factory.setHost("localhost");  
        //創建一個連接  
        Connection connection = factory.newConnection();  
        //創建一個頻道  
        Channel channel = connection.createChannel();  
        //指定一個隊列  
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);  
        //發送的消息  
        String message = "hello world!";  
        //往隊列中發出一條消息  
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());  
        System.out.println(" [x] Sent '" + message + "'");  
        //關閉頻道和連接  
        channel.close();  
        connection.close();  
     }  
}  

值得注意的是隊列只會在它不存在的時候創建,多次聲明并不會重復創建。信息的內容是字節數組,也就意味著你可以傳遞任何數據。
接收端:Recv.java 不斷等待服務器推送消息,然后在控制臺輸出。

package cn.buyforyou;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {

    // 隊列名稱
    private final static String QUEUE_NAME = "helloMQ";

    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(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //創建消費者
        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(" [x] Received '" + message + "'");
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

RabbitMQ工作隊列-Work Queues(Java實例)


創建一個工作隊列用來在工作者(consumer)間分發耗時任務。

工作隊列的主要任務是:避免立刻執行資源密集型任務,然后必須等待其完成。相反地,我們進行任務調度:我們把任務封裝為消息發送給隊列。工作進行在后臺運行并不斷的從隊列中取出任務然后執行。當你運行了多個工作進程時,任務隊列中的任務將會被工作進程共享執行。
這樣的概念在web應用中極其有用,當在很短的HTTP請求間需要執行復雜的任務。

準備

我們使用Thread.sleep來模擬耗時的任務。我們在發送到隊列的消息的末尾添加一定數量的點,每個點代表在工作線程中需要耗時1秒,例如hello…將會需要等待3秒。

發送端:

NewTask.java

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

public class NewTask {

  private static final String TASK_QUEUE_NAME = "task_queue";

  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(TASK_QUEUE_NAME, true, false, false, null);

    String message = getMessage(argv);

    channel.basicPublish("", TASK_QUEUE_NAME,
        MessageProperties.PERSISTENT_TEXT_PLAIN,
        message.getBytes("UTF-8"));
    System.out.println(" [x] Sent '" + message + "'");

    channel.close();
    connection.close();
  }

  private static String getMessage(String[] strings) {
    if (strings.length < 1)
      return "Hello World!";
    return joinStrings(strings, " ");
  }

  private static String joinStrings(String[] strings, String delimiter) {
    int length = strings.length;
    if (length == 0) return "";
    StringBuilder words = new StringBuilder(strings[0]);
    for (int i = 1; i < length; i++) {
      words.append(delimiter).append(strings[i]);
    }
    return words.toString();
  }
}

接收端:

Work.java

import com.rabbitmq.client.*;

import java.io.IOException;

public class Worker {

  private static final String TASK_QUEUE_NAME = "task_queue";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    final Connection connection = factory.newConnection();
    final Channel channel = connection.createChannel();

    channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    channel.basicQos(1);

    final 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(" [x] Received '" + message + "'");
        try {
          doWork(message);
        } finally {
          System.out.println(" [x] Done");
          channel.basicAck(envelope.getDeliveryTag(), false);
        }
      }
    };
    channel.basicConsume(TASK_QUEUE_NAME, false, consumer);
  }

  private static void doWork(String task) {
    for (char ch : task.toCharArray()) {
      if (ch == '.') {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException _ignored) {
          Thread.currentThread().interrupt();
        }
      }
    }
  }
}

循環調度

使用任務隊列的好處是能夠很容易的并行工作。如果我們積壓了很多工作,我們僅僅通過增加更多的工作者就可以解決問題,使系統的伸縮性更加容易。

消息確認

執行一個任務需要花費幾秒鐘。你可能會擔心當一個工作者在執行任務時發生中斷。我們上面的代碼,一旦RabbItMQ交付了一個信息給消費者,會馬上從內存中移除這個信息。在這種情況下,如果殺死正在執行任務的某個工作者,我們會丟失它正在處理的信息。我們也會丟失已經轉發給這個工作者且它還未執行的消息。

boolean ack = false ; //打開應答機制  
channel.basicConsume(QUEUE_NAME, ack, consumer);  
//另外需要在每次處理完成一個消息后,手動發送一次應答。  
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);  

消息的持久性

我們已經學習了即使消費者被殺死,消息也不會被丟失。但是如果此時RabbitMQ服務被停止,我們的消息仍然會丟失。
當RabbitMQ退出或者異常退出,將會丟失所有的隊列和信息,除非你告訴它不要丟失。我們需要做兩件事來確保信息不會被丟失:我們需要給所有的隊列和消息設置持久化的標志。
第一, 我們需要確認RabbitMQ永遠不會丟失我們的隊列。為了這樣,我們需要聲明它為持久化的。

boolean durable = true;
channel.queueDeclare("task_queue", durable, false, false, null);

注:RabbitMQ不允許使用不同的參數重新定義一個隊列,所以已經存在的隊列,我們無法修改其屬性。
第二, 我們需要標識我們的信息為持久化的。通過設置MessageProperties(implements BasicProperties)值為PERSISTENT_TEXT_PLAIN。

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

現在你可以執行一個發送消息的程序,然后關閉服務,再重新啟動服務,運行消費者程序做下實驗。

公平的分配

或許會發現,目前的消息轉發機制(Round-robin)并非是我們想要的。例如,這樣一種情況,對于兩個消費者,有一系列的任務,奇數任務特別耗時,而偶數任務卻很輕松,這樣造成一個消費者一直繁忙,另一個消費者卻很快執行完任務后等待。
造成這樣的原因是因為RabbitMQ僅僅是當消息到達隊列進行轉發消息。并不在乎有多少任務消費者并未傳遞一個應答給RabbitMQ。僅僅盲目轉發所有的奇數給一個消費者,偶數給另一個消費者。
為了解決這樣的問題,我們可以使用basicQos方法,傳遞參數為prefetchCount = 1。這樣告訴RabbitMQ不要在同一時間給一個消費者超過一條消息。換句話說,只有在消費者空閑的時候會發送下一條信息。

int prefetchCount = 1;  
channel.basicQos(prefetchCount);  

相關文檔

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

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • 來源 RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。支持消息的持久化、事務、擁塞控...
    jiangmo閱讀 10,398評論 2 34
  • 1 RabbitMQ安裝部署 這里是ErLang環境的下載地址http://www.erlang.org/down...
    Bobby0322閱讀 2,267評論 0 11
  • 1. 歷史 RabbitMQ是一個由erlang開發的AMQP(Advanced Message Queue )的...
    高廣超閱讀 6,116評論 3 51
  • rabbitMQ是一款基于AMQP協議的消息中間件,它能夠在應用之間提供可靠的消息傳輸。在易用性,擴展性,高可用性...
    點融黑幫閱讀 3,039評論 3 41