異步消息機制及使用ActiveMQ(JMS)發送消息

一,異步消息

    像RMI和Hession/Burlap這些遠程調用機制都是同步的,當客戶端調用遠程方法時,客戶端必須等到遠程方法完成後,纔可以繼續執行,但是有些操作是不需要等待,這時就可以用異步消息。

    有兩個主要概念,消息代理(message broker)和目的地(destination),當一個應用消息發送時,會將消息發送給消息代理,消息代理可以確保消息被投遞到目的地,同時解放發送者,使其可以繼續進行其他的業務。

    不同的消息系統會有不同的消息路由模式,但是有兩種通用的目的地,隊列(queue)和主題(topic),每種類型與特定的消息模型關聯,分別是點對點模型和發佈/訂閱模型。

    點對點模型:每條消息只會發送給一個接收者,消息投遞後就會從隊列中刪除,所以可以確保每條消息只有一個接收者,但是並不意味着一個隊列只可以有一個接收者,一個隊列可以有多個接收者。

    發佈/訂閱模型:主題的所有訂閱者都可以接收到消息的副本。

二,異步消息相比同步rpc的優點

1,無需等待

2,面向消息和解耦,與面向方法調用rpc通信不同,發送異步消息是以數據爲中心的,客戶端沒有與特定的方法簽名綁定

3,與遠程服務位置解耦(位置獨立),同步rpc服務一般需要網絡地址定位,在使用消息後,客戶端只需要知道通過那個隊列或主題來發送消息,服務只需要能夠從隊列或主題中獲取消息即可。基於此(位置獨立),在點對點模型中,當服務過載時,可以添加服務實例來監聽相同的隊列就可以增加處理能力;在發佈/訂閱模型中,多個服務可以訂閱同一個主題,但是每個服務的處理邏輯可以不同。例如,一組服務共同監聽新員工消息這個主題,其中一個服務可以在工資系統中增加該員工,一個系統可以將新員工添加到HR門戶中,還有一個服務可以爲新員工分配可訪問系統權限。

4,確保投遞,即使消息發送時服務無法使用,消息也會存儲起來,直到服務可用爲止。

三,Java消息服務JMS(Java Message Service)

    JMS是Java EE的標準/規範之一,這種規範指出:消息發送應該是一步的,非阻塞的,發送者和接收者可以說是互不影響。jms只是java ee中定義的一組標準的API,它自身並不是一個消息服務系統,它是消息傳送服務的一個抽象,也就是它定義了消息傳送的接口但是並沒有具體的實現,也就是說jms只是一組接口而已。

    而ActiveMQ就是JMS規範的具體實現,它是apache下的一個項目,採用java開發;就像JDBC抽象了關係數據庫的訪問,JPA抽象了對象與關係數據庫的映射,JMS的具體實現由不同的消息中間件廠商提供,而Apache ActiveMQ就是一個,所以ActiveMQ是消息服務系統,而JMS不是。

四,以下是一個使用ActiveMQ的隊列消息demo

消息發送端:

package com.uiao.activemq;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;

import javax.jms.*;

/**
 * 點對點消息發送端
 *
 * Created by uiao on 2018/3/10.
 */
public class QueueProducer {

    public static Logger logger = Logger.getLogger(QueueProducer.class);

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session;
        Destination destination;
        MessageProducer messageProducer;
        // 在這可以根據用戶名和密碼以及url使用有參的構造函數創建鏈接工廠
        connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

        try {
            connection = connectionFactory.createConnection();
            connection.start();
          
            session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
            destination = session.createQueue("mq-queue");

            messageProducer = session.createProducer(destination);

            //DeliveryMode.PERSISTENT 當activemq關閉的時候,隊列數據將會被保存
            //DeliveryMode.NON_PERSISTENT 當activemq關閉的時候,隊列裏面的數據將會被清空
            messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

            sendMessage(session, messageProducer);
            messageProducer.setTimeToLive(1000000);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception {
        for (int i = 0; i < 20; i++) {
            Thread.sleep(100);

            // 消息轉化器可以自定義或者用jmsMessagingTemplate
            TextMessage textMessage = session.createTextMessage("第 " + i + " 條文本消息");

            // 傳遞序列化對象,傳遞javaBean,TopicProducer就是用的這個
            MqBean bean = new MqBean();
            bean.setAge(24);
            bean.setName("uiao");
            ObjectMessage objectMessage = session.createObjectMessage(bean);

            // 傳遞流,用來傳遞文件
            StreamMessage streamMessage = session.createStreamMessage();

            // 傳遞map
            MapMessage mapMessage = session.createMapMessage();

            // 傳遞文件
            BytesMessage bytesMessage = session.createBytesMessage();

            logger.info("發送消息:" + textMessage.getText());
            messageProducer.send(textMessage);
        }
    }
}

    Session的方法createSession(boolean var1, int var2) 第一個參數爲true是表示支持事務,後面的自動確認爲Session.SESSION_TRANSACTED

第一個參數爲false時,var2的是可以爲Session.AUTO_ACKNOWLEDGE,Session.CLIENT_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE其中一個。

消息接收端:

package com.uiao.activemq;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;

import javax.jms.*;

/**
 * 點對點消息接收端
 *
 * Created by uiao on 2018/3/10.
 */
public class QueueConsumer {

    private static Logger logger = Logger.getLogger(QueueConsumer.class);

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session;
        Destination destination;
        MessageConsumer messageConsumer;
        connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); // 需要接收端確認
            destination = session.createQueue("mq-queue");
            messageConsumer = session.createConsumer(destination);
            // 不使用監視器的情況
            while (true) {
                TextMessage textMessage = (TextMessage) messageConsumer.receive(1000); // 設置超時時間,超過不等待消息
                if (textMessage != null) {
                    logger.info("收到的消息:" + textMessage.getText());
                    textMessage.acknowledge();
                } else {
                    break;
                }
            }
            // 使用監視器的情況
            /*messageConsumer.setMessageListener(new MessageListener() {
                // 實現了一個監聽器
                @Override
                public void onMessage(Message message) {
                    try {
                        TextMessage textMessage = (TextMessage) message;
                        //MqBean mqBean = (MqBean) ((ObjectMessage) message).getObject();
                        if (null != message) {
                            logger.info("收到的消息:" + textMessage.getText());
                        }
                        // 如果session設置爲Session.CLIENT_ACKNOWLEDGE,要加上這一步
                        message.acknowledge();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });*/
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打開瀏覽器,進入管理頁面http://localhost:8161/admin/queues.jsp

密碼可以在apache-activemq-5.15.4\conf\jetty-realm.properties設置:

輸入密碼後進入管理頁面

執行完QueueProducer發送端後,可以看到該隊列有0個接受端,總共有20條消息,其中0條消息已經處理了,20條消息尚未處理。

然後我們開兩個QueueConsumer接收端,再一次執行發送端後查看後臺

發送端控制檯打印:

接收端1控制檯打印:

接收端2控制檯打印:

可以看到我們起的兩個接收端服務幾乎平均的把消息接收了

再看一下管理頁面:

接收端從0個變成了2個,消息總數和處理的消息也從20變爲40。

主題和隊列在使用上一致,只不過主題使用Topic實現Destination接口。

除此之外,針對冗長和複雜的JMS代碼,我們還可以使用Spring提供的JMS模板JmsTemplate來創建連接,獲得會話,以及發送和接受消息;JmsTemplate還可以處理所有的拋出的JMSException異常。

五,幾個常見問題

1,消息確認機制

消息只有被確認之後,才認爲是被成功消費,然後消息纔會從隊列或主題中刪除。

客戶端接收消息 = 》 消息處理 =》 消息確認

四種確認機制:

(1)、Session.AUTO_ACKNOWLEDGE;客戶(消費者)成功從receive方法返回時,或者從MessageListener.onMessage方法成功返回時,會話自動確認消息,然後自動刪除消息.

(2)、Session.CLIENT_ACKNOWLEDGE;客戶通過顯式調用消息的acknowledge方法確認消息,。 即在接收端調用message.acknowledge();方法,否則,消息是不會被刪除的.

(3)、Session. DUPS_OK_ACKNOWLEDGE ;不是必須確認,是一種“懶散的”消息確認,消息可能會重複發送,在第二次重新傳送消息時,消息頭的JMSRedelivered會被置爲true標識當前消息已經傳送過一次,客戶端需要進行消息的重複處理控制。

(4)、 Session.SESSION_TRANSACTED;事務提交併確認。

2,丟消息怎麼辦?

    可以設置持久化消息,messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

    使用acknowledge()方法,客戶端返回確認信息。

3,事務消息

    創建回話Session使用transacted = true,事務消息在發送和接收後必須顯示的調用session.commit(),事務消息會自動確認。與設置的確認機制無關

3,消息接收方式

receive方法接收,一次即結束;使用監聽器接收,實現onMessage方法,作爲一個服務實時監測。

 

 

 

項目地址:https://gitee.com/uiaoadf/myActiveMQ

https://www.jianshu.com/p/b1a5acac15ef

《Spring 實戰》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章