ActiveMQ你學會了嗎

ljx1129071273發表於2020-11-27

ActiveMQ學習筆記

ActiveMQ與RocketMQ之間的區別

該文章讀完你會了解

  • api傳送與接收
  • MQ高可用
  • MQ的叢集和容錯配置
  • MQ的持久化
  • 延時傳送和定時投遞
  • 簽收機制
  • Spring整合

主要作用就是解耦 非同步 削峰

大致流程是:

傳送者把訊息傳送給訊息伺服器,訊息伺服器把訊息存放在佇列/主題中,在合適的時候 訊息伺服器會將訊息轉發給接受者 在這個過程中傳送和接受是非同步的 也就是傳送無需等待 而且傳送者和接受者的生命週期沒有必然的關係 尤其是在釋出p/訂閱sub的模式下 也可以實現一對多的通訊 即讓一個訊息有多個接受者

能夠實現的功能

  • 主要的功能就是 實現高可用 高效能 可伸縮 易用 和安全的企業
  • 非同步訊息的消費和處理
  • 控制訊息的消費順序
  • 可以和Spring/SpringBoot整合 簡化程式碼
  • 配置叢集容錯的MQ叢集

知識點: ps -ef | grep activemq 或者使用 netstat -anp | grep 埠 或者 lsof -i:61616

安裝activemq

  • 下載apache-activemq-5.15.13-bin.tar
  • 解壓 拷貝到單獨的檔案下面
  • 進入到bin目錄下面 ./activemq start進行執行 ./activemq stop關閉 預設埠61616

測試Activemq的demo

1、新增配置

<!--activemq所需要的jar包-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.13</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.16</version>
        </dependency>
<!--下面是junit/long4j的基礎配置-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <!--Brock需要的jar包-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.7.0</version>
        </dependency>
        <!--ActiceMQ對JMS的支援  整合Spring和ActiveMQ-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <!--ActiveMQ所需要的pool包配置-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-pool</artifactId>
            <version>5.15.9</version>
        </dependency>
        <!--Spring-aop等相關的jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>



        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.6</version>
        </dependency>

在點對點的訊息傳遞過程中 目的地被稱為佇列(queue)

在釋出訂閱訊息傳遞中 目的地被稱為主題(topic)

在這裡插入圖片描述

佇列模式

生產者:

  • 定義activeMq的地址和佇列的名字
  • 建立連線工廠 開啟非同步傳送 通過連線工廠獲取連線
  • 建立會話 第一個引數事務 我二個引數簽收
  • 建立訊息的生產者 傳送訊息
    • 建立訊息
    • 設定訊息的請求頭
    • 傳送訊息
  • 關閉生產者 會話 連線
/**
 * 建立生產者
 */
public class JmsQueue {
    public static final String ACTIVE_MQ="tcp://192.168.120.32:61616";
    public static final String QUEUE_NAME="queue01";
    public static void main(String[] args) throws JMSException {
        //建立連線工廠,按照給定的url地址  採用預設的使用者名稱和密碼
        ActiveMQConnectionFactory mq=new ActiveMQConnectionFactory(ACTIVE_MQ);
        //開始非同步傳送
        mq.setUseAsyncSend(true);
        //通過連線工廠  獲取連線connection
        Connection connection = mq.createConnection();
        connection.start();

        //建立會話session
        //第一個:事務  第二個:簽收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //建立目的地
        /**
         * 生產者將訊息釋出到Queue上面  每個訊息有一個消費者   屬於1:1的過程
         */
        Queue queue = session.createQueue(QUEUE_NAME);
        //建立訊息的生產者
        //MessageProducer producer = session.createProducer(queue);
        ActiveMQMessageProducer producer = (ActiveMQMessageProducer)session.createProducer(queue);
        //producer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //通過訊息生產者生產3條訊息  傳送到mq的佇列裡面
        for (int i=1;i<=6;i++){
            //建立訊息
            TextMessage message = session.createTextMessage("message" + i);
            //設定一個請求頭
            message.setJMSMessageID(UUID.randomUUID().toString()+"--->");
            //通過producer傳送給mq
            //producer.send(message);
            final String msgID=message.getJMSMessageID();
            producer.send(message, new AsyncCallback() {
                //就可以知道那些訊息失敗那些訊息成功
                public void onSuccess() {
                    System.out.println(msgID+"成功");
                }

                public void onException(JMSException e) {
                    System.out.println(msgID+"失敗");
                }
            });
        }
        producer.close();//關閉資源
        session.close();
        connection.close();
        System.out.println("訊息傳送到MQ完成");
        //執行上面的步驟  只是生產者將資訊放入到佇列裡面
        //消費者並沒有進行消費
    }
}

對於消費者來說

  • 前三步與生產者一樣

  • 建立消費者

  • 同步阻塞的方式接受資訊

    同步阻塞方式:receive()
            訂閱者或者接受者的呼叫MessageConsumer的receive方法來接受資料,receive方法能夠接受到資料之前(或者一直處於阻塞的狀態)
            while (true){
                //Message receive = consumer.receive();消費者一直處於等待狀態
                TextMessage receiveMessage = (TextMessage)consumer.receive(3000);//一百ms之後不消費就走了
                if (receiveMessage!=null){
                    System.out.println("****消費者接收到訊息****"+receiveMessage.getText());
                }else break;
            }
            consumer.close();
            session.close();
            connection.close();
            System.out.println("接受完成");
    

    通過監聽

    /**
             * 通過監聽的方式
             * 非同步非阻塞的方式(監聽onMessage())
             * 訂閱者或者接受者通過MessageConsumer的setMessageListener(MessageListener listener)註冊一個訊息註冊器
             * 當訊息到達之後  系統自動呼叫監聽器MessageListener的onMessage(Message message)方法
             */
            consumer.setMessageListener(new MessageListener() {
                public void onMessage(Message message) {
                    if (message!=null && message instanceof TextMessage){
                        TextMessage textMessage=(TextMessage) message;
                        try {
                            System.out.println("textMessage"+textMessage.getText());
                        } catch (JMSException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            System.in.read();//保證控制檯不滅  防止還沒有消費就關了
            consumer.close();
            session.close();
            connection.close();
    

在這裡插入圖片描述

同步阻塞(receive())和非同步非阻塞方式地對比

同步阻塞:

訂閱者或接受者呼叫MessageConsumer的receive方法來接受訊息 receive方法在能夠接受到訊息之前將一直處於阻塞的狀態

非同步非阻塞方式

訂閱者或者接受者通過MessageConsumer的setMessageListener(MessageListener listener)註冊一個訊息監聽器,當訊息到達之後 系統自動呼叫監聽器MessageListener的onMessage方法

點對點訊息傳遞的特點:

(1)每個訊息只能有一個消費者,類似1對1的關係。好比個人快遞自己領取自己的。
(2)訊息的生產者和消費者之間沒有時間上的相關性。無論消費者在生產者傳送訊息的時候是否處於執行狀態,消費者都可以提取訊息。好比我們的傳送簡訊,傳送者傳送後不見得接收者會即收即看。
(3)訊息被消費後佇列中不會再儲存,所以消費者不會消費到已經被消費掉的訊息。

釋出訂閱模式

  1. 生產者將訊息釋出到topic中 每個訊息可以有多個消費者 屬於1:n的關係(一條訊息可以讓多個消費者同時接受到)
  2. 生產者和消費者之間有時間上的相關性 消費者只能消費在他訂閱之後的訊息
  3. 生產者生產的時候 topic不儲存訊息 他是無狀態地不落地的 假如無人訂閱就去生產 那麼就是一條廢訊息 所以一般先啟動消費者在啟動生產者
  4. 類似微信公眾號
/**
 * 釋出者
 */
public class JmsTopic {
    public static final String ACTIVE_MQ="tcp://192.168.120.132:61616";
    public static final String TOPIC_NAME="topic01";
    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory mq = new ActiveMQConnectionFactory(ACTIVE_MQ);
        Connection connection = mq.createConnection();
        connection.start();
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Topic topic = session.createTopic(TOPIC_NAME);
        MessageProducer producer = session.createProducer(topic);
        //非持久化
        producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
        for (int i=1;i<=3;i++){
            TextMessage textMessage = session.createTextMessage("Topic" + i);
            textMessage.setStringProperty("c1","v1");
            //textMessage.setJMSDeliveryMode();
            producer.send(textMessage);

        }
        producer.close();
        session.close();
        connection.close();
        System.out.println("傳送完成");
    }
    /**
     * 生產者將訊息傳送到topic上面  每一個訊息可以有多個消費者  屬於1:N的過程
     * 某一個主題的消費者只能夠訂閱在他之後釋出的訊息
     * 所以一般來說是先啟動消費者在啟動生產者
     */

}
/**
 * 訂閱者
 */
public class JmsConsumer1 {
    public static final String ACTIVE_MQ="tcp://192.168.120.132:61616";
    public static final String TOPIC_NAME="topic01";
    public static void main(String[] args) throws JMSException, IOException {
        ActiveMQConnectionFactory mq=new ActiveMQConnectionFactory(ACTIVE_MQ);
        //通過連線工廠  獲取連線connection
        final Connection connection = mq.createConnection();
        connection.start();

        //建立會話session
        //第一個:事務  第二個:簽收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //建立目的地
        /**
         * 生產者將訊息釋出到Queue上面  每個訊息有一個消費者   屬於1:1的過程
         */
        Topic topic = session.createTopic(TOPIC_NAME);
        //建立消費者
        final MessageConsumer consumer = session.createConsumer(topic);
        /**
         * 通過監聽的方式
         * 非同步非阻塞的方式(監聽onMessage())
         * 訂閱者或者接受者通過MessageConsumer的setMessageListener(MessageListener listener)註冊一個訊息註冊器
         * 當訊息到達之後  系統自動呼叫監聽器MessageListener的onMessage(Message message)方法  這樣就不用先啟動消費者在啟動監聽者了
         */
        consumer.setMessageListener(new MessageListener() {
            public void onMessage(Message message) {
                if (message!=null && message instanceof TextMessage){
                    TextMessage textMessage=(TextMessage) message;
                    try {
                        System.out.println("訊息屬性--->"+textMessage.getStringProperty("c1"));
                        //System.out.println("接收Topic的資訊"+textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        System.in.read();//保證控制檯不滅  防止還沒有消費就關了
        consumer.close();
        session.close();
        connection.close();
        /**
         * 每一個消費者都有3條訊息   先啟動消費者在啟動生產者
         */
    }
}

兩種模式詳細對比

Queue

  • 如果當前沒有消費者 訊息不會丟棄 如果有多個消費者 那麼一條訊息也只能傳送給一個消費者並且要求消費者ack訊息
  • 因為消費者再多 訊息也只能傳送給一個 所以效能不會有明顯的降低
  • 佇列的資料會在mq伺服器上以檔案的形式進行儲存

Topic

  • 如果沒有訂閱者 訊息會被丟棄 如果有多個訂閱者 那麼這些訂閱者都會收到訊息
  • 資料不會儲存
  • 由於訊息要按照訂閱者的數量進行復制,所以處理效能會隨著訂閱者的增加而明顯降低,並且還要結合不同訊息協議自身的效能差異

JMS

JavaMessageService(Java訊息服務是JavaEE中的一個技術)

Java訊息服務指的是兩個應用程式之間進行非同步通訊的API,它為標準協議和訊息服務提供了一組通用介面,包括建立、傳送、讀取訊息等,用於支援Java應用程式開發。在JavaEE中,當兩個應用程式使用JMS進行通訊時,它們之間不是直接相連的,而是通過一個共同的訊息收發服務元件關聯起來以達到解耦/非同步削峰的效果

JMS的組成結構和特點

  • JMS Provider 實現JMS介面和規範的訊息中介軟體,也就是我們說的MQ伺服器
  • JMS Producter 訊息生產者,建立和傳送JMS訊息的客戶端應用
  • JMSConsumer 訊息消費者,接收和處理JMS訊息的客戶端應用
  • JMS Message
    • 訊息頭
    • JMSDestination訊息傳送的目的地,主要是指Queue和Topic
    • JMSDeliveryMode 設定這個訊息是持久的還是非持久的一條永續性的訊息:應該被傳送“一次僅僅一次”,這就意味著如果JMS提供者出現故障,該訊息並不會丟失,它會在伺服器恢復之後再次傳遞。一條非持久的訊息:最多會傳遞一次,這意味著伺服器出現故障,該訊息將會永遠丟失。
    • JMSExpiration 設定過期時間 預設是永不過期的 等於Destination的send方法中的timeToLive值加上傳送時刻的GMT時間值。如果timeToLive值等於0,則JMSExpiration被設為0,表示該訊息永不過期。如果傳送後,在訊息過期時間之後還沒有被髮送到目的地,則該訊息被清除。
    • JMSMessageID唯一標識每個訊息的標識由MQ產生
    • JMSPriority 設定優先順序訊息優先順序,從0-9十個級別,O-4是普通訊息5-9是加急訊息。JMS不要求MQ嚴格按照這十個優先順序傳送訊息但必須保證加急訊息要先於普通訊息到達。預設是4級。

訊息屬性

主要的兩種

  • TxtMessage 普通字串訊息 包含一個String
  • MapMessage 一個Map型別的訊息 Key為String型別而值為Java基本型別
  • 要求傳送和接受的訊息體型別必須是一致的

訊息體

識別 / 去重 /重點標註等操作非常有用的方法

例如message.setStringProperty/setString等設定訊息體 在接收方getStringProperty來接受訊息體中的資訊

訊息的可靠性(重點)

永續性

  • 持久化messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT)當伺服器當機訊息依然存在
  • 非持久化 messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT) 當伺服器當機 訊息不存在
  • 佇列模式預設是持久化的

這裡主要講解主題模式 佇列模式不在描述

消費者
public static final String ACTIVE_MQ="tcp://192.168.120.32:61616";
    public static final String TOPIC_NAME="topic01";
    public static void main(String[] args) throws JMSException, IOException {
        System.out.println("消費者1");
        ActiveMQConnectionFactory mq=new ActiveMQConnectionFactory(ACTIVE_MQ);
        //通過連線工廠  獲取連線connection
        Connection connection = mq.createConnection();
        connection.setClientID("li");//設定Id
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Topic topic = session.createTopic(TOPIC_NAME);//設定目的地
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark...");//開啟持久化
        connection.start();
        Message message = topicSubscriber.receive();
        while (message!=null){
            TextMessage textMessage=(TextMessage)message;
            System.out.println("***收到持久化的topic"+textMessage.getText());
            message = topicSubscriber.receive(1000);
        }
        System.in.read();//保證控制檯不滅  防止還沒有消費就關了
        session.close();
        connection.close()}
/**
 * 訂閱模式
 * 生產者
 */
public class JmsTopic_Persit {
    public static final String ACTIVE_MQ="tcp://192.168.120.32:61616";
    public static final String TOPIC_NAME="topic01";
    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory mq = new ActiveMQConnectionFactory(ACTIVE_MQ);
        Connection connection = mq.createConnection();

        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Topic topic = session.createTopic(TOPIC_NAME);
        MessageProducer producer = session.createProducer(topic);
        //持久化
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);//開啟持久化
        connection.start();
        for (int i=1;i<=3;i++){
            TextMessage textMessage = session.createTextMessage("Topic" + i);
            //textMessage.setStringProperty("c1","v1");
            //textMessage.setJMSDeliveryMode();
            producer.send(textMessage);

        }
        producer.close();
        //session.commit();如果是true的話
        session.close();
        connection.close();
        System.out.println("傳送完成");
    }
}

一般對於事務而言基本在生產者使用 對於簽收在消費者使用

事務 :

什麼是事務?

事務的四大特性?

事務的隔離級別?

如果session在建立的時候開啟了事務 那麼在關閉session之前要執行session.commit()進行提交 這樣訊息才會被真正的提交到佇列裡面

一旦執行過程中出現了錯誤那麼在finally裡面執行sesssion.rollback()

對於生產者來說:Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);需要commit

對於消費者來說:Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);需要commit

簽收 :

  • 自動簽收Session.AUTOACKNOWLEDGE
  • 手動簽收 Session.CLIENT ACKNOWLEDGE客戶端需要呼叫acknowledge方法手動簽收 消費者收到訊息之後執行message.acknowledge()進行確認消費
  • 允許重複簽收

總結:在事務性會話中當一個事務被成功提交則訊息被自動簽收如果事務回滾則訊息會被再次傳送

非事務性會話中 訊息何時被確認取決於建立會話時的應答模式

Spring整合ActiveMQ

配置jar包

<!--ActiceMQ對JMS的支援  整合Spring和ActiveMQ-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>4.3.23.RELEASE</version>
        </dependency>
        <!--ActiveMQ所需要的pool包配置-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-pool</artifactId>
            <version>5.15.9</version>
        </dependency>
        <!--Spring-aop等相關的jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>

applicationContext.xml

<?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:contest="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 http://www.springframework.org/schema/context/spring-context.xsd">
    <!--  開啟包的自動掃描  -->
    <contest:component-scan base-package="com.ljx"/>
    <!--  配置生產者  -->
    <bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
        <property name="connectionFactory">
            <!-- 正真可以生產Connection的ConnectionFactory,由對應的JMS服務商提供 -->
            <bean class="org.apache.activemq.spring.ActiveMQConnectionFactory">
                <property name="brokerURL" value="tcp://192.168.120.132:61616"/>
            </bean>
        </property>
        <property name="maxConnections" value="100"/>
    </bean>

    <!--  這個是佇列目的地,點對點的Queue  -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <!--    通過構造注入Queue名    -->
        <constructor-arg index="0" value="spring-active-queue"/>
    </bean>

    <!--  這個是釋出訂閱目的地,  釋出訂閱的主題Topic-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-active-topic"/>
    </bean>

    <!--  Spring提供的JMS工具類,他可以進行訊息傳送,接收等  -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <!--    傳入連線工廠    -->
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--    傳入目的地    -->
        <property name="defaultDestination" ref="destinationTopic"/>
        <!--    引用的是佇列或者主題    -->
        <!--    訊息自動轉換器    -->
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
        </property>
    </bean>
</beans>

編寫佇列的消費者和生產者

//業務邏輯層
@Service
public class SpringMQ_producer {
    @Autowired
    private JmsTemplate jmsTemplate;

    //在配置檔案配置的能夠實現訊息的傳送和訂閱
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        SpringMQ_producer springMQ_producer = applicationContext.getBean(SpringMQ_producer.class);
        springMQ_producer.jmsTemplate.send(session -> session.createTextMessage("Spring整合ActiveMQ的案例"));
        System.out.println("傳送成功");
    }
}
@Service
public class SpringMQ_Consumer {
    @Autowired
    private JmsTemplate jmsTemplate;

    public static void main(String[] args) {
        ApplicationContext applicationContext=
                new ClassPathXmlApplicationContext("applicationContext.xml");
        SpringMQ_Consumer springMQ_consumer = applicationContext.getBean(SpringMQ_Consumer.class);
        String returnValue = (String) springMQ_consumer.jmsTemplate.receiveAndConvert();
        System.out.println("接受者接受的訊息是:"+returnValue);
    }
}

對於訂閱釋出模式 只需要在配置檔案的JMT裡面設定對應的方式就可以了

通過監聽來接受訊息 不啟動消費者便能接受到訊息

 <!--  配置Jms訊息監聽器  -->
    <bean id="defaultMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <!--  Jms連線的工廠     -->
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--   設定預設的監聽目的地     -->
        <property name="destination" ref="destinationTopic"/>
        <!--  指定自己實現了MessageListener的類     -->
        <property name="messageListener" ref="myMessageListener"/>
    </bean>
@Component
public class MyMessageListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        if (message!=null && message instanceof TextMessage){
            TextMessage textMessage=(TextMessage) message;
            try {
                System.out.println("訊息者受到的訊息"+textMessage.getText());
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

SpringBoot整合activemq

引入配置檔案

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>		
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-activemq</artifactId>
        </dependency>

配置application.properties

server.port=7777
spring.activemq.broker-url=tcp://192.168.120.32:61616
spring.activemq.user=admin
spring.activemq.password=admin
#false是佇列  true指的是topic
spring.jms.pub-sub-domain=false

#自定義佇列的名字
myqueue=boot-active-queue

配置Bean

@Component
@EnableJms
//@EnableJms開啟了JMS適配的註解  JmsMessagingTemplate
public class ConfigBean {
    //獲取佇列的名字
    @Value("${myqueue}")
    private String myQueue;

    @Bean   //相當於在applicationcontext.xml裡面的配置
    public Queue queue(){
        return new ActiveMQQueue(myQueue);
    }
}

編寫傳送者

@Component
/**
 * 將這個實體類新增到Spring容器裡面
 * 這個類不屬於@Controller  @Service等這些的時候
 */
public class Queue_Produce {
    @Resource
    private JmsMessagingTemplate jmsMessagingTemplate;
    @Resource
    private Queue queue; //jms包下面的public interface Queue extends Destination 
    //因為通過@Bean將容器裡面的進行了管理  就可以通過@Resource獲取Queue
    //觸發傳送點一個傳送一次
    public void produceMsg(){
        jmsMessagingTemplate.convertAndSend(queue,"---"+ UUID.randomUUID().toString().substring(0,6));
    }
    //實現定時傳送資訊  每個3秒自動的向佇列裡面傳送資訊
    //間隔定投
    @Scheduled(fixedDelay = 3000)
    public void produceMsgScheduled(){
        jmsMessagingTemplate.convertAndSend(queue,"----scheduled"+UUID.randomUUID().toString().substring(0,6));
        System.out.println("--->scheduled");
    }
}

在啟動類上面新增註解

@EnableScheduling

補充內容:

每個一段時間向MQ推送資訊

 //實現定時傳送資訊  每個3秒自動的向佇列裡面傳送資訊
    //間隔定投
    @Scheduled(fixedDelay = 3000)
    public void produceMsgScheduled(){
        jmsMessagingTemplate.convertAndSend(queue,"----scheduled"+UUID.randomUUID().toString().substring(0,6));
        System.out.println("--->scheduled");
    }

對於消費者來說**

配置檔案和pom與傳送者是一樣的

@Component
public class Queue_Consumer {
    @JmsListener(destination = "${myqueue}")
    public void receive(TextMessage textMessage)throws JMSException{
        System.out.println("消費者消費資訊--->"+textMessage.getText());
    }
}
傳送者的類
@Component
public class Queue_Consumer {
    @JmsListener(destination = "${myqueue}")
    public void receive(TextMessage textMessage)throws JMSException{
        System.out.println("消費者消費資訊--->"+textMessage.getText());
    }
}
由於訊息的一致性  所以要求佇列的名字要是一致的

基於訂閱模式的生產者與消費者

訂閱者

server.port=8888
#模擬兩個消費者  8888和5566
#spring.activemq.broker-url=tcp://192.168.120.32:61616
#使用NIO協議
spring.activemq.broker-url=nio://192.168.120.32:61618
spring.activemq.user=admin
spring.activemq.password=admin
spring.jms.pub-sub-domain=true

mytopic=boot-activemq-topic
消費者操作的容器
@Component
public class Topic_Consumer {
    @JmsListener(destination = "${mytopic}")
    public void receive(TextMessage textMessage)throws JMSException{
        System.out.println("消費者topic收到的訊息是:"+textMessage.getText());

    }
}

釋出者

server.port=6666
#spring.activemq.broker-url=tcp://192.168.120.32:61616
#使用NIO協議
spring.activemq.broker-url=nio://192.168.120.32:61618
spring.activemq.user=admin
spring.activemq.password=admin
spring.jms.pub-sub-domain=true

#自定義主題名稱
mytopic=boot-activemq-topic

配置ConfigBean

@Component
@EnableJms
public class ConfigBean {
    @Value("${mytopic}")
    private String topicName;
    @Bean
    public Topic topic(){
        return new ActiveMQTopic(topicName);
    }
}

配置釋出者

@Component
public class Topic_Produce {
     @Resource
     private JmsMessagingTemplate jmsMessagingTemplate;
     @Resource
     public Topic topic;

     @Scheduled(fixedDelay = 3000)
     public void topicMsg(){
        jmsMessagingTemplate.convertAndSend(topic,"主題--->"+ UUID.randomUUID().toString().substring(0,3));
     }
 }

針對於啟動類

@SpringBootApplication
@EnableScheduling
public class ApplicationTopic {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationTopic.class, args);
    }

}

ActiveMQ的傳輸協議

預設的61616埠如何進行修改–>在activemq.xml裡面進行修改

在這裡插入圖片描述

在生產中連線協議如何配置 使用tcp嗎

可以使用NIO的形式進行傳輸

<transportConnector name="nio"uri="nio://0.0.0.0:61618?trace=true"/>
在裡面進行新增

一旦配置未NIO那麼在生產者和消費者就要進行修改

對應的地址就要修改為nio://ip:61618

為什麼使用OpenWire 模式

在網路進行傳輸之前必須要序列化資料 訊息是通過一個叫wire protocol的來序列化成位元組流的

預設情況下 ActiveMQ把wire protocol叫做OpenWire 他的目的是促使網上的效率和資料快速互動

如何能夠支援既支援NIO網路模型又可以支援其他的網路模型

  • 使用auto關鍵字

  • 使用+來為

  • <transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:616 08?
    maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600&amp; org.apache.activemq.transport.nio.SelectorManager.corePoo1Size=20&amp; org.apache.activemq.transport.niio.SelectorManager. maximumPoolSize=50"
    
  • 這樣在設定連線的時候 既可以使用tcp也可以使用nio

ActiveMQ的訊息儲存和持久化

訊息持久化機制主要有

AMQ Message Store(瞭解)是一種基於檔案的儲存方式

KahaDB訊息儲存(重要)

  • 是一種基於日誌檔案進行儲存的 是ActiveMQ5.4開始預設的持久化外掛 存放在本地硬碟裡面
  • 如何知道
    • 在activemq.xml檔案裡面
  • 訊息儲存使用一個事務日誌和一個索引檔案來儲存的所有的地址
  • 地址在data/kahadb檔案裡面
  • Kahadb儲存原理

Kahadb在訊息儲存的目錄中有4類檔案和一個lock 根ActiveMQ的其他的幾種檔案儲存引擎相比更加簡潔

  • db-number.log檔案KahaDB儲存訊息到預定大小的資料紀錄檔案中,檔名為db-number.log。當資料檔案已滿時,一個新的檔案會隨之建立,number數值也會隨之遞增,它隨著訊息數量的增多,如沒32M一個檔案,檔名按照數字進行編號,如db-1.log,db-2.log······。當不再有引用到資料檔案中的任何訊息時,檔案會被刪除或者歸檔
  • db.data檔案 該檔案包含了持久化BTree索引 記錄了訊息資料記錄中的訊息 他是訊息的索引檔案 本質是B-Tree 使索引指向db-number.log裡面儲存的資料
  • db.free 訪問當前db.data檔案裡面那些頁面是空閒的 檔案具體內容是所有的空閒也的ID
  • db.redo 用來進行訊息恢復 如果在執行期間KahaDB訊息儲存在強制退出後啟動 可以用來恢復BTree索引
  • lock 檔案所 表示當前kahadb讀寫許可權的broker

JDBC訊息儲存(重要 企業中常用)

  • MQ+MySQL 將MySql的jar拷貝到lib資料夾下面

  • <persistenceAdapter>
       <kahaDB directory="${activemq.data}/kahadb"/>
    </persistenceAdapter>
    變為
    <persistenceAdapter> 
      <jdbcPersistenceAdapter dataSource="#mysql-ds" createTableOnStartup="true"/> 
     </persistenceAdapter>
    dataSource是指定將要引用的持久化資料庫的bean名稱。
    createTableOnStartup是否在啟動的時候建立資料庫表,預設是true,這樣每次啟動都會去建立表了,一般是第一次啟動的時候設定為true,然後再去改成false。
    
  • 在activemq.xml裡面配置資料庫連線池
      <bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> 
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> 
        <property name="url" value="jdbc:mysql://192.168.10.132:3306/activemq?relaxAutoCommit=true"/> 
        <property name="username" value="root"/> 
        <property name="password" value="123123"/> 
        <property name="poolPreparedStatements" value="true"/> 
      </bean>
    
    
  • 建立一個activemq的資料庫並且在程式啟動之後會建立三張表

    • ACTIVEMQ_MSGS Queue和Topic都存在裡面
    • ACTIVEMQ_ACKS 用來控制訊息的簽收資訊
    • ACTIVEMQ_LOCK 在叢集環境中進行使用 只有一個Broker可以獲取訊息 其他的只能作為備份等待Master Broker不可用才會成為下一個Master Broker
  • 程式碼執行測試

    • 前提是開啟了持久化messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); 因為如果是非持久化的會儲存在記憶體裡面 持久化的會儲存在相應的檔案或者資料庫裡面

    • 對於佇列模式來說
      生產者
      public class Producer {
          private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616";
          private static final String ACTIVEMQ_QUEUE_NAME = "Queue-JdbcPersistence";
      
          public static void main(String[] args) throws JMSException {
              ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
              activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL);
              Connection connection = activeMQConnectionFactory.createConnection();
              Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
              Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME);
              MessageProducer messageProducer = session.createProducer(queue);
              messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
              connection.start();
              for (int i = 0; i < 3; i++) {
                  TextMessage textMessage = session.createTextMessage("Queue-JdbcPersistence測試訊息" + i);
                  messageProducer.send(textMessage);
              }
              session.commit();
              System.out.println("訊息傳送完成");
              messageProducer.close();
              session.close();
              connection.close();
          }
      }
      消費者
      public class Consumer {
          private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616";
          private static final String ACTIVEMQ_QUEUE_NAME = "Queue-JdbcPersistence";
      
          public static void main(String[] args) throws JMSException, IOException {
              ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
              activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL);
              Connection connection = activeMQConnectionFactory.createConnection();
              Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
              Queue queue = session.createQueue(ACTIVEMQ_QUEUE_NAME);
              MessageConsumer messageConsumer = session.createConsumer(queue);
              connection.start();
              messageConsumer.setMessageListener(new MessageListener() {
                  @SneakyThrows
                  @Override
                  public void onMessage(Message message) {
                      if (message instanceof TextMessage) {
                          TextMessage textMessage = (TextMessage) message;
                          session.commit();
                          System.out.println("消費者收到訊息" + textMessage.getText());
                      }
                  }
              });
              System.in.read();
          }
      }s
      
    • 對於主題訂閱釋出模式
      釋出者
      public class Producer {
          private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616";
          private static final String ACTIVEMQ_TOPIC_NAME = "Topic-JdbcPersistence";
      
          public static void main(String[] args) throws JMSException {
              ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
              activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL);
              Connection connection = activeMQConnectionFactory.createConnection();
              connection.setClientID("我是生產者張三");
              Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
              Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME);
              MessageProducer messageProducer = session.createProducer(topic);
              messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
              connection.start();
              for (int i = 0; i < 3; i++) {
                  TextMessage textMessage = session.createTextMessage("Topic-JdbcPersistence測試訊息" + i);
                  messageProducer.send(textMessage);
              }
              session.commit();
              System.out.println("主題傳送到MQ完成");
              messageProducer.close();
              session.close();
              connection.close();
          }
      }
      消費者
      public class Consumer1 {
          private static final String ACTIVEMQ_URL = "nio://192.168.10.130:61616";
          private static final String ACTIVEMQ_TOPIC_NAME = "Topic-JdbcPersistence";
      
          public static void main(String[] args) throws JMSException, IOException {
              ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
              activeMQConnectionFactory.setBrokerURL(ACTIVEMQ_URL);
              Connection connection = activeMQConnectionFactory.createConnection();
              connection.setClientID("我是消費者李四");
              Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
              Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME);
              TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "我是消費者李四我要訂閱這個訊息");
              connection.start();
              topicSubscriber.setMessageListener(new MessageListener() {
                  @Override
                  public void onMessage(Message message) {
                      if (message instanceof TextMessage) {
                          TextMessage textMessage = (TextMessage) message;
                          try {
                              System.out.println("消費者李四收到的訊息: " + textMessage.getText());
                              session.commit();
                          } catch (JMSException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              });
              System.in.read();
          }
      }
      
      

對於佇列模式來說

  • 非持久話的時候會被儲存在記憶體裡面
  • 持久化的時候會被儲存在對應的檔案和資料庫裡面
  • 並且一旦被消費者消費之後 資料就會從資料庫中進行刪除但是在消費前還是儲存在資料庫裡面的

對於釋出訂閱模式來說

  • 設定了持久化訂閱,資料庫裡面會儲存訂閱者的資訊

總結

如果是queue在沒有消費者消費的情況下會將訊息儲存到activemq_msgs表中,只要有任意一個消費者消費了,就會刪除消費過的訊息

如果是topic 一般是先啟動消費訂閱者然後再生產的情況下會將持久訂閱者永久儲存到qctivemq_acks,而訊息則永久儲存在activemq_msgs,facks表中的訂閱者有一個last ack id對應了activemq_msgs中的id欄位,這樣就知道訂閱者最後收到的訊息是哪一條。

開發過程中遇到的問題

  1. 需要將對應版本的資料庫jar或者自己使用的資料庫連線池的jar包放到lib資料夾下面
  2. 對於配置檔案裡面的createTablesOnStartup屬性預設為true 就是每一次啟動activeMQ的時候會進行自動的建立表 所以在第一次啟動之後 將值設定為false 來避免一些不必要的損失
  3. 注意主機的名稱不能夠存在下劃線 否則會出現一個java.lang.IllegalStateException: LifecycleProcessor not initialized因為在這裡下劃線是一個不能進行編譯的字元

LevelDB訊息儲存(瞭解)

JDBC Message Store with ActiveMQ Journal(JDBC加強)

這種技術克服了JDBC Store的不足 因為每一次JDBC訊息傳送過來地時候都要去讀寫資料庫ActiveMQ Journal使用了告訴快取的技術 大大的提高了效能

當消費者消費訊息的速度能夠及時的跟上生產者生產的速度 那麼ActiveMQ Journal檔案就能大大的減少需要寫入DB的訊息

比如:生產者生產了1000條訊息 這1000條訊息會被儲存在journal問阿金 如果消費者消費的速度很快 在Journal檔案還沒有同步到DB之前 消費者已經消費了90% 那麼這個時候只需要同步剩餘的10%的訊息到DB 如果消費者的速度很慢 這個時候Journal檔案就可以讓資訊以批量的方式寫到DB中

在配置檔案裡面進行配置

修改
<persistenceAdapter> 
    <jdbcPersistenceAdapter dataSource="#mysql-ds" /> 
</persistenceAdapter><persistenceFactory>        
    <journalPersistenceAdapterFactory 
        journalLogFiles="5" 
        journalLogFileSize="32768" 
        useJournal="true" 
        useQuickJournal="true" 
        dataSource="#mysql-ds" 
        dataDirectory="../activemq-data" /> 
</persistenceFactory>

總結:以前實時的寫入到mysql裡面 像Redis的RDB在使用了journal之後 資料會被journal處理如果在一定的時間內 journal處理完了 就不寫入mysql 如果沒消費完 就寫入mysql 起到一個快取的作用

啟動activeMQ的時候會進行自動的建立表 所以在第一次啟動之後 將值設定為false 來避免一些不必要的損失
3. 注意主機的名稱不能夠存在下劃線 否則會出現一個java.lang.IllegalStateException: LifecycleProcessor not initialized因為在這裡下劃線是一個不能進行編譯的字元

LevelDB訊息儲存(瞭解)

JDBC Message Store with ActiveMQ Journal(JDBC加強)

這種技術克服了JDBC Store的不足 因為每一次JDBC訊息傳送過來地時候都要去讀寫資料庫ActiveMQ Journal使用了告訴快取的技術 大大的提高了效能

當消費者消費訊息的速度能夠及時的跟上生產者生產的速度 那麼ActiveMQ Journal檔案就能大大的減少需要寫入DB的訊息

比如:生產者生產了1000條訊息 這1000條訊息會被儲存在journal問阿金 如果消費者消費的速度很快 在Journal檔案還沒有同步到DB之前 消費者已經消費了90% 那麼這個時候只需要同步剩餘的10%的訊息到DB 如果消費者的速度很慢 這個時候Journal檔案就可以讓資訊以批量的方式寫到DB中

在配置檔案裡面進行配置

修改
<persistenceAdapter> 
    <jdbcPersistenceAdapter dataSource="#mysql-ds" /> 
</persistenceAdapter><persistenceFactory>        
    <journalPersistenceAdapterFactory 
        journalLogFiles="5" 
        journalLogFileSize="32768" 
        useJournal="true" 
        useQuickJournal="true" 
        dataSource="#mysql-ds" 
        dataDirectory="../activemq-data" /> 
</persistenceFactory>

總結:以前實時的寫入到mysql裡面 像Redis的RDB在使用了journal之後 資料會被journal處理如果在一定的時間內 journal處理完了 就不寫入mysql 如果沒消費完 就寫入mysql 起到一個快取的作用

好的本文就寫到這裡 如果有需要補償和交流的地方 還請在評論區進行交流

相關文章