1. ActiveMQ概述與HelloWorld

1. JMS簡介

  1. JMS全稱:Java Message Service ,中文:Java 消息服務,是 Java 的一套 API 標準
  2. 最初的目的是爲了使應用程序能夠訪問現有的 MOM系
  3. MOM:Message Oriented Middleware,即消息中間件,它可以利用高效可靠的消息傳遞機 制進行平臺無關的數據交流,並基於數據通信來進行分佈式系統的集成
  4. 常見 MOM 系統包括 Apache 的 ActiveMQ、 阿里巴巴的 RocketMQ、IBM 的 MQSeries、Microsoft 的 MSMQ、BEA 的 RabbitMQ 等。並非全部的 MOM 系統都遵循 JMS 規範,也就是並非全提供了JMS實現,提供了JMS 實現的 MOM,又被稱爲 JMSProvider
  5. JMS與MOM的關係類似JDBC和數據庫之間的關係

2 消息中間件的應用場景

  1. 異步通信:消息隊列提供了異步處理機制,允許用戶把一個消息放入隊列,但並不立即處理它
  2. 過載保護:不將請求直接發送給服務端,而是由服務端自己來取,這樣防止大量請求同時到達服務端,使整個系統崩潰
  3. 解耦:A和B直接相連時,一旦B死掉,那麼A的功能也都不好用了,採用消息中間件解耦,可以保證B死掉,A也能正常發送給隊列,當B復活後,又可以繼續完成之前隊列中的任務
  4. 消息通訊:客戶端A,客戶端B,客戶端N訂閱同一主題,進行消息發佈和接收。實現類似聊天室效果
  5. 順序保證:在大多使用場景下,數據處理的順序都很重要。大部分消息隊列本來就是排序的,並且能保證數據會按照特定的順序來處理。
  6. 數據流處理:分佈式系統產生的海量數據流,如:業務日誌、監控數據、用戶行爲等,針對這些數據流進行實時或批量採集彙總,然後進行大數據分析是當前互聯網的必備技術,通過消息隊列完成此類數據收集是最好的選擇
  7. 擴展性:消息中間件可以很容易橫向擴容

3 常用消息隊列比較

特性MQ ActiveMQ RabbitMQ RocketMQ Kafka
生產者消費者模式 支持 支持 支持 支持
發佈訂閱模式 支持 支持 支持 支持
請求迴應模式 支持 支持 不支持 不支持
Api完備性
多語言支持 支持 支持 java 支持
單機吞吐量 萬級 萬級 萬級 十萬級
消息延遲 微秒級 毫秒級 毫秒級
可用性 高(主從) 高(主從) 非常高(分佈式) 非常高(分佈式)
消息丟失 理論上不會丟失 理論上不會丟失
文檔的完備性
提供快速入門
社區活躍度
商業支持 商業雲 商業雲

4 JMS中的角色

  1. Broker:消息服務器,相當於server,提供消息核心服務
  2. Provider:消息生產者,是由會話創建的一個對象,用於把消息發送到一個目的地
  3. Consumer:消息消費者,是由會話創建的一個對象,它用於接收發送到目的地的消息
    1. 消費消息的兩種方式:
      1. 同步消費:調用消費者的receive方法,從目的地中顯式提取消息。receive方法可以一直阻塞到消息到達
      2. 異步消費:客戶端可以爲消費者註冊一個消息監聽器,以定義在消息到達時所採取的動作
  4. p2p:基於點對點的消息模型
    1. 消息生產者生產消息發送到 queue 中,然後消息消費者從 queue 中取出並且消費消息
    2. 消息被消費以後,queue 中不再有存儲,所以消息消費者不可能消費到已經被消費的消息
    3. Queue 支持存在多個消費者,但是對一個消息而言,只會有一個消費者可以消費、其它 的則不能消費此消息了
    4. 當消費者不存在時,消息會一直保存,直到有消費消費
      在這裏插入圖片描述
  5. pub/sub:基於訂閱/發佈的消息模型
    1. 消息生產者(發佈)將消息發佈到 topic 中,同時有多個消息消費者(訂閱)消費該消
    2. 和點對點方式不同,發佈到 topic 的消息會被所有訂閱者消費
    3. 當生產者發佈消息,不管是否有消費者。都不會保存消息 一定要先有消息的消費者,後有消息的生產者
      在這裏插入圖片描述
  6. PTP 和 PUB/SUB 簡單對比
1 Topic Queue
Publish Subscribe messaging 發佈 訂閱消息 Point-to-Point 點對點
有無狀態 topic 數據默認不落地,是無狀態的,也就是發消息時,如果接收的人不在線,那麼該消息他就收不到了。 Queue 數據默認會在 mq 服 務器上以文件形式保存,比如 Active MQ 一 般 保 存 在 $AMQ_HOME\data\kahadb 下 面。也可以配置成 DB 存儲。
完整性保障 並不保證 publisher 發佈的每條數 據,Subscriber 都能接受到。 Queue 保證每條數據都能 被 receiver 接收。消息不超時。
消息是否會丟失 一般來說 publisher 發佈消息到某 一個 topic 時,只有正在監聽該 topic 地址的 sub 能夠接收到消息;如果沒 有 sub 在監聽,該 topic 就丟失了。 Sender 發 送 消 息 到 目 標 Queue, receiver 可以異步接收這 個 Queue 上的消息。Queue 上的 消息如果暫時沒有 receiver 來 取,也不會丟失。前提是消息不 超時。
消息發佈接 收策略 一對多的消息發佈接收策略,監 聽同一個topic地址的多個sub都能收 到 publisher 發送的消息。Sub 接收完 通知 mq 服務器 一對一的消息發佈接收策 略,一個 sender 發送的消息,只 能有一個 receiver 接收。 receiver 接收完後,通知 mq 服務器已接 收,mq 服務器對 queue 裏的消 息採取刪除或其他操作。
  1. Queue:隊列存儲,常用與點對點消息模型 ,默認只能由唯一的一個消費者處理。一旦處理消息刪除
  2. Topic:主題存儲,用於訂閱/發佈消息模型主題中的消息,會發送給所有的消費者同時處理。只有在消息可以重複處 理的業務場景中可使用。Queue/Topic都是 Destination 的子接口
  3. ConnectionFactory :連接工廠,jms中用它創建連接。連接工廠是客戶用來創建連接的對象,例如ActiveMQ提供的ActiveMQConnectionFactory
  4. Connection:JMS Connection封裝了客戶與JMS提供者之間的一個虛擬的連接
  5. Destination:消息的目的地。在點對點消息傳遞域中,目的地被成爲隊列(queue);在發佈/訂閱消息傳遞域中,目的地被成爲主題(topic)
  6. Session:JMS Session是生產和消費消息的一個單線程上下文。會話用於創建消息生產者(producer)、消息消費者(consumer)和消息(message)等。會話提供了一個事務性的上下文,在這個上下文中,一組發送和接收被組合到了一個原子操作中

5 JMS的消息格式與類型

5.1 格式

  1. 消息頭
  2. 消息屬性
  3. 消息體

5.2 類型

5.2.1 TextMessage:文本消息
5.2.2 MapMessage:k-v
  1. 發送端
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("name","lucy");
mapMessage.setBoolean("yihun",false);
mapMessage.setInt("age", 17);
producer.send(mapMessage);
  1. 接收端
Message message = consumer.receive();
MapMessage mes = (MapMessage) message;
System.out.println(mes);
System.out.println(mes.getString("name"));
5.2.3 BytesMessage:字節流,一般用於傳輸小文件、圖片
  1. 發送端
BytesMessage bytesMessage = session.createBytesMessage();
bytesMessage.writeBytes("str".getBytes());
bytesMessage.writeUTF("哈哈");
  1. 接收端
//方法一
if(message instanceof BytesMessage) {
	BytesMessage bm = (BytesMessage)message;
	 byte[] b = new byte[1024];
           int len = -1;
           while ((len = bm.readBytes(b)) != -1) {
               System.out.println(new String(b, 0, len));
           }
}
//方法二:使用ActiveMQ給提供的便捷方法,但要注意讀取和寫入的順序,寫入是什麼順序,讀取時就是什麼順序
bm.readBoolean();
bm.readUTF();
5.2.4 StreamMessage:java原始的數據流
5.2.5 ObjectMessage:序列化的java對象
  1. 發送端
//必須先將要序列化的對象,添加到信任列表,否則反序列化時,會拋出如下異常
//Exception in thread "main" javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.mashibing.mq.Girl! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
connectionFactory.setTrustedPackages(
				new ArrayList<String>(Arrays.asList(new String[] { Girl.class.getPackage().getName() })));
Girl girl = new Girl("qiqi",25,398.0);
Message message = session.createObjectMessage(girl);
  1. 接收端
if(message instanceof ActiveMQObjectMessage) {
	Girl girl = (Girl)((ActiveMQObjectMessage)message).getObject();
	System.out.println(girl);
	System.out.println(girl.getName());
}

6 消息的特性

6.1 持久性
  1. 持久化消息後,即使ActiveMQ宕機,消息也不會消失,消息被消費者消費掉後,數據庫中內容纔會消失
  2. MQ接受消息,還需要向數據庫中寫,會影響效率,所以不建議使用大數據庫,而是推薦使用kahadb這種小型數據庫,速度非常快
  3. 生產環境幾乎不可能用mysql或oracle進行消息持久化存儲,此處用oracle是爲了方便觀察消息在數據庫中的存儲形式
  4. JMS中的持久化
//MessageProducer
//DeliveryMode.PERSISTENT:持久化消息
//DeliveryMode.NON_PERSISTENT:不持久化消息
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
6.1.1 KahaDB存儲
  1. KahaDB是默認的持久化策略,所有消息順序添加到一個日誌文件中,同時另外有一個索引文件記錄指向這些日誌的存儲地址,還有一個事務日誌用於消息回覆操作。是一個專門針對消息持久化的解決方案,它對典型的消息使用模式進行了優化
  2. 在data/kahadb這個目錄下,會生成四個文件,來完成消息持久化
    1. db.data:消息的索引文件,本質上是B-Tree(B樹),使用B-Tree作爲索引指向db-*.log裏面存儲的消息
    2. db.redo:用來進行消息恢復
    3. db-.log:存儲消息內容。新的數據以APPEND的方式追加到日誌文件末尾。屬於順序寫入,因此消息存儲是比較 快的。默認是32M,達到閥值會自動遞增
    4. lock:鎖,寫入當前獲得kahadb讀寫權限的broker ,用於在集羣環境下的競爭處理
  3. 配置
<persistenceAdapter> 
	<!--directory:保存數據的目錄;journalMaxFileLength:保存消息的文件大小,是每個數據文件大小,超過後滾動 --> 
	<kahaDBdirectory="${activemq.data}/kahadb"journalMaxFileLength="16mb"/> </persistenceAdapter>

  1. 特性
    1. 日誌形式存儲消息
    2. 消息索引以 B-Tree 結構存儲,可以快速更新
    3. 完全支持 JMS 事務
    4. 支持多種恢復機制
6.1.2 JDBC存儲
  1. activemq.xml
<!--設置數據源,名爲oracle-ds,使用org.apache.commons.dbcp.BasicDataSource來管理連接池-->
<bean id="oracle-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
	<property name="driverClassName" value="oracle.jdbc.OracleDriver"/> 
	<property name="url" value="jdbc:oracle:thin:@192.168.15.110:1521:fcrhost"/> 
	<property name="username" value="c50hst"/>
	<property name="password" value="c50hst"/>
	<property name="maxActive" value="200"/>
	<property name="poolPreparedStatements" value="true"/> 
</bean>
...
<persistenceAdapter>
	<!--屏蔽之前使用的kahadb進行持久化,並如果數據庫中沒有表,自動建立表-->
    <!--<kahaDB directory="${activemq.data}/kahadb"/>-->
	<jdbcPersistenceAdapter dataSource="#oracle-ds" createTablesOnStartup="true" /> 
</persistenceAdapter>
  1. 數據庫連接池和數據庫jdbc連接,需要依賴如下jar包
    1. commons-dbcp-1.4.jar
    2. commons-pool-1.6.jar
    3. ojdbc6.jar
  2. 使用JDBC持久化方式,數據庫默認會創建3個表
  3. activemq_msgs:用於存儲消息,Queue和Topic都存儲在這個表中
id:自增的數據庫主鍵 
container:消息的destination 
msgid_prod:消息發送者客戶端的主鍵 
msg_seq:是發送消息的順序,msgid_prod+msg_seq可以組成jms的messageid 
expiration:消息的過期時間,存儲的是從1970-01-01到現在的毫秒數 
msg:消息本體的java序列化對象的二進制數據 
priority:優先級,從0-9,數值越大優先級越高 
xid:用於存儲訂閱關係。如果是持久化topic,訂閱者和服務器的訂閱關係在這個表保存。
  1. activemq_acks:用於存儲訂閱關係。如果是持久化Topic,訂閱者和服務器的訂閱關係在這個表保存
container:消息的destination 
sub_dest:如果是使用static集羣,這個字段會有集羣其他系統的信息 
client_id:每個訂閱者都必須有一個唯一的客戶端id用以區分 
sub_name:訂閱者名稱 
selector:選擇器,可以選擇只消費滿足條件的消息。條件可以用自定義屬性實現,可支持多屬性and和or操作 
last_acked_id:記錄消費過的消息的id。
  1. activemq_lock:在集羣環境中才有用,只有一個Broker可以獲得消息,稱爲Master Broker,其他的只能作爲備份等待Master Broker不可用,纔可能成爲下一個Master Broker。這個表用於記錄哪個Broker是當前的Master Broker
6.1.3 JDBC Message store with ActiveMQ Journal
  1. 消息中間件收到消息後,向內存中存儲消息同時,往文件裏寫,然後就返回ack
  2. 此時有consumer來消費,就會從文件中刪除
  3. 如果指定時間內未被消費,會通過jdbc寫入數據庫,而且是批量地寫和刪(不是單條),減少寫入數據庫中的數據量
6.2 本地事務
  1. 在一個JMS客戶端,可以使用本地事務來組合消息的發送和接收
  2. 可以在創建session時,指定開啓事務
  3. 當事務設置爲true,應答模式默認只能是Session.SESSION_TRANSACTED,即使設置爲其他的,也不會生效,當設置爲其他值時,手動調用message.acknowledge(),會和producer.send類似,只要沒最終通過session.commit提交事務,就無法通知ActiveMQ該消息被確認
  4. 開啓事務可以避免頻繁發送消息造成的網絡連接問題,也可以在出現問題時,及時回滾
//Connection
//true表示開啓事務,如果事務開啓,只要不調用session.commit方法, 即使調用了producer.send或message.acknowledge(),消息也不會被髮送到消息隊列、消息也無法被確認
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
6.3 可靠性
  1. 消息的成功消費通常包含三個階段:客戶接收消息、客戶處理消息和消息被確認
  2. 消息隊列收到確認包後,纔會將該消息從消息隊列中移除(如果持久化消息,會從數據庫中移除)
  3. 在一個session中consumer接收到的消息,在另一個consumer中無法重複接收該消息,如果第一個客戶端始終沒確認消息,且最後該客戶端對應session斷開,那麼消息會重新投遞給第二個客戶端
  4. 在事務性會話中,當一個事務被提交的時候,確認自動發生
  5. 在非事務性會話中,消息何時被確認取決於創建會話時的簽收模式(acknowledgement mode),acknowledgement其實就是ack,也就是確認包
//1. Session.AUTO_ACKNOWLEDGE
	//1. 消費者調用consumer.receive方法成功後
	//2. 或消費者中,MessageListener.onMessage方法成功返回後,會話自動確認客戶收到的消息
//2. Session.CLIENT_ACKNOWLEDGE
	//1. 消費者調用message.acknowledge()方法成功後,消息被確認
	//2. 確認是在會話層上進行:確認一個被消費的消息將自動確認所有已被會話消費的消息。例如,如果一個消息消費者消費了10個消息,然後確認第5個消息,那麼所有10個消息都被確認
//3. Session.DUPS_ACKNOWLEDGE:不需要確認消息
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
6.3 優先級
  1. 可以設置消息優先級,這樣就可以不按發送消息的順序去消費
  2. 優先級分10個級別,從0(最低)到9(最高)。如果不指定優先級,默認級別是4
  3. 開啓優先級:activemq.xml
<!--爲queue1開啓消息優先級,xml中如果不設置不會生效-->
<policyEntry queue="queue1" prioritizedMessages="true" />
//方案一
producer.setPriority(9);
//方案二
producer.send(textMessage,DeliveryMode.PERSISTENT,9,1000 * 100);

7 HelloWorld

  1. 下載ActiveMQ
http://activemq.apache.org/
  1. 啓動ActiveMQ
//實際上調用的就是bin\win64\wrapper.exe,只不過直接調wrapper.exe的話,如果有異常,會直接閃退,而不會打印錯誤信息
bin/win64/activemq.bat
  1. 進入管理界面
//ActiveMQ是在jetty(web容器)上運行的,會在本機開一個8161端口,提供網頁控制檯
http://localhost:8161/
  1. 修改訪問端口
conf/jetty.xml
  1. 創建maven項目
    1. 會自動幫你下載、並引入需要的activemq-all-5.15.12.jar以及源碼,如果創建正常項目,需要將E:\Program Files (x86)\apache-activemq-5.15.12\下的該jar包引入
    2. File–new–Project–Maven Project–選中create a simple project–填寫Goup Id(com.mashibing.mq)與Artifact ID(activemq02)
    3. 修改pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mashibing.mq</groupId>
	<artifactId>activemq02</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
	<dependencies>
		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>activemq-all</artifactId>
			<version>5.15.12</version>
		</dependency>
	</dependencies>
</project>
  1. Sender
package com.mashibing.mq;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

public class Sender {
	public static void main(String[] args) throws Exception{
		// 1.獲取連接工廠
		ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
				"admin",
				"admin",
				"tcp://localhost:61616"
				);
		// 2.獲取一個向ActiveMQ的連接
		Connection connection = connectionFactory.createConnection();
		// 3.獲取session
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 4. 找目的地,獲取destination,消費端,也會從這個目的地取消息
		Queue queue = session.createQueue("user");
		// 51.消息創建者
		MessageProducer producer = session.createProducer(queue);
	//	producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
		// consumer -> 消費者
		// producer -> 創建者
		// 5.2. 創建消息
		for (int i = 0; i < 1000; i++) {
			TextMessage textMessage = session.createTextMessage("hi: " + i);
			// 5.3 向目的地寫入消息
			if(i % 4 == 0) {
				// 設置消息的優先級
				// 對producer 整體設置
			//	producer.setPriority(9);
			//	producer.send(textMessage,DeliveryMode.PERSISTENT,9,1000 * 100);
				textMessage.setJMSPriority(9);
			}		
				producer.send(textMessage);
		Thread.sleep(3000);
		}	
		// 6.關閉連接
		connection.close();	
		System.out.println("System exit....");
	}
}

  1. Receiver
package com.mashibing.activemq01;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

public class Receiver {
	public static void main(String[] args) throws Exception {
		// 1. 建立工廠對象,
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
				ActiveMQConnectionFactory.DEFAULT_USER,
				ActiveMQConnectionFactory.DEFAULT_PASSWORD,
				"tcp://localhost:61616"
				);
		//2 從工廠裏拿一個連接
		Connection connection = activeMQConnectionFactory.createConnection();
		connection.start();
		//3 從連接中獲取Session(會話)
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 從會話中獲取目的地(Destination)消費者會從這個目的地取消息
		Queue queue = session.createQueue("f");
		//從會話中創建消息提供者
		MessageConsumer consumer = session.createConsumer(queue);
		//從會話中創建文本消息(也可以創建其它類型的消息體)
		while (true) {
			TextMessage receive = (TextMessage)consumer.receive();
			System.out.println("TextMessage:" + receive.getText());
		}
	}
}

8 Active MQ的安全機制

  1. web控制檯安全:重啓後生效
//conf/jetty.xml中id="securityLoginService">配置了其登錄相關用戶的配置文件
conf/jetty-realm.properties
//用戶名:密碼,角色
admin: admin, admin
user: user, user
  1. 消息安全機制:配置建立連接工廠時的用戶和密碼
conf/activemq.xml
<!--添加在</shutdownHooks>後面,與它平級即可-->
<plugins>
      <simpleAuthenticationPlugin>
          <users>
              <authenticationUser username="admin" password="admin" groups="admins,publishers,consumers"/>
              <authenticationUser username="publisher" password="publisher"  groups="publishers,consumers"/>
              <authenticationUser username="consumer" password="consumer" groups="consumers"/>
              <authenticationUser username="guest" password="guest"  groups="guests"/>
          </users>
      </simpleAuthenticationPlugin>
 </plugins>

9 消息超時/過期

  1. 消息未被消費時,會存放於內存中,長期堆積存在內存裝不下的風險,爲防止這種情況產生,通常可以設置消息的超時時間
//單位毫秒
producer.setTimeToLive(1000);
  1. ActiveMQ會隔一段時間、或在消費端嘗試訪問某隊列中消息時,檢查該隊列中消息是否超時,如果超時,不會讓消費者消費此消息,而是讓死信隊列消費該消息,同時自動將該消息放入死信隊列
9.1 死信隊列
  1. 死信隊列保存一些因爲業務邏輯處理失敗,而導致消息的失敗或者說是消息發送過期的消息,有了死信隊列能夠保證在發送消息和接收消息過程中因爲某些異常導致消息丟失的隊列
  2. 由於非持久化消息,系統可能認爲這些消息並不重要,丟不丟失無所謂,默認情況下,不會進入死信隊列
  3. 對於不進死信隊列的消息,超時候,控制檯上會發現該消息被莫名其妙消費
  4. 死信隊列和正常隊列功能相同,本質上就是一個默認名爲ActiveMQ.DLQ的隊列,客戶端同樣可以從該隊列中獲取消息
  5. 修改死信隊列名稱與非持久化消息進入死信隊列
<policyEntry queue="user" prioritizedMessages="true" >
  <deadLetterStrategy> 
    <!--queuePrefix:指定死信隊列前綴,也就是將死信隊列名設爲"DLxxQ.user",useQueueForQueueMessages開啓死信隊列,默認開啓,processNonPersistent:非持久化消息也進入死信隊列-->	
    <individualDeadLetterStrategy   queuePrefix="DLxxQ." useQueueForQueueMessages="true" processNonPersistent="true" /> 
  </deadLetterStrategy> 
</policyEntry>
  1. 不讓消息進入死信隊列
<individualDeadLetterStrategy   processExpired="false"  /> 

10 獨佔消費者

  1. 創建Queue或Topic時,可以設置,其創建出的消費者,必須獨佔這個隊列中的所有消息
  2. 也就是說,當這個消費者開始消費這個隊列中的消息,只要這個消費者沒掛掉,剩下所有消息,必須都由這個消費者來消費, 其他消費者無法消費到該消息
//xxoo爲隊列名,該語句是創建consumer時用的,而不是producer
Queue queue = session.createQueue("xxoo?consumer.exclusive=true");
//同時設置優先級,Broker會根據consumer的優先級來發送消息到較高的優先級的Consumer上,此處優先級和上面消息的優先級概念不同,它是消費者的優先級
Queue queue = session.createQueue("xxoo?consumer.exclusive=true&consumer.priority=10");
  1. 使用selector
    1. 可以爲消息分組,同時設定consumer只消費某組下的消息
    2. 可以達到定向分發、消費消息,也就是負載均衡的感覺
    3. 如果我們已知一個服務處理消息的速度,我們就可以動態的去調整給每個服務器發多少消息
//在producer端,設置消息的屬性,其實就是在消息頭中,設定了properties={week=xx}
textMessage.setLongProperty("week", i%7);
//在consumer端,可以爲consumer設置selector,設置只接收頭中week屬性值爲1的消息,selector本質上就是一個字符串形式的表達式
MessageConsumer consumer = session.createConsumer(queue,"week=1");
//selector裏面也可以寫and,類似sql的語法
//MessageConsumer consumer = session.createConsumer(queue,"age > 17 and price<200");

11 消息發送原理

  1. 同步發送:調用send 方法發送消息時,該方法一直阻塞,直到ActiveMQ確認消息已經存儲在持久性數據存儲中,併發回確認消息
  2. 異步發送時,調用的是ActiveMQSession中send方法中的this.connection.asyncSendPacket(msg),而同步發送,調用的是其下面的this.connection.syncSendPacket(msg,sendTimeout)
開啓事務 關閉事務
持久化 異步 同步
非持久化 異步 異步
  1. 注意send只是阻塞到mq將數據都存放到數據庫中,而不是阻塞到consumer處理完消息
  2. 可以用以下幾種方式設置爲異步發送
//方法一
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin",
				"tcp://localhost:61616?jms.useAsyncSend=true");
//方法二
((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);
//方法三
((ActiveMQConnection)connection).setUseAsyncSend(true)
  1. 異步發送消息是有消息丟失的風險的
  2. 設置異步發送時的windowSize
    1. 用來約束在異步發送時,producer端允許積壓的(尚未ACK)的消息的尺寸,
    2. 只對異步發送有意義,因爲同步發送根本不會積壓ACK
    3. 每次發送消息之後,都將會導致memoryUsage尺寸增加(+message.size),當broker返回producerAck時,memoryUsage尺寸減少producerAck.size,此size表示先前發送消息的大小
    4. 發送消息時,會檢測memoryUsage中是否還有足夠空間,如果足夠,正常發送,如果不足,將會阻塞
    5. 可以通過如下2種方式設置
//方法一:會對所有producer生效
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin",
				"tcp://localhost:61616?jms.producerWindowSize=1048576");
//方法二:會對使用該目的地的所有producer生效,會覆蓋方法一的設置
Queue queue = session.createQueue("user?producer.windowSize=1048576");

12 延遲消息投遞

  1. 消息發送給消息中間件,然後消息中間件決定這個信息多久後才生效
  2. 需要在配置文件中開啓延遲和調度
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">
  1. 編程時設置消息的屬性爲延遲投遞
//其實就是給消息頭,加了一對屬性,key就是ScheduledMessage.AMQ_SCHEDULED_DELAY對應的字符串,value就是10*1000
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 10*1000);
  1. 帶間隔的重複發送
long delay = 10 * 1000;
long period = 2 * 1000;
int repeat = 9;
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
//間隔時間
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
//額外重複幾次,第一次不算,repeat必須設置爲int,不能設爲long,會失效
message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
createProducer.send(message);

13 Cron表達式指定時間發送消息

  1. Cron表達式是一個字符串,字符串以5或6個空格隔開,分爲6或7個域,每一個域代表一個含義,Cron有如下兩種語法格式
    1. Seconds Minutes Hours DayofMonth Month DayofWeek Year
    2. Seconds Minutes Hours DayofMonth Month DayofWeek
  2. 每一個域可出現的字符如下
    1. Seconds:可出現", - * /"四個字符,有效範圍爲0-59的整數
    2. Minutes:可出現", - * /"四個字符,有效範圍爲0-59的整數
    3. Hours:可出現", - * /"四個字符,有效範圍爲0-23的整數
    4. DayofMonth:可出現", - * / ? L W C"八個字符,有效範圍爲0-31的整數
    5. Month:可出現", - * /"四個字符,有效範圍爲1-12的整數或JAN-DEc
    6. DayofWeek:可出現", - * / ? L C #"四個字符,有效範圍爲1-7的整數或SUN-SAT兩個範圍。1表示星期天,2表示星期一, 依次類推
    7. Year:可出現", - * /"四個字符,有效範圍爲1970-2099年
  3. 特殊字符的含義
    1. :表示匹配該域的任意值,假如在Minutes域使用 , 即表示每分鐘都會觸發事件
    2. ?:只能用在DayofMonth和DayofWeek兩個域。它也匹配域的任意值,但實際不會。因爲DayofMonth和 DayofWeek會相互影響。例如想在每月的20日觸發調度,不管20日到底是星期幾,則只能使用如下寫法: 13 13 15 20 * ?, 其中最後一位只能用?,而不能使用*,如果使用*表示不管星期幾都會觸發,實際上並不是這樣
    3. -:表示範圍,例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發一次
    4. /:表示起始時間開始觸發,然後每隔固定時間觸發一次,例如在Minutes域使用5/20,則意味着5分鐘觸發一次,而25,45等分別觸發一次
    5. ,:表示列出枚舉值值。例如:在Minutes域使用5,20,則意味着在5和20分每分鐘觸發一次
    6. L:表示最後,只能出現在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最後的一個星期四觸發
    7. W:表示有效工作日(週一到週五),只能出現在DayofMonth域,系統將在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(週一)觸發;如果5日在星期一 到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份
    8. LW:這兩個字符可以連用,表示在某個月最後一個工作日,即最後一個星期五
    9. #:用於確定每個月第幾個星期幾,只能出現在DayofMonth域。例如在4#2,表示某月的第二個星期三
  4. 舉例
//每天中午12點觸發 
"0 0 12 * * ?" 
//每天上午10:15觸發 
"0 15 10 ? * *" 
//每天上午10:15觸發 
"0 15 10 * * ?" 
//每天上午10:15觸發 
"0 15 10 * * ? *" 
//2005年的每天上午10:15觸發 
"0 15 10 * * ? 2005" 
//在每天下午2點到下午2:59期間的每1分鐘觸發 
"0 * 14 * * ?" 
//在每天下午2點到下午2:55期間的每5分鐘觸發 
"0 0/5 14 * * ?" 
//在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發 
"0 0/5 14,18 * * ?" 
//在每天下午2點到下午2:05期間的每1分鐘觸發 
"0 0-5 14 * * ?" 
//每年三月的星期三的下午2:10和2:44觸發 
"0 10,44 14 ? 3 WED" 
//週一至週五的上午10:15觸發 
"0 15 10 ? * MON-FRI" 
//每月15日上午10:15觸發 
"0 15 10 15 * ?" 
//每月最後一日的上午10:15觸發 
"0 15 10 L * ?" 
//每月的最後一個星期五上午10:15觸發 
"0 15 10 ? * 6L" 
//2002年至2005年的每月的最後一個星期五上午10:15觸發 
"0 15 10 ? * 6L 2002-2005" 
//每月的第三個星期五上午10:15觸發
"0 15 10 ? * 6#3" 

14 activemq.xml中的memoryUsage

<!--啓動broker時,相當於啓動一個java程序,該參數設定最多使用分配給broker的內存空間的百分之多少,來存儲消息數據-->
<memoryUsage>
	<memoryUsage percentOfJvmHeap="70" />
</memoryUsage>
<!--表示activemq那些持久化消息,可最多使用磁盤空間的大小,如果持久化到mysql中,該參數不生效,持久化到kahadb中生效-->
<!--如果啓動時,硬盤只有20g,那麼最大空間如果設置爲100g,那麼此時就算再騰出空間,也只能用到20g-->
<storeUsage>
	<storeUsage limit="100 gb" />
</storeUsage>
<!--不持久化的消息,會先寫入內存,如果超過了memoryUsage值,就會寫入到一塊臨時空間,該參數用於限制這塊臨時空間大小-->
<tempUsage>
	<tempUsage limit="50 gb" />
</tempUsage>

15 監聽器

  1. 使用for(true)接收消息時
    1. 如果一條消息特別大,會接收很久,receive方法會一直阻塞
    2. for循環中的業務處理邏輯,如果需要處理很久,其他消息也同樣無法進來
    3. 無法高併發處理
  2. 可以使用監聽器來處理消息接收,從而併發處理消息,當收到消息後會調用onMessage方法對消息進行業務處理,就不用手動調用consumer.receive來接收消息了
consumer.setMessageListener(new MessageListener() {
	public void onMessage(Message message) {
		try {
			System.out.println("message2:" + ((TextMessage)message).getText());
		} catch (JMSException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章