ActiveMQ消息中間件學習

1.MQ

mq稱爲消息隊列或稱爲消息中間件。(利用高效可靠的消息傳遞機制進行與平臺無關的數據交流,並基於數據通信來進行分佈式系統的集成)

作用:

(1)降低系統的耦合度。

(2)異步請求模式。

(3)降低峯值流量的訪問,保護主業務功能。

實現過程:發送方把消息發送給消息服務器,消息服務器將消息存放在隊列/主題中,在合適的時候,消息服務器會將消息發送給接收方。在這個過程中,發送和接受是異步的,發送無需等待,而且發送方和接收方的生命週期也沒有必然關係。如果是在主題的模式下,可以完成一對多的通信,既讓一個消息有多個接收者。

2、linux安裝ActiveMq

(1) 官網下載 

  http://activemq.apache.org/components/classic/download/

(2)上傳壓縮包到/opt文件夾下

(3)解壓到/usr/local文件下下

(4)普通啓動  ./activemq start

(4-1)自定義日誌啓動  ./activemq start > 日誌位置

  ./activemq start > /usr/local/activeMq_log/run_activemq.log

(5)activeMq默認進程端口號 61616  前端web項目訪問端口 8161

(6)查詢ps -ef |grep activemq  |grep -v grep

(7)lsof -i : 61616

3、訪問activemq控制檯

(1)關閉linux防火牆或者配置白名單

          service iptables stop

(2)用戶名:admin 密碼:admin

4、java編碼MQ

(1)創建maven工程,引入activemq所需jar包

 <dependencies>
        <!-- activemq所需要的jar包配置-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.10</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.16</version>
        </dependency>

        <!-- broker 需要-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
 </dependencies>

(2)JMS消息結構

(3)java編碼簡易DEMO---生產者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 */
public class Demo01 {

    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";

    public static final String QUEUE_NAME = "queue_ldd_01";

    public static void main(String[] args) {

        //1創建一個鏈接工廠
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME,PASSWORD,ACTIVEMQ_URL);
        try {
            //2根據鏈接工廠創建鏈接
            Connection connection = connectionFactory.createConnection();
            //2-1 啓動鏈接
            connection.start();

            //3根據鏈接創建session對象
            //transacted 事務   Session.AUTO_ACKNOWLEDGE  簽收(自動簽收)
            Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

            //4創建消息類型 是隊列(queue)或者主題(topic)
            Queue queue = session.createQueue( QUEUE_NAME );

            //5創建消息的生產者
            MessageProducer producer = session.createProducer( queue );

            //6創建一個字符串消息
            TextMessage message = session.createTextMessage();
            message.setText( "這是一條字符串消息---queue" );

            //7生產者發送消息
            producer.send( message );

            //釋放資源
            producer.close();
            session.close();
            connection.close();

            System.out.println("消息發送完成");
        } catch (JMSException e) {
            e.printStackTrace();
        }

    }

}




Number Of Pending Messages :等待消費的消息 。【當前未出隊列的消息。等待消費的消息=總接收消息-總出隊列的消息】

Number Of Consumers :消費者數量。【消費者端的消費者數量】

Messages Enqueued :進隊消息數量。【包含出隊列的,只增加不減少】

Messages Dequeued  :出隊消息數。【消費者消費掉的消息數量】

(4)java編碼簡易DEMO---消費者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * 消費者
 */
public class ConsumersDemo01 {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String QUEUE_NAME = "queue_ldd_01";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();

        Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

        Queue queue = session.createQueue( QUEUE_NAME );
        //創建消費者
        MessageConsumer consumer = session.createConsumer( queue );

        while(true){

            /**
             * 拿不到textMessage就卡住無法向下執行  如果爲null可能就出現異常 跳出循環結束
             * receive();  持續等待
             * receive(longTime);設置等待時長,超時不再獲取消息 返回null
             */
            TextMessage textMessage = (TextMessage) consumer.receive();
            if(textMessage != null){
                System.out.println("消費消息----");
                System.out.println("消費內容:[]"+textMessage.getText());
            }else{
                break;
            }
        }

        consumer.close();
        session.close();
        connection.close();

    }
}

5、監聽方式消費消息

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * 消費者
 */
public class ConsumersDemo02 {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String QUEUE_NAME = "queue_ldd_01";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();

        Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

        Queue queue = session.createQueue( QUEUE_NAME );
        //創建消費者
        MessageConsumer consumer = session.createConsumer( queue );

        //監聽消息消費
        consumer.setMessageListener( new MessageListener() {
            @Override
            public void onMessage(Message message) {

                if(message != null && message instanceof TextMessage){

                    TextMessage textMessage = (TextMessage) message;
                    try {
                        System.out.println("監聽事件消費消息---");
                        System.out.println("消息內容[]"+textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        } );

        //保證控制檯不關閉
        System.in.read();
        consumer.close();
        session.close();
        connection.close();
    }
}

6、消費情況

兩種消費方式:

(1)同步阻塞方式(receive())

訂閱者或者接收者調用MessageConsumer的receive()方法來接收消息,receive()方法在能夠接收到消息之前(或超時之前)將一直阻塞。

(2)異步非阻塞方式(監聽器onMessage())

訂閱者或接收者通過MessageConsumer的setMessageListener(MessageListener listener)註冊一個消息監聽器。

當消息到達之後,系統自動調用監聽器MessageListener的onMessage(Message message)的方法。

隊列消費情況---------------------

一個生產者,生產6條消息,2個消費者依次開啓服務,消費消息數平分。

7、java編程topicDemo

(1)生產者將消息發佈到topic中,每個消息可以有多個消費者,屬於一對多關係。

(2)生產者和消費者之間有時間上的相關性,訂閱某個主題的消費者只能消費自它訂閱之後發佈的消息。

(3)生產者產生消息,topic不保存消息它是無狀態的,無人訂閱就生產消息,所產生的消息是廢消息。所有,先訂閱後發佈消息。

持久訂閱:JMS允許客戶創建持久訂閱,持久訂閱允許消費者消費它在未處於激活狀態時生產者發送的消息。

生產者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * 生產者
 */
public class TopicProducerDemo01 {

    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";

    public static final String TOPIC_NAME = "topic_ldd_01";

    public static void main(String[] args) {

        //1創建一個鏈接工廠
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME,PASSWORD,ACTIVEMQ_URL);
        try {
            //2根據鏈接工廠創建鏈接
            Connection connection = connectionFactory.createConnection();
            //2-1 啓動鏈接
            connection.start();

            //3根據鏈接創建session對象
            //transacted 事務   Session.AUTO_ACKNOWLEDGE  簽收(自動簽收)
            Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

            //4創建消息類型 是隊列(queue)或者主題(topic)
            Topic topic = session.createTopic( TOPIC_NAME );

            //5創建消息的生產者
            MessageProducer producer = session.createProducer( topic );


            //6創建一個字符串消息
            for( int i = 0; i < 7; i++ ) {
                TextMessage message = session.createTextMessage();
                message.setText( "這是一條字符串消息---topic" );
                //7生產者發送消息
                producer.send( message );
            }

            //釋放資源
            producer.close();
            session.close();
            connection.close();

            System.out.println("消息發送完成");
        } catch (JMSException e) {
            e.printStackTrace();
        }

    }

}




消費者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * 消費者
 */
public class TopicConsumersDemo01 {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String TOPIC_NAME = "topic_ldd_01";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();

        Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

        Topic topic = session.createTopic( TOPIC_NAME );

        //創建消費者
        MessageConsumer consumer = session.createConsumer( topic );

        while(true){

            TextMessage textMessage = (TextMessage) consumer.receive();
            if(textMessage != null){
                System.out.println("消費者1號--內容:[]"+textMessage.getText());
            }else{
                break;
            }
        }

        consumer.close();
        session.close();
        connection.close();

    }
}

8、Topic與Queue區別

  Queue Topic
工作模式 "負載均衡"模式,如果沒有消費者,消息不會丟棄,如果有多個消費者,消息只會被其中一條消費者消費,並且要求消費者ack信息 "訂閱發佈"模式,如果沒有訂閱者,消息會被丟棄,如果存在多個訂閱者,那麼這些訂閱者都會收到消息
有無狀態 Queue數據默認會在mq服務器上以文件形式保存。比如ActiveMq一般保存在&AMQ_HOME\data\kr-store\data下面 無狀態
傳遞完整性 消息不會丟失 如果沒有訂閱者,消息將被丟棄
處理效率 由於一條消息只對應一個消費者,所有就算消費者再多,性能也不會有明顯的降低。 由於一個消息對應多個訂閱者,隨着訂閱者增多,處理性能將會降低

9、JMS

Java Message Service(Java消息服務器是JavaEE中的一個技術)

Java消息服務器是指兩個應用程序之間進行異步通信的API,它爲標準消息協議和消費服務提供了一組通用接口,包括創建,發送,讀取消息等,用於支持Java應用程序的開發。在JavaEE中,當兩個應用程序使用JMS進行通信時,它們之間並不是直接相連的,而是通過一個共同的消息收發服務組件關聯起來,以達到解耦、異步、削峯的效果。

 

10、Message

消息頭:

JMSDestination:設置消息發送的目的地,主要指Queue和Topic。

JMSDeliveryMode:設置持久模式和非持久模式。

持久模式:消息應該被傳送一次僅僅一次。如果JMS提供者出現故障,該消息不會丟失,它會在服務器恢復之後再傳遞。

非持久消息:消息最多被傳遞一次,意味着服務器故障,該消息將永遠丟失。

JMSExpiration:設置消息在一定時間後過期,默認是永不過期。如果發送後,在消息過期時間之後消息還沒有被髮送到目的地,則改消息被清除。

JMSPriority:設置消息優先級,從0-9十個級別,0-4是普通消息,5-9是加急消息。JMS不要求MQ嚴格按照這十個優先級發送消息,但必須保證加急消息要先於普通消息到達。普通消息級別是4。

JMSMessageID:唯一識別每個消息的標識由MQ產生。

消息體:

封裝具體的消息數據。

5種消息體格式:

(1)TextMessage:普通字符串消息,包含一個String

(2)MapMessage:一個Map類型的消息,key爲String類型,而值爲Java的基本類型。

(3)BytesMessage:二進制數組消息,包含一個Byte[]

(4)StreamMessage:Java數據流消息,用標準流操作填充和讀取。

(5)ObjectMessage:對象消息,包含一個可序列化的Java對象。

消息屬性:

消息頭的擴展,K-V形式的數據。

set*Property(k,v)

11、JMS的可靠性

持久化Queue

注意:Queue默認是持久化消息。

(1)非持久化

         服務器宕機,消息丟失。producer.setDeliveryMode( DeliveryMode.NON_PERSISTENT );

(2)持久化

         服務器宕機,消息依然存在。producer.setDeliveryMode(DeliveryMode.PERSISTENT);

持久化Topic:

注意:設置爲持久化訂閱,需要啓動一次,等於向MQ註冊,消費者在線會立刻收到發佈的消息,消費者不在線,下次啓動時會收到消息。

topic消息發佈者(持久化)

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/28.
 * 持久化消息 生產者
 */
public class TopicProducerPersist {

    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String TOPIC_NAME = "topic_persist_01";

    public static void main(String[] args) throws JMSException {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(  USERNAME,PASSWORD,ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

        Topic topic = session.createTopic( TOPIC_NAME );

        //創建topic消息生產者
        MessageProducer producer = session.createProducer( topic );
        //設置爲持久化消息
        producer.setDeliveryMode( DeliveryMode.PERSISTENT );
        //連接
        connection.start();


        for(int i = 0; i < 3; i++){
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText( "我是持久化消息"+i );
            producer.send( textMessage );
        }


        session.close();
        connection.close();
    }
}

topic消息訂閱者(持久化)

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * 持久化訂閱者
 */
public class TopicConsumersPersist {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String TOPIC_NAME = "topic_persist_01";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD,ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();
        //設置訂閱者的名字
        connection.setClientID( "司大雕1號" );

        Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

        Topic topic = session.createTopic( TOPIC_NAME );

        //創建一個持久訂閱者
        TopicSubscriber subscriber = session.createDurableSubscriber( topic, "remark..." );

        connection.start();

        Message message = subscriber.receive();

        while (message != null){

            TextMessage textMessage = (TextMessage)message;

            System.out.println("接收持久化消息:"+textMessage.getText());

            message = subscriber.receive();
        }

        session.close();
        connection.close();


    }
}

 

 

12、事務

手動提交事務,確保程序的可靠性。若在消息發送期間出現異常,可手動回滾。

事務消息生成者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/11/9.
 * <p>
 * 事務發送消息
 * 確保程序的可靠性
 * 出現異常可手動回滾
 */
public class TransactionProducer {

    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String TOPIC_NAME = "transaction_queue";


    public static void main(String[] args) {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
        Connection connection = null;
        Session session = null;
        try {
            connection = connectionFactory.createConnection();
            connection.start();

            session = connection.createSession( true, Session.AUTO_ACKNOWLEDGE );

            Queue queue = session.createQueue( TOPIC_NAME );

            MessageProducer producer = session.createProducer( queue );

            TextMessage textMessage = session.createTextMessage();

            for (int i = 0; i < 3; i++) {
                textMessage.setText( "我是事務消息" + i );
                producer.send( textMessage );
            }

            //手動提交事務,若沒有提交事務 消息無法存在消息服務器中
            session.commit();
        } catch (JMSException e) {
            e.printStackTrace();
            //出現異常手動回滾
            try {
                session.rollback();
            } catch (JMSException e1) {
                e1.printStackTrace();
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

 

事務消息接收者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * /**
 * 接收事務消息
 * <p>
 * 如果已非事務模式接收 消息會被消費一次
 * <p>
 * 如果已事務模式接收 消息會被消息一次 但必須手動 commit提交。
 * <p>
 * 若不commit手動提交,消息不會確認消息,可重複消費。
 */
public class TransactionConsumers {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String QUEUE_NAME = "transaction_queue";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();

        /**
         * 接收事務消息
         *
         * 如果已非事務模式接收 消息會被消費一次
         *
         * 如果已事務模式接收 消息會被消息一次 但必須手動 commit提交。
         *
         * 若不commit手動提交,消息不會確認消息,可重複消費。
         *
         */

        Session session = connection.createSession( true, Session.AUTO_ACKNOWLEDGE );

        Queue queue = session.createQueue( QUEUE_NAME );
        //創建消費者
        MessageConsumer consumer = session.createConsumer( queue );

        while (true){
            TextMessage textMessage = (TextMessage) consumer.receive(4000L);
            if(textMessage != null){
                System.out.println(textMessage.getText());
            }else{
                break;
            }
        }
        //手動接收消息 提交事務
        session.commit();
        session.close();
        connection.close();
    }
}

13、Acknowledge(簽收)

非事務簽收

消息生產者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/11/9.
 * <p>
 * 非事務下消息發送者,設置簽收模式 手動模式
 */
public class NotTransactionProducerAck {

    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String TOPIC_NAME = "not_transaction_queue_ack";


    public static void main(String[] args) {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
        Connection connection = null;
        Session session = null;
        try {
            connection = connectionFactory.createConnection();
            connection.start();

            session = connection.createSession( false, Session.CLIENT_ACKNOWLEDGE );

            Queue queue = session.createQueue( TOPIC_NAME );

            MessageProducer producer = session.createProducer( queue );

            TextMessage textMessage = session.createTextMessage();

            for (int i = 0; i < 3; i++) {
                textMessage.setText( "ack手動簽收消息" + i );
                producer.send( textMessage );
            }
        } catch (JMSException e) {
            e.printStackTrace();
            //出現異常手動回滾
            try {
                session.rollback();
            } catch (JMSException e1) {
                e1.printStackTrace();
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

 

消息接收者

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 * 非事務手動簽收消息
 */
public class NotTransactionConsumersAck {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String QUEUE_NAME = "not_transaction_queue_ack";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();

        Session session = connection.createSession( false, Session.CLIENT_ACKNOWLEDGE );

        Queue queue = session.createQueue( QUEUE_NAME );
        //創建消費者
        MessageConsumer consumer = session.createConsumer( queue );

        while (true){
            TextMessage textMessage = (TextMessage) consumer.receive(4000L);
            if(textMessage != null){
                System.out.println(textMessage.getText());
                //手動簽收消息
                textMessage.acknowledge();
            }else{
                break;
            }
        }
        session.close();
        connection.close();
    }
}

事務簽收

以事務方式發送消息,接收者必須手動commit確認消息已經消費,否則會存在重複消費的問題。(事務模式下消息消費與簽收類型無關,都必須手動提交事務)

package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/11/9.
 * <p>
 * 事務下消息發送者,設置簽收模式 手動模式
 */
public class TransactionProducerAck {

    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String TOPIC_NAME = "transaction_queue_ack_lmr";


    public static void main(String[] args) {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );
        Connection connection = null;
        Session session = null;
        try {
            connection = connectionFactory.createConnection();
            connection.start();

            session = connection.createSession( true, Session.CLIENT_ACKNOWLEDGE );

            Queue queue = session.createQueue( TOPIC_NAME );

            MessageProducer producer = session.createProducer( queue );

            TextMessage textMessage = session.createTextMessage();

            for (int i = 0; i < 3; i++) {
                textMessage.setText( "ack手動簽收消息" + i );
                producer.send( textMessage );
            }

            session.commit();

        } catch (JMSException e) {
            e.printStackTrace();
            //出現異常手動回滾
            try {
                session.rollback();
            } catch (JMSException e1) {
                e1.printStackTrace();
            }
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}








package com.ldd.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/10/17.
 *
 * 事務模式下,不區分消息的簽收類型,
 * 都必須手動commit 確認消息消費。
 */
public class TransactionConsumersAck {
    public static final String ACTIVEMQ_URL = "tcp://192.168.182.129:61616";
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "admin";
    public static final String QUEUE_NAME = "transaction_queue_ack_lmr";

    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USERNAME, PASSWORD, ACTIVEMQ_URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();

        Session session = connection.createSession( true, Session.CLIENT_ACKNOWLEDGE );

        Queue queue = session.createQueue( QUEUE_NAME );
        //創建消費者
        MessageConsumer consumer = session.createConsumer( queue );

        while (true){
            TextMessage textMessage = (TextMessage) consumer.receive(4000L);
            if(textMessage != null){
                System.out.println(textMessage.getText());
                //事務模式下,不區分消息的簽收類型都必須手動commit確認消息消費。
                //textMessage.acknowledge();
            }else{
                break;
            }
        }
        //提交事務
        session.commit();
        session.close();
        connection.close();
    }
}

14、Broker

相當於一個ActiveMq服務器實例。

Broker實現了用代碼形式啓動ActiveMQ將MQ嵌入到JAVA代碼中,以便隨時啓動。

activeMQ指定配置文件啓動:

package com.ldd.activemq;

import org.apache.activemq.broker.BrokerService;

/**
 * Created by ldd on 2019/11/10.
 */
public class Broker {

    public static void main(String[] args) throws Exception {

        BrokerService brokerService = new BrokerService();
        brokerService.setUseJmx( true );
        brokerService.addConnector( "tcp://localhost:61616" );
        brokerService.start();
    }
}

15、Spring整合ActiveMQ

(1)Maven POX.XML

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-pool</artifactId>
            <version>5.15.10</version>
        </dependency>
        <!-- Spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>

(2)Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


    <!-- 開啓自動掃包-->
    <context:component-scan base-package="com.ldd.*" />

    <!-- 連接對象-->
    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">

        <property name="connectionFactory">
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="tcp://192.168.182.129:61616"/>
            </bean>
        </property>
        <property name="maxConnections" value="100" />

    </bean>


    <!--  隊列-->
    <bean id="activeMQQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queur"/>
    </bean>

    <!-- spring提供JMS工具類,它可以進行消息發送、接收 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="pooledConnectionFactory"/>
        <property name="defaultDestination" ref="activeMQQueue"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
        </property>
    </bean>


</beans>

(3)隊列

               隊列----生成者

package com.ldd.spring;

import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;

/**
 * Created by ldd on 2019/11/10.
 */
@Service
public class Spring_produce {

    @Autowired
    private JmsTemplate jmsTemplate;

    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" );

        Spring_produce spring_produce = (Spring_produce) applicationContext.getBean( "spring_produce" );

        spring_produce.jmsTemplate.send( new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage textMessage = session.createTextMessage();
                textMessage.setText( "Spring整合ActiveMq消息測試---" );
                return textMessage;
            }
        } );

        System.out.println("消息發送完畢");
    }

}

       隊列----消費者

package com.ldd.spring;

import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

/**
 * Created by ldd on 2019/11/10.
 */
@Service
public class Spring_consume {

    @Autowired
    private JmsTemplate jmsTemplate;

    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" );

        Spring_consume Spring_consume = (Spring_consume) applicationContext.getBean( "spring_consume" );

        String message = (String) Spring_consume.jmsTemplate.receiveAndConvert();

        System.out.println(message);
    }

}

(4)主題

    <!-- 主題-->
    <bean id="activeMQTopic" class="org.apache.activemq.command.ActiveMQTempTopic">
        <constructor-arg index="0" value="spring-active-topic"/>
    </bean>

(5)Spring配置監聽器

配置消費者監聽器,配置完成後只需要啓動生產者,便可自動監聽接收消息。

    <!-- 監聽器-->
    <bean id="jmsContainer" 
        class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="pooledConnectionFactory"/>
        <property name="destination" ref="activeMQQueue"/>
        <property name="messageListener" ref="myMessageListener"/>
    </bean>
package com.ldd.spring;

import org.springframework.stereotype.Component;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 * Created by ldd on 2019/11/11.
 */
@Component
public class MyMessageListener implements MessageListener{
    @Override
    public void onMessage(Message message) {

        if(message instanceof TextMessage){

            try {
                String text = ((TextMessage) message).getText();
                System.out.println(text + "Spring監聽消息消費");
            } catch (JMSException e) {
                e.printStackTrace();
            }

        }

    }
}

16、SpringBoot整合ActiveMq生產者--Queue

pom.xml文件引入activemq

<dependency>
  <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-activemq</artifactId>
	<version>2.1.5.RELEASE</version>
</dependency>

application.yml文件

server:
  port: 9999

#activeMq服務器地址
spring:
  activemq:
    broker-url: tcp://192.168.182.129:61616
    user: admin
    password: admin


  #設置消息模式   false == queur  true == topic  默認是false
  jms:
    pub-sub-domain: false

  #自定義queue的名稱
  myqueue: spring-boot-queue




java文件

package com.ldd.activemq.config;

import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;

import javax.jms.Queue;

/**
 * Created by ldd on 2019/12/4.
   配置類
 */
@Component
@EnableJms
public class ConfigBean {

    /* queue名*/
    @Value( "${myqueue}" )
    private String queueStr;


    @Bean
    public Queue queue(){

      return new ActiveMQQueue( queueStr );
    }
}




package com.ldd.activemq.produce;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;

import javax.jms.Queue;
import java.util.UUID;

/**
 * Created by ldd on 2019/12/4.
   生產者
 */
@Component
public class Queue_Produce {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Autowired
    private Queue queue;


    public void send_message(){

        jmsMessagingTemplate.convertAndSend( queue,"SpringBoot-queue消息"+ UUID.randomUUID() );
        System.out.println("消息發送完成");
    }

}

17、SpringBoot整合ActiveMq消費者--Queue

package com.ldd.activemq.consumer;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

import javax.jms.TextMessage;

/**
 * Created by ldd on 2019/12/4.
 * 消費者
 */
@Component
public class Queue_Consumer {
    /**
     *  @JmsListener 容器自動監聽queue消息
     *  @param message
     *  @throws Exception
     */
    @JmsListener( destination = "${myqueue}")
    public void receiveMessage(TextMessage message) throws Exception{

        System.out.println("接收消息服務器消息:"+message.getText());
    }

}

18、SpringBoot整合ActiveMq生產者--Topic

server:
  port: 7777

#activeMq\u670D\u52A1\u5668\u5730\u5740
spring:
  activemq:
    broker-url: tcp://192.168.182.129:61616
    user: admin
    password: admin
  jms: #消息類型爲主題
    pub-sub-domain: true



  #\u8BBE\u7F6E\u6D88\u606F\u6A21\u5F0F   false == queur  true == topic  \u9ED8\u8BA4\u662Ffalse
#jms:
 ## pub-sub-domain: false

  #\u81EA\u5B9A\u4E49queue\u7684\u540D\u79F0
#myqueue: spring-boot-queue



#主題消息名
mytopic: spring-boot-topic



package com.ldd.activemq.config;

import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.jms.Topic;

/**
 * Created by ldd on 2019/12/6.
 */
@Component
public class TopicConfigBean {


    @Value( "${mytopic}" )
    private String topicName;


    @Bean
    public Topic topic(){

        return new ActiveMQTopic( topicName );
    }

}
package com.ldd.activemq.produce;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.jms.Topic;
import java.util.UUID;

/**
 * Created by ldd on 2019/12/6.
 */
@Component
public class Topic_Produce {


    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;


    @Autowired
    private Topic topic;


    @Scheduled(fixedDelay = 3000)
    public void sendTopicMessage(){

        jmsMessagingTemplate.convertAndSend( topic,"主題消息:"+ UUID.randomUUID() );

    }
}

19、SpringBoot整合ActiveMq消費者--Topic

package com.ldd.activemq.consumer;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

import javax.jms.TextMessage;

/**
 * Created by ldd on 2019/12/6.
 */
@Component
public class Topic_Consumer {

    @JmsListener( destination = "${mytopic}")
    public void receiveTopic(TextMessage message) throws  Exception{

        System.out.println("主題消息接收:"+message.getText());
    }

}

20、ActiveMq傳輸協議

協議 描述
TCP 默認協議,性能相對可以
NIO 基於TCP協議之上的,進行了擴展和優化,具有更好的擴展性。
UDP 性能比TCP好,但是不可靠
SSL 安全鏈接
HTTP(S) 基於HTTP或者HTTPS
VM VM本身不是協議,當客戶端和代理在同一個VM中運行時,它們之間需要通信,但不想佔用網絡通道,而是直接通信,可以使用VM
   
   

 

默認配置的協議爲以下5中:openwire、amqp、stomp、mqtt、ws。

openwire默認使用的傳輸協議tcp協議

 <transportConnectors>
          
            <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?    
             maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="amqp" uri="amqp://0.0.0.0:5672? 
             maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="stomp" uri="stomp://0.0.0.0:61613? 
             maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883? 
             maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="ws" uri="ws://0.0.0.0:61614? 
             maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

</transportConnectors>

(1)TCP協議

1、這是默認的Broker配置,TCP的Client默認端口號:61616

2、網絡傳輸數據前,通過一個叫wire protocol來序列化成字節流數據。默認情況下,ActiveMq把wire protocol叫做OpenWire,它的目的是促使網絡上的效率和數據快速交互。

3、Tcp連接的URL形式:tcp://hostname:prot?key=value&key=value,後面參數可選

4、Tcp傳輸的優點:

      Tcp協議傳輸可靠性高,穩定性強。

      高效性:字節流方式傳輸、效率高。

     有效性、可用性:應用廣泛,支持任何平臺。

(2)NIO協議

NIO協議與TCP協議類似但NIO協議更側重於底層訪問操作。它允許開發人員對同一資源可有更多的client調用和服務端有更多的負載。

使用場景:

可能有大量的Client去連接Broker上,一般情況下,大量的Client去連接Broker是被操作系統的線程限制。因此NIO的實現比TCP需要更少的線程去運行,所以建議使用NIO。

NIO連接的URL形式:nio://hostname:port?key=value

 

(3)amqp協議

支持版本5.8++

Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開發標準,爲面向消息的中間件設計,基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同開發語言等條件限制。

(4)stomp協議

STOMP流文本定向消息協議,是一種MOM面向消息的中間件設計的簡單文本協議。

(5)MQTT協議

MQTT 消息隊列遙測傳輸是IBM開發的一個即使通訊協議,有可能成爲物聯網的重要組成部分。該協議支持所有平臺,幾乎可以把所有聯網物品和外部連接起來,被用來當做傳感器和致動器的通信協議。

(6)ws

WebSockets協議。

21、ActiveMq之NIO傳輸協議

設置支持NIO網絡IO模型,並且自動支持多協議傳輸。(默認是TCP傳輸)

(1)修改activemq.xml

(2)重啓actviemq服務

(3)生產者、消費者demo

package com.ldd.activemq.nio.produce;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/12/10.
 * nio協議 生產者
 */
public class Nio_Produce {


    private static final String URL = "nio://192.168.182.129:61618";
    private static final String USER = "admin";
    private static final String PASSWORD = "admin";
    private static final String NIO_QUEUE = "nio_queue";


    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USER,PASSWORD,URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();


        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

        Queue queue = session.createQueue(NIO_QUEUE);

        MessageProducer producer = session.createProducer( queue );

        for(int i = 0;i < 3; i ++){

            TextMessage message = session.createTextMessage();
            message.setText( "nio隊列消息--->"+i );
            producer.send( message );
        }
    }


}





package com.ldd.activemq.nio.consumer;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Created by ldd on 2019/12/10.
 *
 * nio   消費者
 */
public class Nio_Consumer {

    private static final String URL = "nio://192.168.182.129:61618";
    private static final String USER = "admin";
    private static final String PASSWORD = "admin";
    private static final String NIO_QUEUE = "nio_queue";


    public static void main(String[] args) throws Exception {

        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( USER,PASSWORD,URL );

        Connection connection = connectionFactory.createConnection();

        connection.start();


        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

        Queue queue = session.createQueue(NIO_QUEUE);

        MessageConsumer consumer = session.createConsumer( queue );


        while(true){

            TextMessage message = (TextMessage) consumer.receive();
            if (message == null){
                break;
            }else{
                System.out.println("接收到消息:->"+message.getText());
            }
        }
    }



}

22、ActiveMQ消息持久化理論

事務、持久化消息類型、簽收都是MQ內部的持久化操作。外部持久化消息到文件或數據庫中存儲。

ActiveMQ的消息持久化機制有JDBC、AMQ、KahaDBLevelDB,無論使用哪種持久化方式,消息的存儲邏輯都是一致的。

發送者發送消息,消息中心首先將消息存儲本地數據文件內存數據庫或者遠程數據庫等再試圖將消息發送給接收者,成功則將消息從存儲中刪除,失敗則繼續發送。

消息中心啓動以後首先要檢查指定的存儲位置,如果有未發送成功的消息,則把消息發送出去。

AMQ持久化:ActiveMQ5.3之前適用,AMQ是一種以文件存儲形式,它具有寫入速度快容易被恢復的特點。消息存儲在一個文件中,文件默認大小是32M,當一個存儲文件中的消息已經被全部消息,那麼這個文件標記被可刪除。在下一個清楚階段,這個文件將被刪除。

KahaDB持久化:基於日誌文件,從ActiveMQ5.4開始默認的持久化插件。

KahaDB原理:KahaDB是目前默認的存儲方式,可用於任何場景,提高了性能和恢復能力,消息存儲使用一個事務日誌和一個索引文件來存儲它所有的地址。KahaDB是一個專門針對消息持久化的解決方案,它對典型的消息使用模型進行了優化。數據被追加到data logs中。當不在需要log文件中的數據的時候,log文件會被丟棄。

<!--        
            activemq.xml配置中,持久化默認使用的是kahaDB

            Configure message persistence for the broker. The default persistence
            mechanism is the KahaDB store (identified by the kahaDB tag).
            For more information, see:

            http://activemq.apache.org/persistence.html
        -->
        <persistenceAdapter>
            <kahaDB directory="${activemq.data}/kahadb"/>
        </persistenceAdapter

1、db-<number>.log KahaDB存儲消息到數據記錄中,文件命名爲db-<number>.log。當數據文件已滿時,一個新的文件會隨之創建,number數值也會隨之遞增。當不再有引用到數據文件中的任何消息時,文件會被刪除或歸檔。

2、db.data該文件包含了持久化的BTree索引,索引了消息數據記錄中的消息,它是消息的索引文件,本質上是B-Tree(B樹),使用B-Tree作爲索引執行db-<number>.log裏面存儲的消息。

3、db.free當前db.data文件裏哪些頁面是空閒的,文件具體內容是所有空閒頁的ID。

4、db.redo用來進行消息恢復,如果KahaDB消息存儲在強制退出後啓動,用於恢復BTree索引。

5、lock文件鎖,表示當前獲得kahaDb讀寫權限的broker。

LevelDB持久化:從ActiveMQ5.8以後引進的,它和kahaDB非常相似,也是基於文件的本地數據庫存儲形式,但是它提供比KahaDB更快的持久性。但它不使用自定義B-Tree實現來索引預習日誌,而是使用基於LevelDB的索引。

 

23、ActiveMQ配置JDBC持久化

(1)上傳Mysql.jar包到activemq lib目錄下

 

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