AcitveMQ第二季——虛擬主題開發

安裝依賴版本一覽

java:1.8.0_144

ActiveMQ:5.15.0

安裝包地址:https://pan.baidu.com/s/1hss2ltq

完整demo下載:百度網盤CSDN


部署ActiveMQ

1. 加入你不想下載上面提供的地址,那麼可以這麼做(PS:java8環境必須先準備好)。

wget http://mirrors.hust.edu.cn/apache//activemq/5.15.0/apache-activemq-5.15.0-bin.tar.gz
tar zxf apache-activemq-5.15.0-bin.tar.gz

# 啓動
cd [activemq_install_dir]/bin
./activemq console
# daemon方式啓動|停止
cd [activemq_install_dir]/bin
./activemq start|stop

2. 如果你下載了上面提供的地址,那麼上傳到服務器後,直接解壓運行即可。

3. 控制檯管理

瀏覽器輸入:http://your_host:8161/admin。用戶名和密碼默認爲admin/admin

然後你就可以看到ActiveMQ的管理界面了。

基本上,你所需要關心的主要在這三個tab。

  • Queues,這是生產者/消費者傳送隊列消息的地方。他的特點就是可以達到消費者的負載均衡和故障轉移的目的。裏面可以看到消費者列表。
  • Topics,這是發佈者/訂閱者通信的地方,每個訂閱者都會收到一份消息的拷貝來進行消費。
  • Subscribers,在這裏可以看到主題和其訂閱者的列表。5.8版本看不到發佈者和非持久化訂閱的訂閱者。。。

上述的三個tab會在之後進行敘述。

生產者/消費者開發

注意,這僅僅是個demo,開發方式基於SpringBoot

先前準備

1. 首先是Maven

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.7.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-client</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

生產者

啓動類

/*****************************************************************
 * Copyright (c) 2017 www.noryar.com Inc. All rights reserved.
 *****************************************************************/
package boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 類描述:啓動類.
 *
 * @author leon.
 */
@SpringBootApplication(scanBasePackages = "publisher")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

生產者

src/publisher

/*****************************************************************
 * Copyright (c) 2017 www.noryar.com Inc. All rights reserved.
 *****************************************************************/
package publisher;

import org.apache.activemq.command.ActiveMQQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;

/**
 * 類描述:發佈者.
 *
 * @author leon.
 */
@Component
public class Publisher {

    private static final Logger LOGGER = LoggerFactory.getLogger(Publisher.class);
    private static final String DEFAULT_MSG = "hello";
    private static final String DEFAULT_QUEUE = "test";
    private static final String EMPTY = "";

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    public String send(String queue, String msg) {
        if (isBlank(queue)) {
            queue = DEFAULT_QUEUE;
        }
        if (isBlank(msg)) {
            msg = DEFAULT_MSG;
        }
        jmsMessagingTemplate.convertAndSend(new ActiveMQQueue(queue), msg);
        return "SUCCESS";
    }

    private boolean isBlank(String str) {
        if (null == str || str.trim().equals(EMPTY)) {
            return true;
        } else {
            return false;
        }
    }

}

服務配置,application.yml

server.port: 8081

spring.activemq.broker-url: failover:(tcp://localhost:61616?trace=true&wireFormat.maxInactivityDuration=3600000)
spring.activemq.user: admin
spring.activemq.password: admin
spring.activemq.in-memory: true
spring.activemq.pool.enabled: false

消息發送

/*****************************************************************
 * Copyright (c) 2017 www.noryar.com Inc. All rights reserved.
 *****************************************************************/
package publisher;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 類描述:web.
 *
 * @author leon.
 */
@Controller
public class Web {

    @Autowired
    private Publisher publisher;

    @RequestMapping("/send")
    public void send(String queue, String msg) {
        publisher.send(queue, msg);
    }

}

通過web界面來發送消息即可

消費者

啓動類

/*****************************************************************
 * Copyright (c) 2017 www.noryar.com Inc. All rights reserved.
 *****************************************************************/
package boot;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 類描述:啓動類.
 *
 * @author leon.
 */
@SpringBootApplication(scanBasePackages = "subscriber")
public class Application {

    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }



}

消費者

/*****************************************************************
 * Copyright (c) 2017 www.noryar.com Inc. All rights reserved.
 *****************************************************************/
package subscriber;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.stereotype.Component;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.TextMessage;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 類描述:訂閱者.
 *
 * @author leon.
 */
@Component
public class Subscriber {

    private static final Logger LOGGER = LoggerFactory.getLogger(Subscriber.class);
    private AtomicInteger cnt = new AtomicInteger();

    // #############################註解啓動方式#############################
    @JmsListener(destination = "test", containerFactory = "myFactory")
    public String receive(Message message) throws JMSException {
        String msg = ((TextMessage) message).getText();
        LOGGER.info("get message: {}", msg);
        cnt.addAndGet(1);
        return msg;
    }

    public int getDealCnt() {
        return cnt.get();
    }


    // #############################Container#############################
    @Autowired
    private Environment env;
    @Autowired
    private CtpListener ctpListener;

    @Bean
    public ActiveMQConnectionFactory myFactory() {
        LOGGER.info(env.getProperty("spring.activemq.broker-url"));
        ActiveMQConnectionFactory myFactory = new ActiveMQConnectionFactory();
        myFactory.setBrokerURL(env.getProperty("spring.activemq.broker-url"));
        myFactory.setAlwaysSyncSend(false);
        myFactory.setUserName(env.getProperty("username"));
        myFactory.setPassword(env.getProperty("password"));
        return myFactory;
    }

    @Bean
    public DefaultMessageListenerContainer myContainer() {
        DefaultMessageListenerContainer myContainer = new DefaultMessageListenerContainer();
        myContainer.setConnectionFactory(myFactory());
        myContainer.setDestination(new ActiveMQQueue(env.getProperty("ctp.topic.queue")));
        myContainer.setMessageListener(ctpListener);
        myContainer.setSubscriptionDurable(true);
        myContainer.setDurableSubscriptionName("ctp_name_2");
        myContainer.setClientId("ctp_id_2");
        myContainer.setSessionAcknowledgeModeName("CLIENT_ACKNOWLEDGE");
        return myContainer;
    }

}

上述消費者提供了兩種啓動方式

1. 基於JmsListener註解的監聽器方式

2. 基於DefaultMessageListenerContainer的啓動方式

使用時候可以隨意挑選一種。

如果使用第二種方式,需要制定監聽器:

/*****************************************************************
 * Copyright (c) 2017 www.noryar.com Inc. All rights reserved.
 *****************************************************************/
package subscriber;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 訂閱者.
 *
 * @author leon.
 */
@Component
public class CtpListener implements MessageListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(CtpListener.class);
    AtomicInteger cnt = new AtomicInteger();
    private LinkedBlockingQueue queue = new LinkedBlockingQueue<MyTask>();
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, queue);

    @Override
    public void onMessage(Message message) {
        executor.execute(new MyTask(message));
    }

    public int getCnt() {
        return cnt.get();
    }

    class MyTask implements Runnable {

        private Message message;

        public MyTask(Message message) {
            this.message = message;
        }

        @Override
        public void run() {
            String msg = ((MapMessage) message).toString();
            LOGGER.info("get message: {}", msg);
            cnt.addAndGet(1);
        }
    }
}

這裏的監聽器使用了異步的方式,當然也可以配置Container的併發消費數量來實現多線程消費。

服務配置,application.yml

server.port: 8283

spring.activemq.broker-url: failover:(tcp://localhost:61616?trace=true&wireFormat.maxInactivityDuration=3600000)
spring.activemq.user: admin
spring.activemq.password: admin
spring.activemq.in-memory: true
spring.activemq.pool.enabled: false

username: system
password: manager
ctp.topic.queue: test.queue

這裏監聽的是test.queue

虛擬主題的配置

如果你想了解虛擬主題的用處,可以參考這裏

ActiveMQ有兩種方式來配置虛擬主題。

直接修改Topic名稱法

這種方法的優點在於不需要修改ActiveMQ的配置,只需要發佈者和訂閱者協定好Topic名稱即可。

缺點是如果發佈者和訂閱者已經存在,現在想使用虛擬主題,且可能發佈者或訂閱者不在一個平臺內,那麼就需要雙方統一進行升級或者回滾,比較麻煩。

配置方式:

1. 虛擬主題的名稱需要以『VirtualTopic.』這樣的前綴來命名。比如說我們有一個topic名稱爲orders,那麼就需要把這個主題命名爲VirtualTopic.orders。然後生髮布者將消息發佈到VirtualTopic.orders這個主題中。訂閱者需要以『Consumer.*.VirtualTopic.orders』這樣的方式進行訂閱,注意需要訂閱的是隊列。


攔截器法

這種方法的優點在於發佈者和不需要改造的訂閱者不需要做任何變動,需要改造的訂閱者使用虛擬主題的方式進行訂閱即可達到負載均衡和故障轉移的目的。

缺點也明顯,需要修改ActiveMQ的配置,也就是說需要重啓ActiveMQ,這可能導致消息丟失。還有一個就是訂閱者的上線和回滾也會比較麻煩。後面會介紹我們在上線時候採用的方案。

需要在ActiveMQ的配置文件中進行修改:

<active_mq>/conf/activemq.xml

<destinationInterceptors> 
    <virtualDestinationInterceptor> 
        <virtualDestinations> 
            <virtualTopic name="orders" prefix="VirtualTopicConsumers.*." selectorAware="false"/>    
        </virtualDestinations>
    </virtualDestinationInterceptor> 
</destinationInterceptors>

這個時候發佈者只需要往orders主題上發佈消息。訂閱者通過訂閱VirtualTopicConsumers.A.orders的隊列的方式來消費就可以了。

假如你不需要負載均衡和故障轉移(或者系統自己已經實現了),那麼你仍然可以訂閱orders主題來進行消費即可。

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