ActiveMQ 是什麼?
“這個簡單,ActiveMQ 是一個 MOM,具體來說是一個實現了 JMS 規範的系統間遠程通信的消息代理。它……”
什麼是 MOM?
“好。MOM 就是面向消息中間件(Message-oriented middleware),是用於以分佈式應用或系統中的異步、鬆耦合、可靠、可擴展和安全通信的一類軟件。MOM 的總體思想是它作爲消息發送器和消息接收器之間的消息中介,這種中介提供了一個全新水平的鬆耦合。”
什麼是JMS呢?
成小胖是個追求極致的人,爲了解釋得更通俗易懂,索性搬來一塊白板邊畫邊說。
“JMS 叫做 Java 消息服務(Java Message Service),是 Java 平臺上有關面向 MOM 的技術規範,旨在通過提供標準的產生、發送、接收和處理消息的 API 簡化企業應用的開發,類似於 JDBC 和關係型數據庫通信方式的抽象。”
下面的這些概念你也需要特別理解下:
Provider:純 Java 語言編寫的 JMS 接口實現(比如 ActiveMQ 就是)
Domains:消息傳遞方式,包括點對點(P2P)、發佈/訂閱(Pub/Sub)兩種
Connection factory:客戶端使用連接工廠來創建與 JMS provider 的連接
Destination:消息被尋址、發送以及接收的對象
這其中 P2P 和 Pub/Sub 的區別:
P2P (點對點)消息域使用 queue 作爲 Destination,消息可以被同步或異步的發送和接收,每個消息只會給一個 Consumer 傳送一次。
Consumer 可以使用 MessageConsumer.receive() 同步地接收消息,也可以通過使用MessageConsumer.setMessageListener() 註冊一個 MessageListener 實現異步接收。
多個 Consumer 可以註冊到同一個 queue 上,但一個消息只能被一個 Consumer 所接收,然後由該 Consumer 來確認消息。並且在這種情況下,Provider 對所有註冊的 Consumer 以輪詢的方式發送消息。
Pub/Sub(發佈/訂閱,Publish/Subscribe)消息域使用 topic 作爲 Destination,發佈者向 topic 發送消息,訂閱者註冊接收來自 topic 的消息。發送到 topic 的任何消息都將自動傳遞給所有訂閱者。接收方式(同步和異步)與 P2P 域相同。
除非顯式指定,否則 topic 不會爲訂閱者保留消息。當然,這可以通過持久化(Durable)訂閱來實現消息的保存。這種情況下,當訂閱者與 Provider 斷開時,Provider 會爲它存儲消息。當持久化訂閱者重新連接時,將會受到所有的斷連期間未消費的消息。
創建ActiveMq的通用步驟:
獲取連接工廠
使用連接工廠創建連接
啓動連接
從連接創建會話
獲取 Destination
創建 Producer,或
創建 Producer
創建 message
創建 Consumer,或發送或接收message發送或接收 message
創建 Consumer
註冊消息監聽器(可選)
發送或接收 message
關閉資源(connection, session, producer, consumer 等)
示例:
public class JMSDemo {
ConnectionFactory connectionFactory;
Connection connection;
Session session;
Destination destination;
MessageProducer producer;
MessageConsumer consumer;
Message message;
boolean useTransaction = false;
try {
Context ctx = new InitialContext();
connectionFactory = (ConnectionFactory) ctx.lookup("ConnectionFactoryName");
//使用ActiveMQ時:connectionFactory = new ActiveMQConnectionFactory(user, password, getOptimizeBrokerUrl(broker));
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(useTransaction, Session.AUTO_ACKNOWLEDGE);
destination = session.createQueue("TEST.QUEUE");
//生產者發送消息
producer = session.createProducer(destination);
message = session.createTextMessage("this is a test");
//消費者同步接收
consumer = session.createConsumer(destination);
message = (TextMessage) consumer.receive(1000);
System.out.println("Received message: " + message);
//消費者異步接收
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if (message != null) {
doMessageEvent(message);
}
}
});
} catch (JMSException e) {
...
} finally {
producer.close();
session.close();
connection.close();
}
}
ActiveMQ 的存儲
ActiveMQ 在 queue 中存儲 Message 時,採用先進先出順序(FIFO)存儲。同一時間一個消息被分派給單個消費者,且只有當 Message 被消費並確認時,它才能從存儲中刪除。
對於持久化訂閱者來說,每個消費者獲得 Message 的副本。爲了節省存儲空間,Provider 僅存儲消息的一個副本。持久化訂閱者維護了指向下一個 Message 的指針,並將其副本分派給消費者。以這種方式實現消息存儲,因爲每個持久化訂閱者可能以不同的速率消費 Message,或者它們可能不是全部同時運行。此外,因每個 Message 可能存在多個消費者,所以在它被成功地傳遞給所有持久化訂閱者之前,不能從存儲中刪除。
對上面這段知識我們用表格展示如下:
ActiveMQ 常用的存儲方式
1.KahaDB
ActiveMQ 5.3 版本起的默認存儲方式。KahaDB存儲是一個基於文件的快速存儲消息,設計目標是易於使用且儘可能快。它使用基於文件的消息數據庫意味着沒有第三方數據庫的先決條件。
要啓用 KahaDB 存儲,需要在 activemq.xml 中進行以下配置:
<broker brokerName="broker" persistent="true" useShutdownHook="false">
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb" journalMaxFileLength="16mb"/>
</persistenceAdapter>
</broker>
2.AMQ
與 KahaDB 存儲一樣,AMQ存儲使用戶能夠快速啓動和運行,因爲它不依賴於第三方數據庫。AMQ 消息存儲庫是可靠持久性和高性能索引的事務日誌組合,當消息吞吐量是應用程序的主要需求時,該存儲是最佳選擇。但因爲它爲每個索引使用兩個分開的文件,並且每個 Destination 都有一個索引,所以當你打算在代理中使用數千個隊列的時候,不應該使用它。
<persistenceAdapter>
<amqPersistenceAdapter
directory="${activemq.data}/kahadb"
syncOnWrite="true"
indexPageSize="16kb"
indexMaxBinSize="100"
maxFileLength="10mb" />
</persistenceAdapter>
3.JDBC
選擇關係型數據庫,通常的原因是企業已經具備了管理關係型數據的專長,但是它在性能上絕對不優於上述消息存儲實現。事實是,許多企業使用關係數據庫作爲存儲,是因爲他們更願意充分利用這些數據庫資源。
<beans>
<broker brokerName="test-broker" persistent="true" xmlns="http://activemq.apache.org/schema/core">
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
</broker>
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/>
<property name="username" value="activemq"/>
<property name="password" value="activemq"/>
<property name="maxActive" value="200"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
</beans>
4.內存存儲
內存消息存儲器將所有持久消息保存在內存中。在僅存儲有限數量 Message 的情況下,內存消息存儲會很有用,因爲 Message 通常會被快速消耗。在 activema.xml 中將 broker 元素上的 persistent 屬性設置爲 false 即可。
<broker brokerName="test-broker" persistent="false" xmlns="http://activemq.apache.org/schema/core">
<transportConnectors>
<transportConnector uri="tcp://localhost:61635"/>
</transportConnectors>
</broker>
ActiveMQ 的部署模式
1.單例模式
這個就不囉嗦了,略過。
2.無共享主從模式
這是最簡單的 Provider 高可用性的方案,主從節點分別存儲 Message。從節點需要配置爲連接到主節點,並且需要特殊配置其狀態。
所有消息命令(消息,確認,訂閱,事務等)都從主節點複製到從節點,這種複製發生在主節點對其接收的任何命令生效之前。並且,當主節點收到持久消息,會等待從節點完成消息的處理(通常是持久化到存儲),然後再自己完成消息的處理(如持久化到存儲)後,再返回對 Producer 的回執。
從節點不啓動任何傳輸,也不能接受任何客戶端或網絡連接,除非主節點失效。當主節點失效後,從節點自動成爲主節點,並且開啓傳輸並接受連接。這是,使用 failover 傳輸的客戶端就會連接到該新主節點。
Broker 連接配置如下:
failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false
但是,這種部署模式有一些限制,
主節點只會在從節點連接到主節點時複製其活動狀態,因此當從節點沒有連接上主節點之前,任何主節點處理的 Message 或者消息確認都會在主節點失效後丟失。不過你可以通過在主節點設置 waitForSlave 來避免,這樣就強制主節點在沒有任何一個從節點連接上的情況下接受連接。
就是主節點只能有一個從節點,並且從節點不允許再有其他從節點。
把正在運行的單例配置成無共享主從,或者配置新的從節點時,你都要停止當前服務,修改配置後再重啓才能生效。
在可以接受一些故障停機時間的情況下,可以使用該模式。
從節點配置:
<services>
<masterConnector remoteURI="tcp://remotehost:62001" userName="Rob" password="Davies"/>
</services>
此外,可以配置 shutdownOnMasterFailure 項,表示主節點失效後安全關閉,保證沒有消息丟失,允許管理員維護一個新的從節點。
3.共享存儲主從模式
允許多個代理共享存儲,但任意時刻只有一個是活動的。這種情況下,當主節點失效時,無需人工干預來維護應用的完整性。另外一個好處就是沒有從節點數的限制。
有兩種細分模式:
(1)基於數據庫
它會獲取一個表上的排它鎖,以確保沒有其他 ActiveMQ 代理可以同時訪問數據庫。其他未獲得鎖的代理則處於輪詢狀態,就會被當做是從節點,不會開啓傳輸也不會接受連接。
(2)基於文件系統
需要獲取分佈式共享文件鎖,linux 系統下推薦用 GFS2。
ActiveMQ 的網絡連接
1.代理網絡
支持將 ActiveMQ 消息代理鏈接到不同拓撲,這就是被人們熟知的代理網絡。
ActiveMQ 網絡使用存儲和轉發的概念,其中消息總是存儲在本地代理中,然後通過網絡轉發到另一個代理。
當連接建立後,遠程代理將把包含其所有持久和活動消費者目的地的信息傳遞給本地代理,本地代理根據信息決定遠程代理感興趣的 Message 並將它發送給遠程代理。
如果希望網絡是雙向的,您可以使用網絡連接器將遠程代理配置爲指向本地代理,或將網絡連接器配置爲雙工,以便雙向發送消息。
<networkConnectors>
<networkConnector uri="static://(tcp://backoffice:61617)"
name="bridge"
duplex="true"
conduitSubscriptions="true"
decreaseNetworkConsumerPriority="false">
</networkConnector>
</networkConnectors>
注意,配置的順序很重要:
1.網絡連接——需要在消息存儲前建立好連接,對應 networkConnectors 元素
2.消息存儲——需要在傳輸前配置好,對應 persistenceAdapter 元素
3.消息傳輸——最後配置,對應 transportConnectors 元素
2.網絡發現
(1)動態發現
使用多播來支持網絡動態發現。配置如下:
<networkConnectors>
<networkConnector uri="multicast://default"/>
</networkConnectors>
其中,multicast:// 中的默認名稱表示該代理所屬的組。因此使用此方式時,強烈推薦你使用一個獨特的組名,避免你的代理連接到其他不相關代理。
(2)靜態發現
靜態發現接受代理 URI 列表,並將嘗試按列表中確定的順序連接到遠程代理。
<networkConnectors>
<networkConnector uri="static:(tcp://remote-master:61617,tcp://remote-slave:61617)"/>
</networkConnectors>
相關配置如下:
initialReconnectDelay:默認值1000,表示嘗試連接前的時延。
maxReconnectDelay:默認值30000,表示連接失敗後到重新建立連接之間的時延,僅在 useExponentialBackOff 啓用時生效。
useExponentialBackOff:默認值 true,如果啓用,表示每次失敗後增加重建連接的時延。
backOffMultiplier:默認值2,表示啓用 useExponentialBackOff 後每次的時延增量需要注意的是,網絡連接將始終嘗試建立到遠程代理的連接。
需要注意的是,網絡連接將始終嘗試建立到遠程代理的連接。
(3)多連接場景
當網絡負載高時,使用多連接很有意義。但是你需要確保不會重複傳遞消息,這可以通過過濾器來實現。
<networkConnectors>
<networkConnector uri="static://(tcp://remotehost:61617)"
name="queues_only"
duplex="true"
<excludedDestinations>
<topic physicalName=">"/>
</excludedDestinations>
</networkConnector>
<networkConnector uri="static://(tcp://remotehost:61617)"
name="topics_only"
duplex="true"
<excludedDestinations>
<queue physicalName=">"/>
</excludedDestinations>
</networkConnector>
</networkConnectors>