JMS消息隊列--待續

JMS(消息中間件)

1 消息中間件

消息中間件適用于需要可靠的數據傳送的分布式環境。采用消息中間件機制的系統中,不同的對象之間通過傳遞消息來激活對方的事件,完成相應的操作。發送者將消息發送給消息服務器,消息服務器將消息存放在若干隊列中,在合適的時候再將消息轉發給接收者。消息中間件能在不同平臺之間通信,它常被用來屏蔽掉各種平臺及協議之間的特性,實現應用程序之間的協同,其優點在于能夠在客戶和服務器之間提供同步和異步的連接,并且在任何時刻都可以將消息進行傳送或者存儲轉發,這也是它比遠程過程調用更進一步的原因。

1572359464129.png

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

  1. 官網下載ActiveMQ

  2. sftp上傳到linux

  3. tar zxvf tar.gz文件(解壓縮)

  4. chmod 777 解壓后的文件夾 (給該文件夾賦予最高權限,任何用戶都可以訪問)

  5. cd 文件夾/bin

  6. chmod 755 activemq

  7. ./activemq start 啟動

  8. 訪問連接 http://IP地址:8161

9.
activemq首頁.png

? 訪問紅線部分,輸入默認賬號密碼admin

3.2 入門Demo (點對點模式)

  1. 引入依賴

    <!-- 使用哪個版本的activemq就使用哪個版本的client -->
    <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-client</artifactId>
        <version>5.15.10</version>
    </dependency>
    
  2. 編寫一個生產者

    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();
    
        }
    }
    
  3. 運行并查看結果

    activemq第一個demo.png
  1. 編寫一個消費者

    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();
        }
    }
    
  2. 運行后發現消息被消費,以及消費者數+1

    activemq消費者.png
  3. 其他細節

    • 此時消費者由于System.in.read(); 就沒有被終止程序,也就是一直處于監聽狀態
    • 如果此時再次有生產者生產時,處于監聽狀態的消費者會立馬消費
    • 當一個隊列有多個消費者時,如果有消息產生,只會有一個消費者得到消息
  4. 點對點小結

    • 當消息只需要消費一次時,可以使用點對點的方式(比如搜索服務)

3.3 發布/訂閱模式

  1. 編寫生產者

    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();
        }
    }
    
  2. 運行后查看結果

    activemq發布訂閱-生產.png
  3. 編寫消費者

    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. 運行但發現之前發送的主題沒有被消費,說明發布/訂閱模式下

    • 當有主題發送出來時,就會向在場的消費者進行廣播
    • 新進的消費者不能收到以前發布的主題

4 Spring整合JMS

4.1 點對點模式

  • 先做生產者
  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>
    
  2. 編寫配置文件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>
    
  3. 編寫生產者,里面要寫生產的邏輯

    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);
                }
            });
        }
    }
    
  4. 編寫測試類,測試運行效果

    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 點對點");
        }
    }
    
    
  • 編寫消費者
  1. 自定義監聽方法

    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();
            }
        }
    }
    
    
  2. 修改配置文件,加入消費者

    但要注意加入的消費者在導入配置文件后會自動啟動,可以生產者和消費者分成兩個不同的配置文件

    這里為了方便就不另建配置文件和測試類

    <!-- 設置監聽類 -->
    <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>
    
    
  3. 編寫測試類測試消費者

    @Test
    public void textGetQueue() {
        try {
            //添加暫停語句,
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    

4.2 發布/訂閱模式

  • 編寫生產者
  1. 編寫配置文件,此處為了方便沿用點對點模式的配置類,但要先注釋掉點對點模式的消費者監聽器

    (個人認為一個消費者可以類比為一個監聽器了)

    <!--<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="queueTextDestination"/>
        <property name="messageListener" ref="myMessageListener"/>
    </bean>-->
    
    
  2. 編寫生產者類

    對比于點對點模式的生產者,只是目的類型有變化,可以看出點對點模式和發布/訂閱模式的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);
                }
            });
        }
    }
    
    
  3. 編寫測試類

    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 發布/訂閱");
        }
    }
    
    

    運行后可以通過瀏覽器查看結果

  • 編寫消費者
  1. 修改配置文件,加上消費者的監聽器

    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="topicTextDestination"/>
        <property name="messageListener" ref="myMessageListener"/>
    </bean>
    
    

    從配置文件上看,兩種模式的不同也只是目標類型上的不同,和生產者的情況有點類似

  2. 添加測試類方法

    @Test
    public void textGetQueue() {
        try {
            //添加暫停語句,
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容