MQ系列02:ActiveMQ

ActiveMQ

1.關於ActiveMQ

ActiveMQ是Apache 出品的一款開源消息中間件,它實現了JMS標準。

2.關於JMS

JMS API是一個消息服務規範,用於應用程序之間的通信。

3.關於消息模型

  • 3.1 點對點(Point-To-Point)

    使用隊列(Queue)作爲消息通信載體,即生產者與消費者模式。一條消息只能被一個消費者使用,未被消費的消息在隊列中保留直到被消費或超時

  • 3.2 發佈/訂閱(Publisher-Subscriber)

    使用主題(Topic)作爲消息通信載體,類似於廣播模式。發佈者發佈一條消息,該消息通過主題傳遞給所有在線的訂閱者。普通訂閱者在發佈消息之後上線或者是發佈消息之後訂閱,將會錯過這條消息;持久訂閱者不會出現這個問題。

4.消息確認方式

  • 4.1 自動確認模式,不需客戶端進行確認

    Session.AUTO_ACKNOWLEDGE 1

  • 4.2 客戶端進行確認

    Session.CLIENT_ACKNOWLEDGE 2
    如果創建Session時,acknowledgeMode = Session.CLIENT_ACKNOWLEDGE;那麼後續的代碼需要message.acknowledge()才能確認消息。

  • 4.3 允許副本的確認模式

    Session.DUPS_OK_ACKNOWLEDGE 3

  • 4.4 支持實務,需要提交實務

    Session.SESSION_TRANSACTED 0
    創建Session,需要設置transacted = true,acknowledgeMode=Session.SESSION_TRANSACTED;那麼後續代碼需要session.commit()才能提交。

5.控制檯的ActiveMQ

  • 下載ActiveMQ(activemq),選擇Windows
  • 解壓
  • 運行:32位在[ActiveMQ_install_dir]/bin/Win32/,雙擊activemq.bat;64位在[ActiveMQ_install_dir]/bin/Win64/,雙擊activemq.bat
  • 5.1 引入依賴
      <dependency>
          <groupId>javax.jms</groupId>
          <artifactId>javax.jms-api</artifactId>
          <version>2.0.1</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.15.9</version>
      </dependency>
    
  • 5.2 編寫常量類 Constant.java
    import org.apache.activemq.ActiveMQConnection;
    public interface Constant {
        //用戶名
        String USERNAME = ActiveMQConnection.DEFAULT_USER;
        //密碼
        String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;
        //ActiveMQ地址
        String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL;
        Integer MESSAGE_COUNT = 10;
    }
    
  • 5.3 點對點模型
    • 5.3.1 生產者 PointToPointProducer.java
    import cn.techpan.happiness.mq.active_mq.console.Constant;
    import org.apache.activemq.ActiveMQConnectionFactory;
    import javax.jms.Connection;
    import javax.jms.ConnectionFactory;
    import javax.jms.JMSException;
    import javax.jms.MessageProducer;
    import javax.jms.Queue;
    import javax.jms.Session;
    import javax.jms.TextMessage;
    import java.util.Objects;
    /**
     * ActiveMQ P2P 生產端
     */
    public class PointToPointProducer {
        public static void main(String[] args) {
            Connection connection = null;
            try {
                //1獲取ActiveMQ的連接工廠
                ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(Constant.USERNAME, Constant.PASSWORD, Constant.BROKER_URL);
                //2使用連接工廠創建連接
                connection = connectionFactory.createConnection();
                //3開啓連接
                connection.start();
                //4創建session對象
                //  參數一:是否支持事務
                //  參數二:消息確認方式
                Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                //5創建隊列
                Queue queue = session.createQueue("queue");
                //6創建消息生產者(MessageProducer)
                MessageProducer messageProducer = session.createProducer(queue);
                //7發送消息
                sendMessage(session, messageProducer);
            } catch (JMSException e) {
                e.printStackTrace();
            } finally {
                try {
                    Objects.requireNonNull(connection).close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
        private static void sendMessage(Session session, MessageProducer messageProducer) {
            for (int i = 0; i < Constant.MESSAGE_COUNT; i++) {
                try {
                    TextMessage textMessage = session.createTextMessage();
                    textMessage.setText("PointToPointProducer 發送消息: " + i);
                    System.out.println("PointToPointProducer 發送消息: " + i);
                    messageProducer.send(textMessage);
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 5.3.2 消費者 PointToPointCustomer.java
    import cn.techpan.happiness.mq.active_mq.console.Constant;
    import org.apache.activemq.ActiveMQConnectionFactory;
    import javax.jms.Connection;
    import javax.jms.ConnectionFactory;
    import javax.jms.JMSException;
    import javax.jms.MessageConsumer;
    import javax.jms.Queue;
    import javax.jms.Session;
    import javax.jms.TextMessage;
    import java.util.Objects;
    /**
     * ActiveMQ P2P 消費端
     */
    public class PointToPointCustomer {
        public static void main(String[] args) {
            Connection connection = null;
            try {
                ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(Constant.USERNAME, Constant.PASSWORD, Constant.BROKER_URL);
                connection = connectionFactory.createConnection();
                connection.start();
                Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                Queue queue = session.createQueue("queue");
                //創建消息消費者(MessageConsumer)
                MessageConsumer messageConsumer = session.createConsumer(queue);
                while (true) {//同步接收消息(異步可使用MessageListener)
                    //同步接收:主線程阻塞式等待下一個消息的到來,可以設置timeout,超時則返回null。
                    //同步接收又稱爲阻塞式接收
                    //同步接收,是在獲取MessageConsumer實例之後,調用以下的API:
                    //receive():Message
                    //      獲取下一個消息。這個調用將導致無限期的阻塞,直到有新的消息產生。
                    //receive(long timeout):Message
                    //      獲取下一個消息。這個調用可能導致一段時間的阻塞,直到超時或者有新的消息產生。超時則返回null。
                    //receiveNoWait():Message
                    //      獲取下一個消息。這個調用不會導致阻塞,如果沒有下一個消息,直接返回null。
                    TextMessage textMessage = (TextMessage) messageConsumer.receive(10000);
                    if (textMessage != null) {
                        System.out.println("PointToPointCustomer 接收消息: " + textMessage.getText());
                    } else {
                        break;
                    }
                }
            } catch (JMSException e) {
                e.printStackTrace();
            } finally {
                try {
                    Objects.requireNonNull(connection).close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    
  • 5.4 發佈/訂閱模型
    • 5.4.1 生產者 PublishSubscribeProducer.java
    import cn.techpan.happiness.mq.active_mq.console.Constant;
    import org.apache.activemq.ActiveMQConnectionFactory;
    import javax.jms.Connection;
    import javax.jms.ConnectionFactory;
    import javax.jms.DeliveryMode;
    import javax.jms.JMSException;
    import javax.jms.MessageProducer;
    import javax.jms.Session;
    import javax.jms.TextMessage;
    import javax.jms.Topic;
    import java.util.Objects;
    /**
     * ActiveMQ Pub-Sub 生產者
     */
    public class PublishSubscribeProducer {
    
        public static void main(String[] args) {
            Connection connection = null;
            try {
                ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(Constant.USERNAME, Constant.PASSWORD, Constant.BROKER_URL);
                connection = connectionFactory.createConnection();
                connection.start();
                Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                //創建主題
                Topic topic = session.createTopic("topic");
                //創建生產者
                MessageProducer messageProducer = session.createProducer(topic);  
                //發送消息
                sendMessage(session, messageProducer);
            } catch (JMSException e) {
                e.printStackTrace();
            } finally {
                try {
                    Objects.requireNonNull(connection).close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
        private static void sendMessage(Session session, MessageProducer messageProducer) throws JMSException {
            for (int i = 0; i < Constant.MESSAGE_COUNT; i++) {
                TextMessage textMessage = session.createTextMessage();
                String message = "PublishSubscribeProducer Send Message:" + i;
                System.out.println(message);
                textMessage.setText(message);
                messageProducer.send(textMessage);
            }
        }
    }
    
    • 5.4.2 消費者 PublishSubscribeCustomer.java
    import cn.techpan.happiness.mq.active_mq.console.Constant;
    import org.apache.activemq.ActiveMQConnectionFactory;
    import javax.jms.Connection;
    import javax.jms.ConnectionFactory;
    import javax.jms.JMSException;
    import javax.jms.MessageConsumer;
    import javax.jms.Session;
    import javax.jms.TextMessage;
    import javax.jms.Topic;
    import java.util.Objects;
    /**
     * ActiveMQ Pub-Sub 消費者
     */
    public class PublishSubscribeCustomer {
        public static void main(String[] args) {
            Connection connection = null;
            try {
                ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(Constant.USERNAME, Constant.PASSWORD, Constant.BROKER_URL);
                connection = connectionFactory.createConnection();
                connection.start();
                //創建session,不支持事務,自動確認
                Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                //創建topic
                Topic topic = session.createTopic("topic");
                //創建普通訂閱者
                MessageConsumer messageConsumer = session.createConsumer(topic);
                while (true) {
                    //同步獲取消息,等待超時時間爲10000ms。
                    TextMessage textMessage = (TextMessage) messageConsumer.receive(10000);
                    if (textMessage != null) {
                        System.out.println("Message Arrived: " + textMessage.getText());
                    } else {
                        break;
                    }
                }
            } catch (JMSException e) {
                e.printStackTrace();
            } finally {
                try {
                    Objects.requireNonNull(connection).close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

6.與Spring集成

  • 6.1 引入依賴
    <dependency>
        <groupId>javax.jms</groupId>
        <artifactId>javax.jms-api</artifactId>
        <version>2.0.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-all</artifactId>
        <version>5.15.9</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-messaging</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jms</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
        <scope>provided</scope>
    </dependency>
    
  • 6.2 消息監聽器
    • 6.2.1 QueueMessageListener.java
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Component;
      import javax.jms.JMSException;
      import javax.jms.Message;
      import javax.jms.MessageListener;
      import javax.jms.TextMessage;
      /**
       * 配置消息監聽器
       */
      @Slf4j
      @Component
      public class QueueMessageListener implements MessageListener {
          @Override
          public void onMessage(Message message) {
              TextMessage textMessage = (TextMessage) message;
              try {
                  if (textMessage != null) {
                      log.info("message come from: {}, content: {}", message.getJMSDestination(), textMessage.getText());
                  }
              } catch (JMSException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 6.2.2 TopicMessageListener.java
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Component;
      import javax.jms.JMSException;
      import javax.jms.Message;
      import javax.jms.MessageListener;
      import javax.jms.TextMessage;
      /**
       * 配置監聽器
       */
      @Slf4j
      @Component
      public class TopicMessageListener implements MessageListener {
          @Override
          public void onMessage(Message message) {
              TextMessage textMessage = (TextMessage) message;
              try {
                  if (textMessage != null) {
                      log.info("topic01: message come from: {}, content: {}", message.getJMSDestination(), textMessage.getText());
                  }
              } catch (JMSException e) {
                  e.printStackTrace();
              }
          }
      }
      
  • 6.3 消息生產者
    • 6.3.1 QueueProducer.java
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.jms.core.JmsTemplate;
      import org.springframework.stereotype.Component;
      import javax.jms.Destination;
      /**
       * 消息發送
       */
      @Slf4j
      @Component
      public class QueueProducer {
          private JmsTemplate queueJmsTemplate;
          public QueueProducer(JmsTemplate queueJmsTemplate) {
              this.queueJmsTemplate = queueJmsTemplate;
          }
          public void sendMessageToDefaultDestination(String message) {
              Destination destination = queueJmsTemplate.getDefaultDestination();
              log.info("a message has send to {}",  destination);
              queueJmsTemplate.send(session -> session.createTextMessage(message));
          }
          public void sendMessage(Destination destination, String message) {
              log.info("a message has send to {}",  destination);
              queueJmsTemplate.send(destination, session -> session.createTextMessage(message));
          }
      }
      
    • 6.3.2 TopicProducer.java
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.jms.core.JmsTemplate;
      import org.springframework.stereotype.Component;
      import javax.jms.Destination;
      /**
       * 消息發送
       */
      @Slf4j
      @Component
      public class TopicProducer {
          private JmsTemplate jmsTemplate;
          public TopicProducer(@Qualifier("topicJmsTemplate") JmsTemplate jmsTemplate) {
              this.jmsTemplate = jmsTemplate;
          }
          public void sendMessageToDefaultDestination(String message) {
              Destination destination = jmsTemplate.getDefaultDestination();
              log.info("a message has publish to {}",  destination);
              jmsTemplate.send(session -> session.createTextMessage(message));
          }
      }
      
  • 6.4 配置文件 SpringConfig.Java
    import lombok.extern.slf4j.Slf4j;
    import org.apache.activemq.command.ActiveMQQueue;
    import org.apache.activemq.command.ActiveMQTopic;
    import org.apache.activemq.spring.ActiveMQConnectionFactory;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jms.connection.CachingConnectionFactory;
    import org.springframework.jms.core.JmsTemplate;
    import org.springframework.jms.listener.DefaultMessageListenerContainer;
    import javax.jms.Destination;
    import javax.jms.MessageListener;
    
    @Slf4j
    @Configuration
    @ComponentScan(value = "cn.techpan.happiness.mq.active_mq.spring.*")
    public class SpringConfig {
        /**
         * 創建ActiveMQ連接工廠,實現客戶端與ActiveMQ服務提供者連接
         * @return ActiveMQConnectionFactory
         */
        @Bean
        public ActiveMQConnectionFactory getActiveMQConnectionFactory() {
            ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
            activeMQConnectionFactory.setUserName(ActiveMQConnectionFactory.DEFAULT_USER);
            activeMQConnectionFactory.setPassword(ActiveMQConnectionFactory.DEFAULT_PASSWORD);
            activeMQConnectionFactory.setBrokerURL(ActiveMQConnectionFactory.DEFAULT_BROKER_URL);
            return activeMQConnectionFactory;
        }
        /**
         * 創建Spring Caching連接工廠,並注入ActiveMQ連接工廠,用於對ActiveMQ連接工廠的管理
         * @param activeMQConnectionFactory ActiveMQ連接工廠
         * @return CachingConnectionFactory
         */
        @Bean
        public CachingConnectionFactory getCachingConnectionFactory(ActiveMQConnectionFactory activeMQConnectionFactory) {
            CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
            cachingConnectionFactory.setTargetConnectionFactory(activeMQConnectionFactory);
            //設置緩存大小
            cachingConnectionFactory.setSessionCacheSize(100);
            return cachingConnectionFactory;
        }
        /**
         * 創建Destination
         * @return Queue
         */
        @Bean
        public ActiveMQQueue activeMQQueue() {
            return new ActiveMQQueue("spring-queue");
        }
        /**
         * 創建JmsTemplate(queue)
         * @param cachingConnectionFactory 連接工廠
         * @param activeMQQueue Destination
         * @return JmsTemplate
         */
        @Bean("queueJmsTemplate")
        public JmsTemplate getQueueJmsTemplate(CachingConnectionFactory cachingConnectionFactory,
                                               Destination activeMQQueue) {
            JmsTemplate queueJmsTemplate = new JmsTemplate(cachingConnectionFactory);
            //MQ 模型。 true - pub/sub     false - p2p
            queueJmsTemplate.setPubSubDomain(false);
            queueJmsTemplate.setDefaultDestination(activeMQQueue);
            return queueJmsTemplate;
        }
        /**
         * 消息監聽容器 負責處理消息接收的註冊、事務管理、資源獲取與釋放和異常轉換等
         * @param cachingConnectionFactory 連接工廠
         * @param activeMQQueue Destination
         * @param queueMessageListener 消息監聽器
         * @return DefaultMessageListenerContainer
         */
        @Bean
        public DefaultMessageListenerContainer queueMessageListenerContainer(CachingConnectionFactory cachingConnectionFactory,
                                                                             Destination activeMQQueue,
                                                                             MessageListener queueMessageListener) {
            DefaultMessageListenerContainer queueMessageListenerContainer = new DefaultMessageListenerContainer();
            //注入ConnectionFactory
            queueMessageListenerContainer.setConnectionFactory(cachingConnectionFactory);
            //設置Destination
            queueMessageListenerContainer.setDestination(activeMQQueue);
            //註冊MessageListener
            queueMessageListenerContainer.setMessageListener(queueMessageListener);
            return queueMessageListenerContainer;
        }
        /**
         * 創建Destination
         * @return ActiveMQTopic
         */
        @Bean
        public ActiveMQTopic activeMQTopic() {
            return new ActiveMQTopic("spring-topic");
        }
        /**
         * 創建JmsTemplate(Topic)
         * @param cachingConnectionFactory 連接工廠
         * @param activeMQTopic Destination
         * @return JmsTemplate
         */
        @Bean("topicJmsTemplate")
        public JmsTemplate getTopicJmsTemplate(CachingConnectionFactory cachingConnectionFactory,
                                               Destination activeMQTopic) {
            JmsTemplate topicJmsTemplate = new JmsTemplate(cachingConnectionFactory);
            topicJmsTemplate.setPubSubDomain(true);
            topicJmsTemplate.setDefaultDestination(activeMQTopic);
            return topicJmsTemplate;
        }
        /**
         * 消息監聽容器
         * @param cachingConnectionFactory 連接工廠
         * @param activeMQTopic Destination
         * @param topicMessageListener 消息監聽器
         * @return DefaultMessageListenerContainer
         */
        @Bean
        public DefaultMessageListenerContainer topicMessageListenerContainer(CachingConnectionFactory cachingConnectionFactory,
                                                                             Destination activeMQTopic,
                                                                             MessageListener topicMessageListener) {
            DefaultMessageListenerContainer topicMessageListenerContainer = new DefaultMessageListenerContainer();
            topicMessageListenerContainer.setConnectionFactory(cachingConnectionFactory);
            topicMessageListenerContainer.setDestination(activeMQTopic);
            topicMessageListenerContainer.setMessageListener(topicMessageListener);
            return topicMessageListenerContainer;
        }  
    }
    
  • 6.5 消息發送服務
    • 6.5.1 QueueMessageService.java
      import cn.techpan.happiness.mq.active_mq.spring.jms.producer.QueueProducer;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Service;
      @Slf4j
      @Service
      public class QueueMessageService {
          //注入消息生產者
          private QueueProducer queueProducer;
          public QueueMessageService(QueueProducer queueProducer) {
              this.queueProducer = queueProducer;
          }
          public void sendMessage() {
              log.info("ready for sending message");
              log.info("producer is sending messages!");
              for (int i = 0; i < 10; i++) {
                  String message = "message NO." + i;
                  queueProducer.sendMessageToDefaultDestination(message);
              }
              log.info("message send over");
          }
      }
      
    • 6.5.2 TopicMessageService.java
      import cn.techpan.happiness.mq.active_mq.spring.jms.producer.TopicProducer;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Service;
      @Slf4j
      @Service
      public class TopicMessageService {
          //注入消息生產者
          private TopicProducer topicProducer;
          public TopicMessageService(TopicProducer topicProducer) {
              this.topicProducer = topicProducer;
          }
          public void sendMessage() {
              log.info("ready for publishing message");
              log.info("producer is publishing messages!");
              for (int i = 0; i < 10; i++) {
                  String message = "message NO." + i;
                  topicProducer.sendMessageToDefaultDestination(message);
              }
              log.info("message publish over");
          }
      }
      
  • 6.6 主運行類 SpringActiveMQApplication
    import cn.techpan.happiness.mq.active_mq.spring.config.SpringConfig;
    import cn.techpan.happiness.mq.active_mq.spring.message.QueueMessageService;
    import cn.techpan.happiness.mq.active_mq.spring.message.TopicMessageService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    public class SpringActiveMQApplication {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
            QueueMessageService queueMessageService = applicationContext.getBean(QueueMessageService.class);
            queueMessageService.sendMessage();
            TopicMessageService topicMessageService = applicationContext.getBean(TopicMessageService.class);
            topicMessageService.sendMessage();
        }
    }
    
  • 7 參考文檔
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章