1:ActiveMQ是什么?

ActiveMQ 是一个 MOM,具体来说是一个实现了 JMS 规范的系统间远程通信的消息代理。

MOM 就是面向消息中间件(Message-oriented middleware),是用于以分布式应用或系统中的异步、松耦合、可靠、可扩展和安全通信的一类软件。MOM 的总体思想是它作为消息发送器和消息接收器之间的消息中介,这种中介提供了一个全新水平的松耦合。

JMS 叫做 Java 消息服务(Java Message Service),是 Java 平台上有关面向 MOM 的技术规范,旨在通过提供标准的产生、发送、接收和处理消息的 API 简化企业应用的开发,类似于 JDBC 和关系型数据库通信方式的抽象。

2:jms中相关术语

Provider/MessageProvider:生产者

Consumer/MessageConsumer:消费者

PTP:Point To Point,点对点通信消息模型

Pub/Sub:Publish/Subscribe,发布订阅消息模型

Queue:队列,目标类型之一,和PTP结合

Topic:主题,目标类型之一,和Pub/Sub结合

ConnectionFactory:连接工厂,JMS用它创建连接

Connnection:JMS Client到JMS Provider的连接

Destination:消息目的地,由Session创建

Session:会话,由Connection创建,实质上就是发送、接受消息的一个线程,因此生产者、消费者都是Session创建的

Activemq web info上面的术语

name:目的地

messages enqueued:表示生产了多少条消息

messages dequeued:表示消费了多少条消息

number of consumers:表示在该队列上还有多少消费者在等待接受消息

number of pending messages:表示还有多少条消息没有被消费,实际上是表示消息的积压程度,就是P-C

3:ActiveMQ API

//第一个参数是否支持事务,第二个签收模式

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

中间件 缓存 消息队列_bc

签收模式:

什么是签收?通俗点说,就是消费者接受到消息后,需要告诉消息服务器,我收到消息了。当消息服务器收到回执后,本条消息将失效。因此签收将对PTP模式产生很大影响。如果消费者收到消息后,并不签收,那么本条消息继续有效,很可能会被其他消费者消费掉!

AUTO_ACKNOWLEDGE:表示消费者在receive消息的时候自动签收

CLIENT_ACKNOWLEDGE:表示消费者receive消息后必须手动的调用acknowledge()方法进行签收

DUPS_OK_ACKNOWLEDGE:签不签收无所谓了,只要消费者能够容忍重复的消息接受,当然这样会降低Session的开销

实际中一般使用CLIENT_ACKNOWLEDGE模式,只有成功处理了消息才签收否则可以继续消费消息直到成功处理

关于消息的priority/ttl/deliveryMode

MessageProducer 调用send()可以设置消息的相关属性

ttl:消息的存活时间,一句话:生产者生产了消息,如果消费者不来消费,那么这条消息保持多久的有效期

priority:消息优先级,0-9。0-4是普通消息,5-9是加急消息,消息默认级别是4。注意,消息优先级只是一个理论上的概念,并不能绝对保证优先级高的消息一定被消费者优先消费!也就是说ActiveMQ并不能保证消费的顺序性!

deliveryMode:如果不指定,默认是持久化的消息。如果可以容忍消息的丢失,那么采用非持久化的方式,将会改善性能、减少存储的开销

4:生产者代码实现

package com.cn.mq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class JMSQueueProducer {

    public static void main(String[] args) {

        ConnectionFactory connectionFactory
                = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection=null;
        try {
            connection=connectionFactory.createConnection();
            connection.start();
            //第一个参数 事务,第二个签收模式
            Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
            //创建目的地
            Destination destination = session.createQueue("myQueue");
            //创建发送者
            MessageProducer mp = session.createProducer(destination);
            mp.setDeliveryMode(DeliveryMode.PERSISTENT);
            for(int i =1;i<10;i++){
                //创建需要发送的消息
                TextMessage tm = session.createTextMessage("hello message"+i);
                //数据格式有Text   Map  Bytes  Stream  Object
                mp.send(tm);
            }
            //session.commit();
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if(connection !=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
消费者代码实现
package com.cn.mq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class JMSQueueConsumer {

    public static void main(String[] args) {
        ConnectionFactory factory =new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection=null;
        try {
            connection=factory.createConnection();
            connection.start();
            ////延迟确认
            Session session = connection.createSession(false,Session.DUPS_OK_ACKNOWLEDGE);
            //创建目的地
            Destination destination = session.createQueue("myQueue");
            //创建发送者
            MessageConsumer consumer = session.createConsumer(destination);
            for(int i=1;i<100;i++){
                TextMessage textMessage = (TextMessage) consumer.receive();
                System.out.println(textMessage.getText());
            }
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if(connection !=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

 基于发布订阅模式生产和消费者实现代码如下,多个消费者复制consumer1即可

package com.cn.mq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class JMSTopicProducer {

    public static void main(String[] args) {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection=null;
        try {
            connection=connectionFactory.createConnection();
            //支持事务
            Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
            //创建目的地
            Destination destination = session.createTopic("myTopic");
            //创建发送者
            MessageProducer producer = session.createProducer(destination);
            //持久化
            //producer.setDeliveryMode(DeliveryMode.PERSISTENT);
            //创建需要发送的消息
            TextMessage message = session.createTextMessage("hello topic message ");
            producer.send(message);
            //session.commit();
            producer.close();
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if(connection !=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
package com.cn.mq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class JMSTopicConsumer1 {

    public static void main(String[] args) {

        ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection=null;
        try {
            connection = factory.createConnection();
            connection.start();
            Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
            //创建目的地
            Topic destination=session.createTopic("myTopic");
            //创建消费者
            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();
                    }
                }
            });
            System.out.println("消费者1已经启动");
            System.in.read();
            // 9.关闭资源
            consumer.close();
            session.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(connection !=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

消息类型      是否持久化   是否有Durable订阅者   消费者延迟启动时,消息是否保留   Broker重启时,消息是否保留
Queue                N                            -                                                Y                                                N
Queue                Y                            -                                                 Y                                                Y
Topic                  N                            N                                                N                                                N
Topic                  N                            Y                                                Y                                                 N
Topic                  Y                             N                                                N                                                N
Topic                  Y                            Y                                                  Y                                                Y:

5:ActiveMQ 常用的存储方式

5.1.KahaDB

ActiveMQ 5.3 版本起的默认存储方式。KahaDB存储是一个基于文件的快速存储消息,设计目标是易于使用且尽可能快。它使用基于文件的消息数据库意味着没有第三方数据库的先决条件。

要启用 KahaDB 存储,需要在 activemq.xml 中进行以下配置:

中间件 缓存 消息队列_持久化_02

<broker brokerName="broker" persistent="true" useShutdownHook="false">
        <persistenceAdapter>
                <kahaDB directory="${activemq.data}/kahadb" journalMaxFileLength="16mb"/>
        </persistenceAdapter>
</broker>

中间件 缓存 消息队列_持久化_02

5.2.AMQ

与 KahaDB 存储一样,AMQ存储使用户能够快速启动和运行,因为它不依赖于第三方数据库。AMQ 消息存储库是可靠持久性和高性能索引的事务日志组合,当消息吞吐量是应用程序的主要需求时,该存储是最佳选择。但因为它为每个索引使用两个分开的文件,并且每个 Destination 都有一个索引,所以当你打算在代理中使用数千个队列的时候,不应该使用它。

中间件 缓存 消息队列_持久化_02

<persistenceAdapter>
        <amqPersistenceAdapter
                directory="${activemq.data}/kahadb"
                syncOnWrite="true"
                indexPageSize="16kb"
                indexMaxBinSize="100"
                maxFileLength="10mb" />
</persistenceAdapter>

中间件 缓存 消息队列_持久化_02

 5.3.JDBC

选择关系型数据库,通常的原因是企业已经具备了管理关系型数据的专长,但是它在性能上绝对不优于上述消息存储实现。事实是,许多企业使用关系数据库作为存储,是因为他们更愿意充分利用这些数据库资源。

中间件 缓存 消息队列_持久化_02

<beans>
        <broker brokerName="test-broker" persistent="true" xmlns="http://activemq.apache.org/schema/core">
                <persistenceAdapter>
                        <jdbcPersistenceAdapter dataSource="#mysql-ds"/>
                </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>

中间件 缓存 消息队列_持久化_02

 5.4.内存存储

内存消息存储器将所有持久消息保存在内存中。在仅存储有限数量 Message 的情况下,内存消息存储会很有用,因为 Message 通常会被快速消耗。在 activema.xml 中将 broker 元素上的 persistent 属性设置为 false 即可。

中间件 缓存 消息队列_持久化_02

<broker brokerName="test-broker" persistent="false" xmlns="http://activemq.apache.org/schema/core">
        <transportConnectors>
                <transportConnector uri="tcp://localhost:61635"/>
        </transportConnectors>
</broker>

中间件 缓存 消息队列_持久化_02

6:ActiveMQ 的部署模式

1.单例模式

 

2.无共享主从模式

这是最简单的 Provider 高可用性的方案,主从节点分别存储 Message。从节点需要配置为连接到主节点,并且需要特殊配置其状态。

所有消息命令(消息,确认,订阅,事务等)都从主节点复制到从节点,这种复制发生在主节点对其接收的任何命令生效之前。并且,当主节点收到持久消息,会等待从节点完成消息的处理(通常是持久化到存储),然后再自己完成消息的处理(如持久化到存储)后,再返回对 Producer 的回执。

从节点不启动任何传输,也不能接受任何客户端或网络连接,除非主节点失效。当主节点失效后,从节点自动成为主节点,并且开启传输并接受连接。这是,使用 failover 传输的客户端就会连接到该新主节点。

Broker 连接配置如下:

failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false

但是,这种部署模式有一些限制,

  • 主节点只会在从节点连接到主节点时复制其活动状态,因此当从节点没有连接上主节点之前,任何主节点处理的 Message 或者消息确认都会在主节点失效后丢失。不过你可以通过在主节点设置 waitForSlave 来避免,这样就强制主节点在没有任何一个从节点连接上的情况下接受连接。
  • 就是主节点只能有一个从节点,并且从节点不允许再有其他从节点。
  • 把正在运行的单例配置成无共享主从,或者配置新的从节点时,你都要停止当前服务,修改配置后再重启才能生效。

中间件 缓存 消息队列_中间件 缓存 消息队列_10

在可以接受一些故障停机时间的情况下,可以使用该模式。

从节点配置:

<services>
        <masterConnector remoteURI="tcp://remotehost:62001" userName="Rob" password="Davies"/>
</services>

此外,可以配置 shutdownOnMasterFailure 项,表示主节点失效后安全关闭,保证没有消息丢失,允许管理员维护一个新的从节点。

3.共享存储主从模式

允许多个代理共享存储,但任意时刻只有一个是活动的。这种情况下,当主节点失效时,无需人工干预来维护应用的完整性。另外一个好处就是没有从节点数的限制。

有两种细分模式:

(1)基于数据库

它会获取一个表上的排它锁,以确保没有其他 ActiveMQ 代理可以同时访问数据库。其他未获得锁的代理则处于轮询状态,就会被当做是从节点,不会开启传输也不会接受连接。

中间件 缓存 消息队列_持久化_11


(2)基于文件系统

需要获取分布式共享文件锁,linux 系统下推荐用 GFS2。

中间件 缓存 消息队列_中间件 缓存 消息队列_12

看到这些干货,成小胖欣喜若狂一边听一边记,等老王讲完后他还没记完。而老王则趁机喝了杯铁观音润润嗓子。

在记录完老王所讲的部署模式后,成小胖也不好意思再让老王继续讲下去了,毕竟他知道老王常年加班腰间盘突出,不能长时间站着。

“王哥您坐着休息下,我再给您讲讲我所理解的 ActiveMQ 的网络连接,中不中?”

“中。没事儿,我身体好着呢~”老王知道成小胖担心他的腰,但他还是那个倔脾气。成小胖也不敢多耽误时间,立马开讲。

1.代理网络

支持将 ActiveMQ 消息代理链接到不同拓扑,这就是被人们熟知的代理网络。

ActiveMQ 网络使用存储和转发的概念,其中消息总是存储在本地代理中,然后通过网络转发到另一个代理。

中间件 缓存 消息队列_持久化_13


当连接建立后,远程代理将把包含其所有持久和活动消费者目的地的信息传递给本地代理,本地代理根据信息决定远程代理感兴趣的 Message 并将它发送给远程代理。

如果希望网络是双向的,您可以使用网络连接器将远程代理配置为指向本地代理,或将网络连接器配置为双工,以便双向发送消息。

中间件 缓存 消息队列_持久化_02

<networkConnectors>
        <networkConnector uri="static://(tcp://backoffice:61617)"
                              name="bridge"
                              duplex="true"
                              conduitSubscriptions="true"
                              decreaseNetworkConsumerPriority="false">
        </networkConnector>
</networkConnectors>

中间件 缓存 消息队列_持久化_02

注意,配置的顺序很重要:

    1.网络连接——需要在消息存储前建立好连接,对应 networkConnectors 元素
    2.消息存储——需要在传输前配置好,对应 persistenceAdapter 元素
    3.消息传输——最后配置,对应 transportConnectors 元素

2.网络发现

(1)动态发现

使用多播来支持网络动态发现。配置如下:

<networkConnectors>
    <networkConnector uri="multicast://default"/>
</networkConnectors>

其中,multicast:// 中的默认名称表示该代理所属的组。因此使用此方式时,强烈推荐你使用一个独特的组名,避免你的代理连接到其他不相关代理。

(2)静态发现

静态发现接受代理 URI 列表,并将尝试按列表中确定的顺序连接到远程代理。

<networkConnectors>
    <networkConnector uri="static:(tcp://remote-master:61617,tcp://remote-slave:61617)"/>
</networkConnectors>

相关配置如下:

  • initialReconnectDelay:默认值1000,表示尝试连接前的时延。
  • maxReconnectDelay:默认值30000,表示连接失败后到重新建立连接之间的时延,仅在 useExponentialBackOff 启用时生效。
  • useExponentialBackOff:默认值 true,如果启用,表示每次失败后增加重建连接的时延。
  • backOffMultiplier:默认值2,表示启用 useExponentialBackOff 后每次的时延增量需要注意的是,网络连接将始终尝试建立到远程代理的连接。

需要注意的是,网络连接将始终尝试建立到远程代理的连接。

(3)多连接场景

中间件 缓存 消息队列_持久化_16


当网络负载高时,使用多连接很有意义。但是你需要确保不会重复传递消息,这可以通过过滤器来实现。

中间件 缓存 消息队列_持久化_02

<networkConnectors>
    <networkConnector uri="static://(tcp://remotehost:61617)"
                              name="queues_only"
                              duplex="true"
        <excludedDestinations>
            <topic physicalName=">"/>
        </excludedDestinations>
    </networkConnector>
    <networkConnector uri="static://(tcp://remotehost:61617)"
                              name="topics_only"
                              duplex="true"
        <excludedDestinations>
            <queue physicalName=">"/>
        </excludedDestinations>
    </networkConnector>
</networkConnectors>

中间件 缓存 消息队列_持久化_02