ActiveMQ--简介、安装、Hello Word

一、什么是消息中间件

消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件,如老牌的ActiveMQRabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。说白了,就是实现在两个系统或两个客户端之间进行消息传送的中间应用。特点为:利用 高效可靠 的 消息传递机制 进行 与平台无关的 数据交流,并基于 数据通信 来进行分布式系统的集成
在这里插入图片描述

二、什么是ActiveMQ

官网:http://activemq.apache.org/

ActiveMQ是一种开源的基于JMS(Java Message Servie)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的,面向消息的,能够跨越多语言和多系统的应用集成消息通信中间件。ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。

三、ActiveMQ的应用场景

根据消息队列的特点,可以衍生出很多场景,或者说很多场景都能用到。下面举几个例子:

1)异步处理
​ 注册时的短信、邮件通知,减少响应时间;

2)应用解耦
​ 信息发送者和消息接受者无需耦合,比如调用第三方;

3)流量削峰
​ 例如秒杀系统;

4)消息通讯
接受硬件设备上传的信息(GPS坐标等等)

四、ActiveMQ的安装

1.AcitveMQ的数据传送流程

在这里插入图片描述

2.ActiveMQ的两种消息传递类型

(1)点对点传输,即一个生产者对应一个消费者,生产者向broke推送数据,数据存储在broke的一个队列中,当消费者接受该条队列里的数据。
(2)基于发布/订阅模式的传输,即根据订阅话题来接收相应数据,一个生产者可向多个消费者推送数据。

两种消息传递类型的不同,点对点传输消费者可以接收到在连接之前生产者所推送的数据,而基于发布/订阅模式的传输方式消费者只能接收到连接之后生产者推送的数据。

3.ActiveMQ的安装与启动

1.官网下载对应服务器版本
  下载路径:http://activemq.apache.org/download.html
  下载最新安装包,选择Linux版进行下载
在这里插入图片描述
2.解压重命名
1)解压:

root@localhost opt]# rm -rf apache-activemq-5.14.1-bin.tar.gz

2)重命名activeMQ

[root@localhost opt]# mv apache-activemq-5.14.1 ./activeMQ

3)由于启动脚本activemq没有可执行的权限,需要授权(此步可选)

[root@localhost opt]# cd activemq/bin/
[root@localhost bin]# chmod 755 ./activemq

activeMQ需要用到两个端口:61616(消息通讯端口)、8161(管理后台端口,可以在conf/jetty.xml中修改端口号)

4)在防火墙中打开这两个端口

[root@localhost bin]# vi /etc/sysconfig/iptables

插入:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8161 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 61616 -j ACCEPT

重启防火墙:

[root@localhost bin]# service iptables restart

3、启动activeMQ

[root@localhost activeMQ]# ./bin/activemq start

启动错误分析:进入/root/apache-activemq-5.15.9/data目录查看activemq.log文件,根据错误提示信息修改,例如端口号被占用等。

4、验证是否安装成功

[root@localhost activemq]# ./bin/activemq status
INFO: Loading '/opt/activemq//bin/env'
INFO: Using java '/opt/jdk1.7.0_79/bin/java'
ActiveMQ is running (pid '3535')

5、关闭/重启activeMQ

[root@localhost activemq]# ./bin/activemq stop
[root@localhost activemq]# ./bin/activemq restart

6、登录管理后台

http:ActiveMQ启动的服务器ip:8161
在这里插入图片描述
默认初始用户名和密码是admin和admin.

7.管理后台页面介绍

1)Queue消息队列页面
在这里插入图片描述
列表释义:
Name:消息队列的名称。
Number Of Pending Messages:未被消费的消息数目。
Number Of Consumers:消费者的数量。
Messages Enqueued:进入队列的消息 ;进入队列的总消息数目,包括已经被消费的和未被消费的。 这个数量只增不减。
Messages Dequeued:出了队列的消息,可以理解为是被消费掉的消息数量。在Queues里它和进入队列的总数量相等(因为一个消息只会被成功消费一次),如果暂时不等是因为消费者还没来得及消费。

2.Topic主题页面
在这里插入图片描述
列表释义:
Name:主题名称。
Number Of Pending Messages:未被消费的消息数目。
Number Of Consumers:消费者的数量。
Messages Enqueued:进入队列的消息 ;进入队列的总消息数目,包括已经被消费的和未被消费的。 这个数量只增不减。
Messages Dequeued:出了队列的消息,可以理解为是被消费掉的消息数量。在Topics里,因为多消费者从而导致数量会比入队列数高。

3.Subscribers查看订阅者页面
在这里插入图片描述
查看订阅者信息,只在Topics消息类型中这个页面才会有数据。

4.Connections查看连接数页面
在这里插入图片描述

五、在Java中使用ActiveMQ

1.构建maven项目,引入依赖
		<dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.9.0</version>
        </dependency>
2.生产者类
/**
 * @Description 生产者
 * @Date 2019/7/20
 * @Created by yqh
 */
public class MyProducer {

    private static final String ACTIVEMQ_URL = "tcp://192.168.168.242:61616";

    public static void main(String[] args) throws JMSException {
        // 创建连接工厂
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 创建连接
        Connection connection = activeMQConnectionFactory.createConnection();
        // 打开连接
        connection.start();
        // 创建会话
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
        Destination destination = session.createQueue("myQueue");
        // 创建一个生产者
        MessageProducer producer = session.createProducer(destination);
        // 向队列推送10个文本消息数据
        for (int i = 1 ; i <= 10 ; i++){
            // 创建文本消息
            TextMessage message = session.createTextMessage("第" + i + "个文本消息");
            //发送消息
            producer.send(message);
            //在本地打印消息
            System.out.println("已发送的消息:" + message.getText());
        }
        //关闭连接
        connection.close();
    }

}

运行结果:

已发送的消息:第1个文本消息
已发送的消息:第2个文本消息
已发送的消息:第3个文本消息
已发送的消息:第4个文本消息
已发送的消息:第5个文本消息
已发送的消息:第6个文本消息
已发送的消息:第7个文本消息
已发送的消息:第8个文本消息
已发送的消息:第9个文本消息
已发送的消息:第10个文本消息

测试查看web后台显示,有10条消息在队列中等待消费
在这里插入图片描述

3.消费者类
/**
 * @Description 消费者类
 * @Date 2019/7/20 0020
 * @Created by yqh
 */
public class MyConsumer {

    private static final String ACTIVEMQ_URL = "tcp://192.168.168.242:61616";

    public static void main(String[] args) throws JMSException {
        // 创建连接工厂
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 创建连接
        Connection connection = activeMQConnectionFactory.createConnection();
        // 打开连接
        connection.start();
        // 创建会话
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
        Destination destination = session.createQueue("myQueue");
        // 创建消费者
        MessageConsumer consumer = session.createConsumer(destination);
        // 创建消费的监听
        consumer.setMessageListener(new MessageListener() {
            public void onMessage(Message message) {
                TextMessage textMessage = (TextMessage) message;
                try {
                    System.out.println("消费的消息:" + textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

测试结果:

消费的消息:第1个文本消息
消费的消息:第2个文本消息
消费的消息:第3个文本消息
消费的消息:第4个文本消息
消费的消息:第5个文本消息
消费的消息:第6个文本消息
消费的消息:第7个文本消息
消费的消息:第8个文本消息
消费的消息:第9个文本消息
消费的消息:第10个文本消息

web后台显示有一个消费者处于连接状态,且已消费了10个message,而该条队列已没有message待消费了
在这里插入图片描述

4.当我们运行两个消费者类,消息又是怎么被消费的呢?是两个消费者都能收到生产者生产的message,还是只有其中一个消费者能消费呢?

我们先运行两个消费者,在运行一个生产者对目标队列生产10个message,会发现有以下情况

// Consumer1控制台
消费的消息:第1个文本消息
消费的消息:第3个文本消息
消费的消息:第5个文本消息
消费的消息:第7个文本消息
消费的消息:第9个文本消息
// Consumer2控制台
消费的消息:第2个文本消息
消费的消息:第4个文本消息
消费的消息:第6个文本消息
消费的消息:第8个文本消息
消费的消息:第10个文本消息

即队列中的数据会平均的分给每一个消费者消费,且每一条数据只能被消费一次。

5.以上是基于队列点对点的传输类型,以下是基于发布/订阅模式传输的类型测试
/**
 * @Description 基于发布/订阅模式传输类型的生产者测试
 * @Date 2019/7/20 0020
 * @Created by yqh
 */
public class MyProducerForTopic {

    private static final String ACTIVEMQ_URL = "tcp://192.168.168.242:61616";

    public static void main(String[] args) throws JMSException {
        // 创建连接工厂
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 创建连接
        Connection connection = activeMQConnectionFactory.createConnection();
        // 打开连接
        connection.start();
        // 创建会话
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
        Destination destination = session.createTopic("topicTest");
        // 创建一个生产者
        MessageProducer producer = session.createProducer(destination);
        // 向队列推送10个文本消息数据
        for (int i = 1 ; i <= 10 ; i++){
            // 创建文本消息
            TextMessage message = session.createTextMessage("第" + i + "个文本消息");
            //发送消息
            producer.send(message);
            //在本地打印消息
            System.out.println("已发送的消息:" + message.getText());
        }
        //关闭连接
        connection.close();
    }

}
/**
 * @Description 基于发布/订阅模式传输类型的消费者测试
 * @Date 2019/7/20 0020
 * @Created by yqh
 */
public class MyConsumerForTopic {

    private static final String ACTIVEMQ_URL = "tcp://192.168.168.242:61616";

    public static void main(String[] args) throws JMSException {
        // 创建连接工厂
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 创建连接
        Connection connection = activeMQConnectionFactory.createConnection();
        // 打开连接
        connection.start();
        // 创建会话
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
        Destination destination = session.createTopic("topicTest");
        // 创建消费者
        MessageConsumer consumer = session.createConsumer(destination);
        // 创建消费的监听
        consumer.setMessageListener(new MessageListener() {
            public void onMessage(Message message) {
                TextMessage textMessage = (TextMessage) message;
                try {
                    System.out.println("消费的消息:" + textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

现在如果我们先启动生产者,再启动消费者,会发现消费者是无法接收到之前生产者之前所生产的数据,只有消费者先启动,再让生产者发送数据,消费者才可以正常接收数据,这也是发布/订阅的主题模式与点对点的队列模式的一个明显区别。

而如果启动两个消费者,那么每一个消费者都能完整的接收到生产者生产的数据,即每一条数据都被消费了两次,这是发布/订阅的主题模式与点对点的队列模式的另一个明显区别。

六、集成Spring

1、Maven中添加依赖:

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jms</artifactId>
      <version>4.3.11.RELEASE</version>
</dependency>

2、消息生产者模式:
生产者在Spring的配置文件中增加ActiveMQ相关配置,包括命名空间、连接工厂、连接池配置

 	<amq:connectionFactory id="amqConnectionFactory" 
     brokerURL="tcp://127.0.0.1:61616" userName="" password=""/>

    <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
    <bean id="connectionFactory"
          class="org.springframework.jms.connection.CachingConnectionFactory">
        <property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
        <property name="sessionCacheSize" value="100"></property>
    </bean>

定义生产者模式:

 <!-- 定义JmsTemplate的Queue类型 -->
    <bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
        <constructor-arg ref="connectionFactory"></constructor-arg>
        <!-- 队列模式-->
        <property name="pubSubDomain" value="false"></property>
    </bean>

    <!-- 定义JmsTemplate的Topic类型 -->
    <bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
        <constructor-arg ref="connectionFactory"></constructor-arg>
        <!-- 发布订阅模式-->
        <property name="pubSubDomain" value="true"></property>
    </bean>

在Java代码里封装生产者:

@Component("topicSender")
public class TopicSender {

    @Autowired
    @Qualifier("jmsTopicTemplate")
    private JmsTemplate jmsTemplate;

    public void send(String queueName, final String message) {
        jmsTemplate.send(queueName, new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage textMessage = session.createTextMessage(message);
                return textMessage;
            }
        });
    }
}

定义好上述bean对象以后,提供出相应的send()方法,这样就可以在Spring框架的Service层中进行方法的调用。

3、消息消费者模式
同样的需要定义好Jms的连接工厂、连接池配置,这部分同生产者的配置文件保持一致,不同的是需要定义好消费者的消费模板:

	<!-- 定义Topic监听器 -->
    <jms:listener-container destination-type="topic" container-type="default"
                            connection-factory="connectionFactory" acknowledge="auto">
        <jms:listener destination="topicDemo1" ref="topicReceiver1"></jms:listener>
        <jms:listener destination="topicDemo2" ref="topicReceiver2"></jms:listener>
    </jms:listener-container>

    <!-- 定义Queue监听器 -->
    <jms:listener-container destination-type="queue" container-type="default"
                            connection-factory="connectionFactory" acknowledge="auto">
        <jms:listener destination="queueDemo1" ref="queueReceiver1"></jms:listener>
        <jms:listener destination="queueDemo2" ref="queueReceiver2"></jms:listener>
    </jms:listener-container>

在Java代码里封装消费者:

@Component
public class QueueReceiver1 implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            String messgeStr= ((TextMessage) message).getText();
            // ....业务逻辑...
        } catch (JMSException e) {
            e.printStackTrace();
        }

    }
}

4、扩展的P2P模式——请求应答
Request-Response的通信方式很常见,但不是默认提供的。在前面的两种模式中都是一方负责发送消息而另外一方负责处理。实际中的很多应用可能需要一应一答,需要双方都能给对方发送消息。请求-应答方式并不是JMS规范系统默认提供的一种通信方式,而是通过在现有通信方式的基础上稍微运用一点技巧实现的。下图是典型的请求-应答方式的交互过程:
在这里插入图片描述
请求应答
首先在生产者端配置了特定的监听器(同消费者配置方式一致),监听来自消费者的消息,此处注意目的地tempqueue和引用对象ref的配置:

 	<!--接收消费者应答的监听器-->
    <jms:listener-container destination-type="queue" container-type="default"
                            connection-factory="connectionFactory" acknowledge="auto">
        <jms:listener destination="tempqueue" ref="getResponse"></jms:listener>
    </jms:listener-container>

实现该监听器(即上述配置文件里对应的ref),并将该Bean对象声明给Spring容器托管

@Component
public class GetResponse implements MessageListener {
    @Override
    public void onMessage(Message message) {
        String textMsg = null;
        try {
            textMsg = ((TextMessage) message).getText();
            System.out.println("GetResponse accept msg : " + textMsg);
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

在生产者发送方法send()的代码里配置应答的代码:

//配置,告诉消费者如何应答
Destination tempDst = session.createTemporaryQueue();
MessageConsumer responseConsumer = session.createConsumer(tempDst);
responseConsumer.setMessageListener(getResponse);
msg.setJMSReplyTo(tempDst);

同理在消费者这一方需要配置消息生产的模板,方便收到消息后发送应答通知给消息生产方,在Spring配置文件中加入同样的消息发送配置:

  <bean id="jmsConsumerQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
        <constructor-arg ref="connectionFactory"></constructor-arg>
        <!-- 队列模式-->
        <property name="pubSubDomain" value="false"></property>
    </bean>

实现应答发送的方法,然后将该Bean对象交给Spring容器管理,此处需要注意在send()方法中声明的两个参数,参数一对应的是发送的消息内容,参数二封装的时候消息生产者的对象(方便从中获取应答的对象信息)。

@Component
public class ReplyTo {

    @Autowired
    @Qualifier("jmsConsumerQueueTemplate")
    private JmsTemplate jmsTemplate;

    public void send(final String consumerMsg, Message producerMessage)
            throws JMSException {
        jmsTemplate.send(producerMessage.getJMSReplyTo(),
                new MessageCreator() {
                    @Override
                    public Message createMessage(Session session)
                            throws JMSException {
                        Message msg
                                = session.createTextMessage("ReplyTo " + consumerMsg);
                        return msg;
                    }
                });
    }
}

于是在需要应答的消息处理时引入该Bean对象,即可对收到的消息进行应答处理:

@Autowired
private ReplyTo replyTo;

@Override
public void onMessage(Message message) {
    try {
        String textMsg = ((TextMessage) message).getText();
        // do business work;
        replyTo.send(textMsg,message);
    } catch (JMSException e) {
        e.printStackTrace();
    }
}

上面步骤就完成了一个扩展的P2P请求-应答(Request-Response)模式,只是在原先的消息生产者加入监听、消息的消费方加入了针对消息的应答处理逻辑实现。

七、ActiveMQ高阶应用

为了避免意外宕机以后丢失信息,MQ需要做到重启后可以恢复,这里就涉及到持久化机制。ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的:在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等,然后试图将消息发送给接收者,发送成功则将消息从存储中删除,失败则继续尝试。消息中心启动以后首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去。

1–消息的持久化机制

1.JDBC持久化(推荐)

使用JDBC持久化方式,数据库会创建3个表:activemq_msgs,activemq_acks和activemq_lock。
activemq_msgs用于存储消息,Queue和Topic都存储在这个表中。配置持久化的方式,都是修改安装目录下conf/acticvemq.xml文件,首先定义一个mysql-ds的MySQL数据源,然后在persistenceAdapter节点中配置jdbcPersistenceAdapter并且引用刚才定义的数据源。

<beans>
    <broker brokerName="test-broker" persistent="true" xmlns="http://activemq.apache.org/schema/core">
        <persistenceAdapter>
            <jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="false"/>
        </persistenceAdapter>
    </broker>

    <bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/>
        <property name="username" value="activemq"/>
        <property name="password" value="activemq"/>
        <property name="maxActive" value="200"/>
        <property name="poolPreparedStatements" value="true"/>
    </bean>
</beans>
2.AMQ方式(不推荐)

性能高于JDBC,写入消息时,会将消息写入日志文件,由于是顺序追加写,性能很高。为了提升性能,创建消息主键索引,并且提供缓存机制,进一步提升性能。每个日志文件的大小都是有限制的(默认32m,可自行配置)。
虽然AMQ性能略高于下面的Kaha DB方式,但是由于其重建索引时间过长,而且索引文件占用磁盘空间过大,所以已经不推荐使用。

3.KahaDB方式

KahaDB是从ActiveMQ 5.4开始默认的持久化插件,KahaDb恢复时间远远小于其前身AMQ并且使用更少的数据文件,所以可以完全代替AMQ。kahaDB的持久化机制同样是基于日志文件,索引和缓存。

4.LevelDB方式

从ActiveMQ 5.6版本之后,又推出了LevelDB的持久化引擎。目前默认的持久化方式仍然是KahaDB,不过LevelDB持久化性能高于KahaDB,可能是以后的趋势。在ActiveMQ 5.9版本提供了基于LevelDB和Zookeeper的数据复制方式,用于Master-slave方式的首选数据复制方案。

更多资料参考:《ActiveMQ的几种消息持久化机制》

2–消息的持久化订阅

在上述持久化机制中默认是对P2P模式开启了,但是主题订阅模式下如果需要持久化订阅则还需要做一些额外的工作,主要是在消费端这边进行一些特殊处理:
1、设置客户端id:connection.setClientID(“clientID”);

connection.setClientID("Mark");

2、消息的destination变为 Topic (原先是Destination)

Topic destination = session.createTopic("DurableTopic");

消费者类型变为TopicSubscriber

//任意名字,代表订阅名
messageConsumer = session.createDurableSubscriber(destination,"durableSubscriber");

运行一次消费者,将消费者在ActiveMQ上进行一次注册。在ActiveMQ的管理控制台subscribers页面可看见消费者。生产者端这边是不需要做特殊处理,但是需要注意的是生产者可以对消息是否持久化的处理,而这个配置就会影响到下游的消费者是否能进行持久化订阅,配置是取的枚举值而来:

public interface DeliveryMode {
    int NON_PERSISTENT = 1;
    int PERSISTENT = 2;
}
发布了5 篇原创文章 · 获赞 2 · 访问量 590

猜你喜欢

转载自blog.csdn.net/qq_42230770/article/details/104126897