ActiveMQ——基礎知識與模擬體驗

weixin_34320159發表於2018-09-04

最近在學習一個Java的開源專案,git地址:https://gitee.com/shuzheng/zheng
該開源專案開發環境需具備ActiveMQ,所以特地學習一下ActiveMQ。

一、ActiveMQ是什麼?

1)JMS概述
JMS即Java訊息服務(Java Message Service的簡稱),是Java EE 的標準/規範之一。這種規範(標準)指出:訊息的傳送應該是非同步的、非阻塞的。也就是說訊息的傳送者傳送完訊息後就直接返回了,不需要等待接收者返回後才能返回,傳送者和接收者可以說是互不影響。所以這種規範(標準)能夠減輕或消除系統瓶頸,實現系統之間去除耦合,提高系統的整體可伸縮性和靈活性。JMS只是Java EE中定義的一組標準API,它自身並不是一個訊息服務系統,它是訊息傳送服務的一個抽象,也就是說它定義了訊息傳送的介面而並沒有具體實現。

2)ActiveMQ概述
ActiveMQ就是JMS規範的具體實現;它是Apache下的一個專案,採用Java語言開發,是一款非常流行的開源訊息伺服器(訊息佇列中介軟體)

3)ActiveMQ與JMS關係


13566833-bd2f49d3d46a6fb9.png
二者關係.png

JMS只是定義了一組有關訊息傳送的規範和標準,並沒有真正實現,也就說JMS只是定義了一組介面;其具體實現由不同的訊息中介軟體廠商提供,比如Apache ActiveMQ就是JMS規範的具體實現,Apache ActiveMQ才是一個訊息服務系統,而JMS不是。

訊息佇列中介軟體是分散式系統中重要的元件,主要解決應用耦合,非同步訊息,流量削鋒等問題。實現高效能,高可用,可伸縮和最終一致性架構。是大型分散式系統不可缺少的中介軟體。目前在生產環境,使用較多的訊息佇列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。

二、應用場景——非同步處理,應用解耦,流量削鋒和訊息通訊

2.1非同步處理
場景說明:使用者註冊後,需要發註冊郵件和註冊簡訊。
傳統的做法:1.序列的方式; 2.並行方式。
1)序列方式:將註冊資訊寫入資料庫成功後,傳送註冊郵件,再傳送註冊簡訊。以上三個任務全部完成後,返回給客戶端。

2)並行方式:將註冊資訊寫入資料庫成功後,傳送註冊郵件的同時,傳送註冊簡訊。以上三個任務完成後,返回給客戶端。
與序列的差別是,並行的方式可以提高處理的時間。引入訊息佇列,就可以將不是必須的業務邏輯改為非同步處理。

2.2應用解耦
場景說明:使用者下單後,訂單系統需要通知庫存系統。傳統的做法是,訂單系統呼叫庫存系統的介面。這種模式存在如下缺點:
1) 假如庫存系統無法訪問,則訂單減庫存將失敗,從而導致訂單失敗;

2) 訂單系統與庫存系統耦合;

引入應用訊息佇列後的方案:
1)訂單系統:使用者下單後,訂單系統完成持久化處理,將訊息寫入訊息佇列,返回使用者訂單下單成功。

2)庫存系統:訂閱下單的訊息,採用拉/推的方式,獲取下單資訊,庫存系統根據下單資訊,進行庫存操作。

假如:在下單時庫存系統不能正常使用。也不影響正常下單,因為下單後,訂單系統寫入訊息佇列就不再關心其他的後續操作了。實現訂單系統與庫存系統的應用解耦。

2.3流量削鋒
流量削鋒也是訊息佇列中的常用場景,一般在秒殺或團搶活動中使用廣泛。
應用場景:秒殺活動,一般會因為流量過大,導致流量暴增,應用掛掉。為解決這個問題,一般需要在應用前端加入訊息佇列。
可以控制活動的人數;
可以緩解短時間內高流量壓垮應用;

使用者的請求,伺服器接收後,首先寫入訊息佇列。假如訊息佇列長度超過最大數量,則直接拋棄使用者請求或跳轉到錯誤頁面;
秒殺業務根據訊息佇列中的請求資訊,再做後續處理。

2.4日誌處理
日誌處理是指將訊息佇列用在日誌處理中,比如Kafka的應用,解決大量日誌傳輸的問題。
日誌採集客戶端,負責日誌資料採集,定時寫受寫入Kafka佇列;
Kafka訊息佇列,負責日誌資料的接收,儲存和轉發;
日誌處理應用:訂閱並消費kafka佇列中的日誌資料;

二.ActiveMQ的使用

1、JMS兩種訊息傳送模式
1)點對點( Point-to-Point):專門用於使用佇列Queue傳送訊息;基於佇列Queue的點對點訊息只能被一個消費者消費,如多個消費者都註冊到同一個訊息佇列上,當生產者傳送一條訊息後,而只有其中一個消費者會接收到該訊息,而不是所有消費者都能接收到該訊息。

2)釋出/訂閱(Publish/Subscribe):專門用於使用主題Topic傳送訊息。基於主題的釋出與訂閱訊息能被多個消費者消費,生產者傳送的訊息,所有訂閱了該topic的消費者都能接收到。

2、 JMS API可以分為3個主要部分:
1)公共API:可用於向一個佇列或主題傳送訊息或從其中接收訊息;
2)點對點API:專門用於使用佇列Queue傳送訊息;
3)釋出/訂閱API:專門用於使用主題Topic傳送訊息。

JMS公共API:
在JMS公共API內部,和傳送與接收訊息有關的JMS API介面主要是:ConnectionFactory/Connection/Session/Message/Destination/MessageProducer/MessageConsumer 。它們的關係是:一旦有了ConnectionFactory,就可以建立Connection,一旦有了Connection,就可以建立Session,而一旦有了Session,就可以建立Message、MessageProducer和MessageConsumer。

JMS點對點API:
點對點(p2p)訊息傳送模型API是指JMS API之內基於佇列(Queue)的介面:QueueConnectionFactory/QueueConnection/QueueSession/Message/Queue/QueueSender/QueueReceiver. 從介面的命名可以看出,大多數介面名稱僅僅是在公共API介面名稱之前新增Queue一詞。一般來說,使用點對點訊息傳送模型的應用程式將使用基於佇列的API,而不使用公共API 。

JMS釋出/訂閱API:
釋出/訂閱訊息傳送模型API是指JMS API之內基於主題(Topic)的介面:TopicConnectionFactory/TopicConnection/TopicSession/Message/Topic/TopicPublisher/TopicSubscriber. 由於基於主題(Topic)的JMS API類似於基於佇列(Queue)的API,因此在大多數情況下,Queue這個詞會由Topic取代。

13566833-75a38d8a62b4b68c.png
ActiveMQ點對點傳送與接收訊息示例.png

3、ActiveMQ的下載與安裝
1)直接去官網(http://activemq.apache.org/)下載最新版本即可,由於這是免安裝的,只需要解壓就行了。安裝完之後進入bin目錄,雙擊 activemq.bat檔案(linux下在bin目錄下執行 activemq start

2)訪問控制檯(檢驗是否成功)
在瀏覽器輸入:http://ip:8161/admin/ 會彈出登陸框,賬號和密碼都是預設admin。

3)修改埠號
61616為對外服務埠號
8161為控制器埠號
當埠號衝突時,可以修改這兩個埠號。cd conf ,修改activemq.xml 修改裡面的61616埠。修改jetty.xml,修改裡面的8161埠。

4)刪除不活動佇列
一般情況下,ActiveMQ的queue或者topic在不使用之後,可以通過web控制檯來刪除掉。當然,也可以通過配置,使得broker可以自動探測到無用的佇列(一定時間內為空的佇列)並刪除掉,回收響應資源。
activemq.xml

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.base}/data" destroyApplicationContextOnStop="true" schedulePeriodForDestinationPurge="10000">
    <destinationPolicy>
        <policyMap>
          <policyEntries>
            <policyEntry topic=">" gcInactiveDestinations="true" inactiveTimoutBeforeGC="100000" memoryLimit="1mb">
              <pendingSubscriberPolicy>
                <vmCursor />
              </pendingSubscriberPolicy>
            </policyEntry>
            <policyEntry queue=">" gcInactiveDestinations="true" inactiveTimoutBeforeGC="100000" memoryLimit="1mb">
            </policyEntry>
          </policyEntries>
        </policyMap>
    </destinationPolicy>
</broker>

schedulePeriodForDestinationPurge:10000 每十秒檢查一次,預設為0,此功能關閉
gcInactiveDestinations: true 刪除掉不活動佇列,預設為false
inactiveTimoutBeforeGC:30000 不活動30秒後刪除,預設為60秒
PS:對於topic的不活動佇列只是,10秒中之類沒有消費者進行註冊監聽,如果一個使用者事先註冊了這個監聽,但是他一直沒有登入,那麼這算活動佇列。而queue只要有訊息沒有出佇列就表示活動佇列。

附:訊息中介軟體的用途和優點
1)將資料從一個應用程式傳送到另一個應用程式,或者從軟體的一個模組傳送到另外一個模組;
2)負責建立網路通訊的通道,進行資料的可靠傳送;
3)保證資料不重發,不丟失;
4)能夠實現跨平臺操作,能夠為不同作業系統上的軟體整合技工資料傳送服務

三、模擬HelloWord體驗ActiveMQ

下載MQ以後,要將---bin.zip解壓縮后里面的activemq-all-5.11.1.jar包加入到classpath下面,這個包包含了所有jms介面api的實現,專案結構圖:

13566833-8f92a8b380e222a6.png
MQ

啟動MQ,啟動成功,開啟該連結:http://localhost:8161/admin/
賬號和密碼都是admin,啟動成功後,圖如下:
13566833-27ef284b3513ebbe.png
image.png

生產者和消費者程式碼如下:

package com.activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
 * 生產者(訊息傳送者)
 * @author admin
 *
 */
public class JMSProducer {

    // 賬號(預設連線使用者名稱)
    private static final String user = ActiveMQConnection.DEFAULT_USER;
    // 密碼
    private static final String pwd = ActiveMQConnection.DEFAULT_PASSWORD;
    // 地址
    private static final String url = ActiveMQConnection.DEFAULT_BROKER_URL;

    private static final int sendnum = 10;

    public static void main(String[] args) {
        // 連線工廠
        ConnectionFactory connectionFactory;
        // 連線
        Connection connection = null;
        // 會話
        Session session;
        // 訊息目的
        Destination destination;
        // 訊息生產者
        MessageProducer messageProducer;
        // 例項化工廠
        connectionFactory = new ActiveMQConnectionFactory(JMSProducer.user, JMSProducer.pwd, JMSProducer.url);
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
            destination = session.createQueue("HelloWord");
            messageProducer = session.createProducer(destination);
            sendMessage(session, messageProducer);
            session.commit();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 傳送訊息
     * @param session
     * @param messageProducer  訊息生產者
     * @throws Exception
     */
    public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception {
        for (int i = 0; i < 10; i++) {
            TextMessage message = session.createTextMessage("ActiveMQ" + i);
            System.out.println("傳送訊息" + i);
            messageProducer.send(message);
        }
    }
}
package com.activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
 * 消費者(訊息接收者)
 * @author admin
 *
 */
public class JMSConsumer {
    private static final String USERNAME = ActiveMQConnection.DEFAULT_USER;// 預設連線使用者名稱
    private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;// 預設連線密碼
    private static final String BROKEURL = ActiveMQConnection.DEFAULT_BROKER_URL;// 預設連線地址

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;// 連線工廠
        Connection connection = null;// 連線

        Session session;// 會話 接受或者傳送訊息的執行緒
        Destination destination;// 訊息的目的地

        MessageConsumer messageConsumer;// 訊息的消費者

        // 例項化連線工廠
        connectionFactory = new ActiveMQConnectionFactory(JMSConsumer.USERNAME, JMSConsumer.PASSWORD,
                JMSConsumer.BROKEURL);

        try {
            // 通過連線工廠獲取連線
            connection = connectionFactory.createConnection();
            // 啟動連線
            connection.start();
            // 建立session
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            // 建立一個連線HelloWorld的訊息佇列
            destination = session.createQueue("HelloWord");
            // 建立訊息消費者
            messageConsumer = session.createConsumer(destination);

            while (true) {
                TextMessage textMessage = (TextMessage) messageConsumer.receive(100000);
                if (textMessage != null) {
                    System.out.println("收到的訊息:" + textMessage.getText());
                } else {
                    break;
                }
            }

        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

啟動生產者,輸出如下:


13566833-bc7ff3969907e331.png
image.png

然後看一下ActiveMQ伺服器,Queues內容如下:


13566833-afc296a1def1d5ab.png
MQ伺服器說明

我們可以看到建立了一個名稱為HelloWorld的訊息佇列,佇列中有10條訊息未被消費,我們也可以通過Browse檢視是哪些訊息:
13566833-968f56fe9e66d8da.png
image.png

這些佇列中的訊息,被刪除,消費者則無法消費,接下來,執行一下消費者:


13566833-370f7a4f6ac1c127.png
消費者輸出

現在我們再看一下ActiveMQ伺服器,Queues內容如下:
13566833-e2132794a0dd9672.jpg
1536301523(1).jpg

我們可以看到HelloWorld的訊息佇列發生變化,多一個訊息者,佇列中的10條訊息被消費了,點選Browse檢視,已經為空了。
點選Active Consumers,我們可以看到這個消費者的詳細資訊。