POM.xml文件
<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.11</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.xbean/xbean-spring --> <dependency> <groupId>org.apache.xbean</groupId> <artifactId>xbean-spring</artifactId> <version>4.15</version> </dependency>
JMS編碼總體架構
回憶一下以前的JDBC編碼套路
第一步:註冊驅動(僅僅只做一次)
Class.forName("com.mysql.jdbc.com");
第二步:建立連接(Connection)
DriverManager.getConnection(url,user,password);
第三步:創建運行SQL語句(Statement)
connection.createStatement();
第四步:運行語句
rs.executeQuery(sql);
第五步:處理結果集(ResultSet)
第六步:釋放資源
粗說目的地Destination 隊列(Queue)和主題(Topic) 兩大模式特性
在點對點的消息傳遞域中,目的地被稱爲隊列(queue)
點對點消息傳遞域的特點如下:
(1)每個消息只能有一個消費者,類似於1對1的關係。好比個人快遞自己領自己的。
(2)消息的生產者和消費者之間沒有時間上的相關性。無論消費者在生產者發送消息的時候是否處於運行狀態,消費者都可以提取消息。好比我們的發送短信,發送者發送後不見得接收者會即收即看。
(3)消息被消費後隊列中不會再存儲,所以消費者不會消費到已經被消費掉的消息。
上手案例
消息生產者
代碼
package com.demo.activemq.queue; import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class JmsProduce { private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616"; private static final String QUEUE_NAME = "queue01"; public static void main(String[] args) throws JMSException { //1.創建連接工廠,按照給定的URL,採用默認的用戶名密碼 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通過連接工廠,獲得connection並啓動訪問 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.創建會話session //兩個參數transacted=事務,acknowledgeMode=確認模式(簽收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.創建目的地(具體是隊列queue還是主題topic) Queue queue = session.createQueue(QUEUE_NAME); //5.創建消息的生產者 MessageProducer messageProducer = session.createProducer(queue); //6.通過使用消息生產者,生產三條消息,發送到MQ的隊列裏面 for (int i = 0; i < 3; i++) { //7.創建消息 TextMessage textMessage = session.createTextMessage("msg---hello" + i);//理解爲一個字符串 //8.通過messageProducer發送給MQ隊列 messageProducer.send(textMessage); } //9.關閉資源 messageProducer.close(); session.close(); System.out.println("****消息發佈到MQ隊列完成"); } }
控制檯
控制檯說明
Number Of Pending Messages=等待消費的消息,這個是未出隊列的數量,公式=總接收數-總出隊列數。
Number Of Consumers=消費者數量,消費者端的消費者數量。
Messages Enqueued=進隊消息數,進隊列的總消息量,包括出隊列的。這個數只增不減。
Messages Dequeued=出隊消息數,可以理解爲是消費者消費掉的數量。
總結:
當有一個消息進入這個隊列時,等待消費的消息是1,進入隊列的消息是1。
當消息消費後,等待消費的消息是0,進入隊列的消息是1,出隊列的消息是1。
當再來一條消息時,等待消費的消息是1,進入隊列的消息就是2。
消息消費者
代碼1-阻塞式消費者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class JmsConsumer { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String QUEUE_NAME = "queue01"; public static void main(String[] args) throws JMSException { //1.創建連接工廠,按照給定的URL,採用默認的用戶名密碼 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通過連接工廠,獲得connection並啓動訪問 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.創建會話session //兩個參數transacted=事務,acknowledgeMode=確認模式(簽收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.創建目的地(具體是隊列queue還是主題topic) Queue queue = session.createQueue(QUEUE_NAME); //5.創建消息的消費者,指定消費哪一個隊列裏面的消息 MessageConsumer messageConsumer = session.createConsumer(queue); //循環獲取 while (true) { //6.通過消費者調用方法獲取隊列裏面的消息(發送的消息是什麼類型,接收的時候就強轉成什 麼類型) //receive(2000l)兩個重載方法,參數是lang類型的時間,等待一段時間後沒有消息就關閉,如果沒有參數則不會關閉 TextMessage textMessage = (TextMessage) messageConsumer.receive(2000l); if (textMessage != null) { System.out.println("****消費者接收到的消息: " + textMessage.getText()); }else { break; } } //7.關閉資源 messageConsumer.close(); session.close(); connection.close(); } }
代碼2-異步監聽式消費者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class JmsConsumer2 { private static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; private static final String QUEUE_NAME = "queue01"; public static void main(String[] args) throws JMSException, IOException { //1.創建連接工廠,按照給定的URL,採用默認的用戶名密碼 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通過連接工廠,獲得connection並啓動訪問 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.創建會話session //兩個參數transacted=事務,acknowledgeMode=確認模式(簽收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.創建目的地(具體是隊列queue還是主題topic) Queue queue = session.createQueue(QUEUE_NAME); //5.創建消息的消費者,指定消費哪一個隊列裏面的消息 MessageConsumer messageConsumer = session.createConsumer(queue); //6.通過監聽的方式消費消息 /* 異步非阻塞式方式監聽器(onMessage) 訂閱者或消費者通過創建的消費者對象,給消費者註冊消息監聽器setMessageListener, 當消息有消息的時候,系統會自動調用MessageListener類的onMessage方法 我們只需要在onMessage方法內判斷消息類型即可獲取消息 */ messageConsumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { if (message != null && message instanceof TextMessage) { //7.把message轉換成消息發送前的類型並獲取消息內容 TextMessage textMessage = (TextMessage) message; try { System.out.println("****消費者接收到的消息: " + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } } }); System.out.println("執行了39行"); //保證控制檯不關閉,阻止程序關閉,監聽器是異步執行,如果不阻執行System.in.read();可能還沒有監聽就把連接關閉了 System.in.read(); //關閉資源 messageConsumer.close(); session.close(); connection.close(); } }
編碼小總結
JMS開發的基本步驟
兩種消費方式
兩種消費方式
同步阻塞方式(receive)
訂閱者或接收者抵用MessageConsumer的receive()方法來接收消息,receive方法在能接收到消息之前(或超時之前)將一直阻塞。//循環獲取 while (true) { //6.通過消費者調用方法獲取隊列裏面的消息(發送的消息是什麼類型,接收的時候就強轉成什 麼類型) //receive(2000l)兩個重載方法,參數是lang類型的時間,等待一段時間後沒有消息就關閉,如果沒有參數則不會關閉 TextMessage textMessage = (TextMessage) messageConsumer.receive(2000l); if (textMessage != null) { System.out.println("****消費者接收到的消息: " + textMessage.getText()); }else { break; } }
異步非阻塞方式(監聽器onMessage())
訂閱者或接收者通過MessageConsumer的setMessageListener(MessageListener listener)註冊一個消息監聽器,
當消息到達之後,系統會自動調用監聽器MessageListener的onMessage(Message message)方法。
/* 異步非阻塞式方式監聽器(onMessage) 訂閱者或消費者通過創建的消費者對象,給消費者註冊消息監聽器setMessageListener, 當消息有消息的時候,系統會自動調用MessageListener類的onMessage方法 我們只需要在onMessage方法內判斷消息類型即可獲取消息 */ messageConsumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { if (message != null && message instanceof TextMessage) { //7.把message轉換成消息發送前的類型並獲取消息內容 TextMessage textMessage = (TextMessage) message; try { System.out.println("****消費者接收到的消息: " + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } } }); System.out.println("執行了39行"); //保證控制檯不關閉,阻止程序關閉,監聽器是異步執行,如果不阻執行System.in.read();可能還沒有監聽就把連接關閉了 System.in.read();
如果啓動兩個消費者共同連接一個隊列,當隊列有消息的時候會平均分給兩個消費者,採用類似於負載均衡輪詢的方式
在發佈訂閱消息傳遞域中,目的地被稱爲主題(topic)
發佈/訂閱消息傳遞域的特點如下:
(1)生產者將消息發佈到topic中,每個消息可以有多個消費者,屬於1:N的關係;
(2)生產者和消費者之間有時間上的相關性。訂閱某一個主題的消費者只能消費自它訂閱之後發佈的消息。
(3)生產者生產時,topic不保存消息它是無狀態的不落地,假如無人訂閱就去生產,那就是一條廢消息,所以,一般先啓動消費者再啓動生產者。
JMS規範允許客戶創建持久訂閱,這在一定程度上放鬆了時間上的相關性要求。持久訂閱允許消費者消費它在未處於激活狀態時發送的消息。一句話,好比我們的微信公衆號訂閱
上手案例
發佈主題生產者
代碼
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class JmsProducer_Topic { public static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; public static final String TOPIC_NAME = "topic01"; public static void main(String[] args) throws JMSException { //1.創建連接工廠,按照給定的URL,採用默認的用戶名密碼 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通過連接工廠,獲得connection並啓動訪問 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.創建會話session //兩個參數transacted=事務,acknowledgeMode=確認模式(簽收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.創建目的地(具體是隊列queue還是主題topic) Topic topic = session.createTopic(TOPIC_NAME); //5.創建消息的生產者 MessageProducer messageProducer = session.createProducer(topic); //6.通過使用消息生產者,生產三條消息,發送到MQ的隊列裏面 for (int i = 0; i < 3; i++) { //7.通過session創建消息 TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i); //8.使用指定好目的地的消息生產者發送消息 messageProducer.send(textMessage); } //9.關閉資源 messageProducer.close(); session.close(); connection.close(); System.out.println("****TOPIC_NAME消息發佈到MQ完成"); } }
控制檯
訂閱主題消費者
代碼
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class JmsConsumer_Topic { public static final String ACTIVEMQ_URL = "tcp://192.168.150.134:61616"; public static final String TOPIC_NAME = "topic01"; public static void main(String[] args) throws JMSException, IOException { System.out.println("我是1號消費者"); //1.創建連接工廠,按照給定的URL,採用默認的用戶名密碼 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); //2.通過連接工廠,獲得connection並啓動訪問 Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); //3.創建會話session //兩個參數transacted=事務,acknowledgeMode=確認模式(簽收) Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //4.創建目的地(具體是隊列queue還是主題topic) Topic topic = session.createTopic(TOPIC_NAME); //5.創建消息的消費者 MessageConsumer messageConsumer = session.createConsumer(topic); //5.創建消息的消費者,指定消費哪一個隊列裏面的消息 messageConsumer.setMessageListener(message -> { if (message instanceof TextMessage){ try { String text = ((TextMessage) message).getText(); System.out.println(text); } catch (JMSException e) { e.printStackTrace(); } } }); System.in.read(); } }
先啓動訂閱者再啓動生產者,不然發送的消息是廢消息
小總結
兩大模式特效
兩大模式比較
比較項目 Topic隊列模式 Queue隊列模式
1.工作模式Topic隊列模式 "訂閱-發佈"模式,如果當前沒有訂閱者,消息將會被丟棄,如果有多個訂閱者,那麼這些訂閱者都會收到消息
Queue隊列模式 "負載均衡"模式,如果當前沒有消費者,消息也不會丟棄;如果有多個消費者,那麼一條消息也只會發送給其中一個消費者,並且要求消費者ack信息
2.有無狀態Topic隊列模式 無狀態
Queue隊列模式 Queue數據默認會在mq服務器上已文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面,也可以配置成DB存儲
3.傳遞完整性Topic隊列模式 如果沒有訂閱者,消息會被丟棄
Queue隊列模式 消息不會被丟棄
4.處理效率Topic隊列模式 由於消息要按照訂閱者的數量進行復制,所以處理性能會隨着訂閱者的增加而明顯降低,並且還要結合不同消息協議自身的性能差異
Queue隊列模式 由於一條消息只發送給一個消費者,所以就算消費者再多,性能也不會有明顯降低。當然不同消息協議的具體性能也是有差異的