JMS(消息中間件)
1 消息中間件
消息中間件適用于需要可靠的數據傳送的分布式環境。采用消息中間件機制的系統中,不同的對象之間通過傳遞消息來激活對方的事件,完成相應的操作。發送者將消息發送給消息服務器,消息服務器將消息存放在若干隊列中,在合適的時候再將消息轉發給接收者。消息中間件能在不同平臺之間通信,它常被用來屏蔽掉各種平臺及協議之間的特性,實現應用程序之間的協同,其優點在于能夠在客戶和服務器之間提供同步和異步的連接,并且在任何時刻都可以將消息進行傳送或者存儲轉發,這也是它比遠程過程調用更進一步的原因。
MQ采用異步調動的方式,解除耦合(程序不啟動也能運行),提高了運行性能(不用擔心處理時間,處理時間的長短不影響程序性能,
常用的消息中間件:
-
ActiveMQ
是Apache出品,最流行的,能力強勁的開源消息總線。ActiveMQ是一個完全支持JMS1.1和JSEE1.4規范的JMS Provider實現。我們在本次課程中介紹ActiveMQ的使用。
-
RabbitMQ
AMQP協議的領導實現,支持多種場景。淘寶的MySQL集群內部有使用它進行通訊,OpenStack開源云平臺的通信組件,最先在金融行業得到運用。
-
ZeroMQ
史上最快的消息隊列系統
-
Kafka
Apache下的一個子項目。特點:高吞吐,在一臺普通的服務器上既可以達到10W/s的吞吐速率;完全的分布式系統,適合處理海量數據。
2 簡介
JMS即Java消息服務(Java Message Service)應用程序接口,是一個Java平臺中關于面向消息中間件(MOM)的API,用于在兩個應用程序之間,或分布式系統中發送消息,進行異步通信。Java消息服務是一個與具體平臺無關的API,絕大多數MOM提供商都對JMS提供支持。
JMS本身只定義了一系列的接口規范,是一種與廠家無關的API,用來訪問消息收發系統。它類似于JDBC,JDBC是可以用來訪問許多不同關系數據庫API_(:з」∠) _ ,而JMS則提供同樣與廠商無關的訪問方法,以訪問消息收發服務。許多廠商目前都支持JMS。
JMS定義了五種不同的消息格式,以及調用的消息類型,允許你發送并接收一些不同類型的數據,提供現有消息格式的一些級別的兼容性。
- TextMessage -- 一個字符串對象(常見,也最簡單)
- MapMessage -- 一套鍵值對(傳多個信息)
- ObjectMessage -- 一個序列化的Java對象(傳遞實現了可序列化接口的類)
- BytesMessage -- 一個字節的數據流(文件、音頻)
- StreamMessage -- Java原始值的數據流
2.1 JMS消息傳遞類型
對于消息的傳遞有兩種類型:
-
一種是點對點模式,即一個生產者和一個消費者一一對應。
一個或多個生產者把產品放在隊列上,(一個)消費者在隊列的另一邊接收數據
-
一種是發布/訂閱模式,即一個生產者產生的消息進行發送后,可以由多個消費者進行接收。
一個或多個生產者產生在Topic(主題),所有消費者都能同時(廣播)接收到該Topic(主題)
3 ActiveMQ
3.1 安裝ActiveMQ
在官網下載ActiveMQ
sftp上傳到linux
tar zxvf tar.gz文件(解壓縮)
chmod 777 解壓后的文件夾 (給該文件夾賦予最高權限,任何用戶都可以訪問)
cd 文件夾/bin
chmod 755 activemq
./activemq start 啟動
訪問連接 http://IP地址:8161
? 訪問紅線部分,輸入默認賬號密碼admin
3.2 入門Demo (點對點模式)
-
引入依賴
<!-- 使用哪個版本的activemq就使用哪個版本的client --> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-client</artifactId> <version>5.15.10</version> </dependency>
-
編寫一個生產者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; /** * 點對點關系 */ public class QueueProducer { public static void main(String[] args) throws JMSException { //1.創建連接工廠 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.創建連接 Connection connection = connectionFactory.createConnection(); //3.啟動連接 connection.start(); //4.獲取session(會話對象) /** * boolean:是否啟動事務,如果為true,代表commit的時候才提交事務 * int:消息的確認方式 AUTO_ACKNOWLEDGE = 1(自動確認); CLIENT_ACKNOWLEDGE = 2(客戶端手動確認); * DUPS_OK_ACKNOWLEDGE = 3(自動批量確認); SESSION_TRANSACTED = 0(事務提交并確認) */ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.創建隊列對象,傳入隊列的名字 Queue queue = session.createQueue("test-queue"); //6.創建消息生產者對象 MessageProducer producer = session.createProducer(queue); //7.創建消息對象(文本消息) TextMessage textMessage = session.createTextMessage("歡迎使用activeMQ"); //8.生產者發送消息 producer.send(textMessage); //9.關閉資源 producer.close(); session.close(); connection.close(); } }
-
運行并查看結果
activemq第一個demo.png
-
編寫一個消費者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class QueueConsumer { public static void main(String[] args) throws JMSException, IOException { //1.創建連接工廠 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.創建連接 Connection connection = connectionFactory.createConnection(); //3.啟動連接 connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.創建隊列對象,傳入隊列的名字 Queue queue = session.createQueue("test-queue"); //6.創建消息消費者對象 MessageConsumer consumer = session.createConsumer(queue); //7.設置監聽,當有消息產生的時候就消費消息 consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("提取的消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }); //8.等待鍵盤輸入,目的是讓消費者程序不終止執行 System.in.read(); //9.關閉資源 consumer.close(); session.close(); connection.close(); } }
-
運行后發現消息被消費,以及消費者數+1
activemq消費者.png -
其他細節
- 此時消費者由于System.in.read(); 就沒有被終止程序,也就是一直處于監聽狀態
- 如果此時再次有生產者生產時,處于監聽狀態的消費者會立馬消費
- 當一個隊列有多個消費者時,如果有消息產生,只會有一個消費者得到消息
-
點對點小結
- 當消息只需要消費一次時,可以使用點對點的方式(比如搜索服務)
3.3 發布/訂閱模式
-
編寫生產者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; /** * 發布訂閱模式的生產者 */ public class TopicProducer { public static void main(String[] args) throws JMSException, IOException { //1.創建連接工廠 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.創建連接 Connection connection = connectionFactory.createConnection(); //3.啟動連接 connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.創建主題對象,傳入主題的名字 Topic topic = session.createTopic("test-topic"); //6.創建消息生產者對象 MessageProducer producer = session.createProducer(topic); //7.創建消息對象(文本消息) TextMessage textMessage = session.createTextMessage("歡迎使用activeMQ"); //8.生產者發送消息 producer.send(textMessage); //9.關閉資源 producer.close(); session.close(); connection.close(); } }
-
運行后查看結果
activemq發布訂閱-生產.png -
編寫消費者
import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; import java.io.IOException; public class TopicConsumer { public static void main(String[] args) throws JMSException, IOException { //1.創建連接工廠 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.234.129:61616"); //2.創建連接 Connection connection = connectionFactory.createConnection(); //3.啟動連接 connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.創建隊列對象,傳入隊列的名字 Topic topic = session.createTopic("test-topic"); //6.創建消息消費者對象 MessageConsumer consumer = session.createConsumer(topic); //7.設置監聽,當有消息產生的時候就消費消息 consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("提取的消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }); //8.等待鍵盤輸入,目的是讓消費者程序不終止執行 System.in.read(); //9.關閉資源 consumer.close(); session.close(); connection.close(); } }
-
運行但發現之前發送的主題沒有被消費,說明發布/訂閱模式下
- 當有主題發送出來時,就會向在場的消費者進行廣播
- 新進的消費者不能收到以前發布的主題
4 Spring整合JMS
4.1 點對點模式
- 先做生產者
-
導入依賴包
<spring-version>5.1.5.RELEASE</spring-version> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring-version}</version> </dependency> <!-- 這個版本的Spring需要使用JMS 2.0版本,但spring-jms的依賴沒有自動導入JMS 2.0 可以手動除去jms的錯誤導入避免沖突 --> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-client</artifactId> <version>5.15.10</version> <exclusions> <exclusion> <artifactId>spring-context</artifactId> <groupId>org.springframework</groupId> </exclusion> <exclusion> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jms_1.1_spec</artifactId> </exclusion> </exclusions> </dependency> <!-- 導入正確的jms --> <dependency> <groupId>javax.jms</groupId> <artifactId>javax.jms-api</artifactId> <version>2.0.1</version> </dependency>
-
編寫配置文件applicationContext-jms.xml(此處先編寫生產者的配置文件)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="spring.activemq.demo"></context:component-scan> <!-- 配置產生Connection的ConnectionFactory,由對應的JMS服務廠商提供 --> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.234.129:61616"/> </bean> <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory"/> </bean> <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 --> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 --> <property name="connectionFactory" ref="connectionFactory"/> </bean> <!-- 這個是隊列目的地,點對點的文本信息 --> <bean id="queueTextDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="queue_text"/> </bean> <!-- 這個是發布/訂閱模式的文本信息 --> <bean id="topicTextDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="topic_text"/> </bean> </beans>
-
編寫生產者,里面要寫生產的邏輯
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.stereotype.Component; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; @Component public class QueueProducer { @Autowired private JmsTemplate jmsTemplate; @Autowired private Destination queueTextDestination; /** * 發送文本信息 * @param text */ public void sendTextMessage(final String text) { jmsTemplate.send(queueTextDestination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { return session.createTextMessage(text); } }); } }
-
編寫測試類,測試運行效果
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import spring.activemq.demo.QueueProducer; //需要導入配置文件,讓spring框架能使用配置文件中的配置 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext-jms.xml") public class TestQueue { @Autowired private QueueProducer queueProducer; @Test public void testSend() { queueProducer.sendTextMessage("spring JMS 點對點"); } }
- 編寫消費者
-
自定義監聽方法
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; public class MyMessageListener implements MessageListener { @Override public void onMessage(Message message) { TextMessage textMessage = (TextMessage) message; try { System.out.println("接收到消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }
-
修改配置文件,加入消費者
但要注意加入的消費者在導入配置文件后會自動啟動,可以生產者和消費者分成兩個不同的配置文件
這里為了方便就不另建配置文件和測試類
<!-- 設置監聽類 --> <bean id="myMessageListener" class="spring.activemq.demo.MyMessageListener"></bean> <!-- 消息監聽器,需要設置connectionFactory 目的類型 監聽類 此處沒有id,因為加上該配置后,在啟動該配置時候自動裝載啟動 --> <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="queueTextDestination"/> <property name="messageListener" ref="myMessageListener"/> </bean>
-
編寫測試類測試消費者
@Test public void textGetQueue() { try { //添加暫停語句, System.in.read(); } catch (IOException e) { e.printStackTrace(); } }
4.2 發布/訂閱模式
- 編寫生產者
-
編寫配置文件,此處為了方便沿用點對點模式的配置類,但要先注釋掉點對點模式的消費者監聽器
(個人認為一個消費者可以類比為一個監聽器了)
<!--<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="queueTextDestination"/> <property name="messageListener" ref="myMessageListener"/> </bean>-->
-
編寫生產者類
對比于點對點模式的生產者,只是目的類型有變化,可以看出點對點模式和發布/訂閱模式的connectionFactory是一樣的(至少可以從配置類文件可以看出)
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.stereotype.Component; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; @Component public class TopicProducer { @Autowired private JmsTemplate jmsTemplate; @Autowired private Destination topicTextDestination; /** * 發送文本信息 * @param text */ public void sendTextMessage(final String text) { jmsTemplate.send(topicTextDestination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { return session.createTextMessage(text); } }); } }
-
編寫測試類
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import spring.activemq.demo.TopicProducer; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext-jms.xml") public class TestTopic { @Autowired private TopicProducer topicProducer; @Test public void testSend() { topicProducer.sendTextMessage("spring JMS 發布/訂閱"); } }
運行后可以通過瀏覽器查看結果
- 編寫消費者
-
修改配置文件,加上消費者的監聽器
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="topicTextDestination"/> <property name="messageListener" ref="myMessageListener"/> </bean>
從配置文件上看,兩種模式的不同也只是目標類型上的不同,和生產者的情況有點類似
-
添加測試類方法
@Test public void textGetQueue() { try { //添加暫停語句, System.in.read(); } catch (IOException e) { e.printStackTrace(); } }