ActiveMQ

1、下载安装

linux在activemq.apache.org下载,选择保存文件。自己使用命令解压到自己想要的文件目录中。

在此之前需要确保自己的虚拟机中的确有java环境,因为activemq的底层为java,所以需要java环境支持。

activemq的默认进程端口为61616

命令行效果
ps -ef|grep activemq|grep -v grep查出来的就是结果
ps -ef|grep active会多带一行grep activemq的结果
netstat -anp|grep 61616查询端口是否被使用
lsof -i:61616查询端口号占用情况
./activemq stop/start/启动或者关闭
./activemq start>/soft/activemq.log带运行日志地启动方式

在windows系统使用虚拟机地址:8161/admin/来访问图形化界面,账号密码为admin
在这里插入图片描述

2、java代码连接ActiveMQ

1、队列的写法以及总结

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xjj</groupId>
    <artifactId>activemq_001</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--activemq的依赖-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.9</version>
        </dependency>
        <!--和spring整合的基础包-->
        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.16</version>
        </dependency>
        <!--下面是junit/log4j等基础通用配置-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </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>
    </dependencies>
</project>

procedure端

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @author 徐敬杰
 */
public class Produce {
    public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
    public static final String QUEUE_NAME="queue02";
    public static void main(String[] args) throws JMSException {
        /*1、创建连接工厂,按照给定的url,采用默认的用户名和密码*/
        ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        /*2、通过连接工厂,获得连接connection,并且启动*/
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        /*3、创建session会话,第一个参数为事务,第二个为签收*/
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        /*4、创建队列还是主题的目的地*/
        Queue queue = session.createQueue(QUEUE_NAME);
        /*5、创建消息的生产者*/
        MessageProducer producer = session.createProducer(queue);
        /*6、通过使用消息生产者生产三条消息发送到mq队列里面*/
        for(int i=1;i<=3;i++){
            /*7、创建消息*/
            TextMessage textMessage = session.createTextMessage("msg02--" + i);
            /*8、生产者发送消息*/
            producer.send(textMessage);
        }
        /*9、关闭连接*/
        producer.close();
        session.close();
        connection.close();
        System.out.println("消息发布到mq上了,已经完成");
    }
}

consume端

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @author 徐敬杰
 */
public class Consumer {
    public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
    public static final String QUEUE_NAME="queue02";
    public static void main(String[] args) throws JMSException {
        //1、创建工厂
        ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2、获取连接
        Connection connection=activeMQConnectionFactory.createConnection();
        connection.start();
        //3、创建session
        Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        //4、创建queue
        Queue queue=session.createQueue(QUEUE_NAME);
        //5、创建接收者
        MessageConsumer consumer = session.createConsumer(queue);
        //6、接收消息
        while(true){
            //这种方法会一直等候,一旦有新消息就会直接取出来
            //TextMessage textMessage = (TextMessage)consumer.receive();
            //这种方法当取完了队列中的信息以后会等待4秒,如果时间到了还没有信息就退出程序
            TextMessage textMessage=(TextMessage)consumer.receive(4000L);
            if(textMessage!=null) {
                System.out.println(textMessage.getText());
            } else{
                break;
            }
        }
        consumer.close();
        session.close();
        connection.close();
    }
}

consumer端使用MessageListener

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;

/**
 * @author 徐敬杰
 */
public class Consumer {
    public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
    public static final String QUEUE_NAME="queue02";
    public static void main(String[] args) throws JMSException, IOException, InterruptedException {
        //1、创建工厂
        ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2、获取连接
        Connection connection=activeMQConnectionFactory.createConnection();
        connection.start();
        //3、创建session
        Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        //4、创建queue
        Queue queue=session.createQueue(QUEUE_NAME);
        //5、创建接收者
        MessageConsumer consumer = session.createConsumer(queue);
        consumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                //如果信息不为空,同时也可以强制转型为TextMessage
                if(message!=null&&message instanceof TextMessage){
                    TextMessage textMessage=(TextMessage)message;
                    try {
                        System.out.println("接收到了消息:"+textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //如果使用messageListener的话,就必须暂缓程序,不能直接走完,否则接收不到信息
        //不会自动停止,会无限期等待新的信息到来,类似于不带参数的写法
        //System.in.read();
        //会自动停止,但只停留4s,类似于带参数的写法
        Thread.sleep(4000L);
        consumer.close();
        session.close();
        connection.close();
    }
}

消费者三大消费情况

情况结果
先生产,只启动一号消费者1号可以消费到,2号不行
先生产,先启动一号消费者,再启动二号消费者1号可以消费到,2号不行
先启动两个消费者,再生产6条消息根据谁先启动进入的消费者队列判定,然后大家一条一条轮流交替。

2、topic的使用(写法一致,只是将queue改为了topic)

生产者端

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @author 徐敬杰
 */
public class ProducerTopic {
    public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
    public static final String TOPIC_NAME="topic01";
    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);
        Topic topic=session.createTopic(TOPIC_NAME);
        MessageProducer producer = session.createProducer(topic);
        for(int i=1;i<=3;i++){
            TextMessage message =(TextMessage) session.createTextMessage("topic:" + i);
            producer.send(message);
        }
        producer.close();
        session.close();
        connection.close();
        System.out.println("消息发布到topic成功");
    }
}

消费者端

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @author 徐敬杰
 */
public class ConsumerTopic {
    public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
    public static final String TOPIC_NAME="topic01";
    public static void main(String[] args) throws JMSException {
        System.out.println("启动了一号消费者");
        ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        Connection connection=activeMQConnectionFactory.createConnection();
        connection.start();
        Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        Topic topic=session.createTopic(TOPIC_NAME);
        MessageConsumer consumer = session.createConsumer(topic);
        while(true){
            //这种方法会一直等候,一旦有新消息就会直接取出来
            TextMessage textMessage = (TextMessage)consumer.receive();
            //这种方法当取完了队列中的信息以后会等待4秒,如果时间到了还没有信息就退出程序
            //TextMessage textMessage=(TextMessage)consumer.receive(4000L);
            if(textMessage!=null){
                System.out.println("消息接收成功:"+textMessage.getText());
            } else {
                break;
            }
        }
        consumer.close();
        session.close();
        connection.close();
    }
}
import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;


/**
 * @author 徐敬杰
 */
public class ConsumerTopic {
    public static final String ACTIVEMQ_URL="tcp://192.168.216.128:61616";
    public static final String TOPIC_NAME="topic01";
    public static void main(String[] args) throws JMSException, IOException {
        System.out.println("启动了一号消费者");
        ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        Connection connection=activeMQConnectionFactory.createConnection();
        connection.start();
        Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        Topic topic=session.createTopic(TOPIC_NAME);
        MessageConsumer consumer = session.createConsumer(topic);
        //使用高级特性来写
        consumer.setMessageListener((message) -> {
            if(message!=null&message instanceof TextMessage){
                try {
                    System.out.println("接收到了消息:"+((TextMessage) message).getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
        //如果使用messageListener的话,就必须暂缓程序,不能直接走完,否则接收不到信息
        //不会自动停止,会无限期等待新的信息到来,类似于不带参数的写法
        //System.in.read();
        //会自动停止,但只停留4s,类似于带参数的写法
        Thread.sleep(4000L);
        consumer.close();
        session.close();
        connection.close();
    }
}

3、两大模式比较

比较项目topicqueue
工作模式订阅发布模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息负载均衡模式,如果当前没有消费者,消息也不会丢弃;如果有多个消费者,那么一条消息也只会发送给其中一个消费者,并且要求消费者ack信息
有无状态无状态Queue数据默认会在mq服务器上以文件的形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面。也可以配置成DB存储
传递完整性如果没有订阅者,消息会被丢弃消息不会丢弃
处理效率由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,还要结合不同消息协议自身的性能差异由于一条消息只发送给一个消费者,所以就算消费者再多,性能也不会有明显降低,当然不同消息协议的具体性能也是有差异的。

3、JMS

1、结构

  1. JMS provider:实现JMS接口和规范的消息中间件,也就是我们的MQ服务
  2. JMS producer:消息生产者,创建呵发送JMS消息的客户端应用
  3. JMS consumer:消息消费者,接受和处理JMS消息的客户端应用
  4. JMS message
    1. 消息头(标题)
      • JMSDestination,手动设置地址
      • JMSDeliveryMode,设置是否持久化
      • JMSExpiration,设置过期时间
      • JMSPriority,消息优先级
      • JMSMessageID,唯一识别的id
    2. 消息体(内容)
      • 封装具体的消息数据
      • 5种消息格式
        • TextMessage:普通字符串消息,包含一个String

        • MapMessage:一个Map类型的消息,key为String类型,而值为Java的基本类型

          //发送部分
          MapMessage mapMessage=session.createMapMessage();
          //要放入什么类型的value就调用什么类型的方法
          mapMessage.setString("k1","v1");
          producer.send(mapMessage);
          //接收部分
          if(message!=null&&message instanceof MapMessage){
                              MapMessage mapMessage=(MapMessage)message;
                              try {
                                  //接收参数为同理
                                  System.out.println("接收到了map:"+mapMessage.getString("k1"));
                              } catch (JMSException e) {
                                  e.printStackTrace();
                              }
                          }
          
        • BytesMessage:二进制数组消息,包含一个byte[]

        • StreamMessage:Java数据流消息,用标准流操作来顺序的填充呵读取。

        • ObjectMessage:对象消息,包含一个可序列化的Java对象。

      • 发送和接受的消息体类型必须一致对应
    3. 消息属性
      • 如果需要去除消息头字段以外的值,那么可以使用消息属性

      • 识别/去重/重点标注等操作非常有用的方法

      • 相当于消息头的拓展,所有的消息格式都可以发送消息属性

        //发送消息属性
        for(int i=1;i<=3;i++){
            TextMessage textMessage = session.createTextMessage("msg02--" + i);
            //添加消息属性,可以是别的基本类型
            textMessage.setStringProperty("tezheng","tezheng");
            producer.send(textMessage);
        }
        //接收消息属性
        if(message!=null&&message instanceof TextMessage){
                            TextMessage textMessage=(TextMessage)message;
                            try {
                                System.out.println("接收到了消息:"+textMessage.getText()+"特征为:"+textMessage.getStringProperty("tezheng"));
                            } catch (JMSException e) {
                                e.printStackTrace();
                            }
                        }
        

2、消息的可靠性

  1. 队列持久化

    //队列部分,默认策略为执行持久化
    //取消持久化。直接将信息删除。
    producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
    //执行持久化。没有被消费过的消息,当activemq被关掉以后也还会存在于虚拟机中。
    producer.setDeliveryMode(DeliveryMode.PERSISTENT);
    
  2. 主题持久化

    由于普通的主题进行持久化是没有效果的,因为普通consumer的就是当我程序在启动的状态下,会去接收procedure发送来的消息。所以将consumer改变为subscribe。

    import org.apache.activemq.ActiveMQConnectionFactory;
    
    import javax.jms.*;
    import javax.xml.soap.Text;
    
    /**
     * @author 徐敬杰
     */
    public class TopicProcedurePersistent {
        public static void main(String[] args) throws JMSException {
            ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory("tcp://192.168.216.128:61616");
            Connection connection=activeMQConnectionFactory.createConnection();
            Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
            Topic topic=session.createTopic("topic01");
            MessageProducer messageProducer = session.createProducer(topic);
            //整体流程大致一样,只是将connection.start()往后了几步
            connection.start();
            for(int i=1;i<=3;i++){
                TextMessage message = (TextMessage) session.createTextMessage("topic" + i);
                messageProducer.send(message);
            }
            messageProducer.close();
            session.close();
            connection.close();
            System.out.println("消息发布成功");
        }
    }
    
    import org.apache.activemq.ActiveMQConnectionFactory;
    
    import javax.jms.*;
    
    /**
     * @author 徐敬杰
     */
    public class TopicConsumerPersistent {
        public static void main(String[] args) throws JMSException {
            ActiveMQConnectionFactory activeMQConnectionFactory=new ActiveMQConnectionFactory("tcp://192.168.216.128:61616");
            Connection connection = activeMQConnectionFactory.createConnection();
            //设置我的id,这样子就算我下线了,也会保存记录,方便我下次上线的时候将我订阅的信息发送给我
            connection.setClientID("张三");
            Session session=connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
            Topic topic=session.createTopic("topic01");
            //不能使用普通的consumer,需要改变为subscribe
            TopicSubscriber topicSubscriber=session.createDurableSubscriber(topic,"备注......");
            connection.start();
            TextMessage textMessage=(TextMessage) topicSubscriber.receive();
            while(textMessage!=null){
                System.out.println("接收到了消息:"+textMessage.getText());
                textMessage=(TextMessage) topicSubscriber.receive();
            }
            session.close();
            connection.close();
        }
    }
    
  3. 事务

    生产者:

    false:只要执行send就会进入到队列。关闭事务,那第二个签收参数的设置需要为有效。

    true:先执行send再执行commit,消息才会被真正的提交到队列中。消息需要批量发送,需要缓冲区处理。既能commit,也能rollback。

    消费者:

    false:只要读取了,就会自动将消息给移出队列。

    true:必须要执行commit,否则读取以后也不会将消息移出队列。

  4. 签收

    消费者:

    AUTO_ACKNOWLEDGE:自动签收,当读取了信息以后,自动将信息从队列中取出。

    CLIENT_ACKNOWLEDGE:手动签收,当读取完信息以后,需要调用消息.acknowledge()方法才能将消息从队列中出队。

  5. 签收和事物的关系:事务大于签收

    在事务性会话中,当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息被再次传送。

    非事务性会话中,消息何时被确认取决于创建会话时的应答模式。

3、点对点总结

点对点模型是基于队列的,生产者发消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。者和我们平时给朋友发送短信类似。

如果在Session关闭时有部分消息已被收到但还没有被签收,那当消费者下次连接到相同的队列时,这些消息还会被再次接收。

队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势。

4、发布订阅总结

JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作为topic。

主题可以被认为是消息的传输中介,发布者发布消息到主题。订阅者从主题订阅消息。

主题使得消息订阅者和消息发布者保持相互独立,不需要接触即可保证消息的传送。

非持久订阅:非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收到发送到某个主题的消息。如果消费者处于离线模式,生产者发送的主题消息将会丢失作废,消费者永远不会收到。

持久订阅:客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线状态时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ时,会根据客户的ID得到所有当自己处于离线时生产者发送到主题的消息。

非持久订阅状态下,不能恢复或重新派送一个未签收的消息。

持久订阅才能恢复或重新派送一个未签收的消息。

4、Broker

是什么:相当于一个ActiveMQ的服务器实例。Broker其实就是实现了用代码的形式启动ActiveMQ将MQ嵌入到Java代码中,以便随时用随时启动,在用的时候再去启动这样能节省资源,也保证可靠性。

在activemq的conf目录下有配置文件

可以通过以下的命令来启动

./activemq start xbean:file:/soft/activemq/apache-activemq-5.15.10/conf/activemq02.xml

java自启动一个模拟的activeMQ

<!--需要引入的依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.5</version>
</dependency>
import org.apache.activemq.broker.BrokerService;

/**
 * @author 徐敬杰
 */
public class Broker {
    public static void main(String[] args) throws Exception {
        BrokerService brokerService=new BrokerService();
        brokerService.setUseJmx(true);
        brokerService.addConnector("tcp://localhost:61616");
        brokerService.start();
    }
}

5、Spring整合ActiveMQ

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xjj</groupId>
    <artifactId>SpringActiveMQ</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--activemq的依赖-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.9</version>
        </dependency>
        <!--和spring整合的基础包-->
        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.16</version>
        </dependency>
        <!--配置迷你版的ActiveMQ-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
        <!--Spring对JMS的支持-->
        <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所需要的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>
        <!--下面是junit/log4j等基础通用配置-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
       http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
       http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
    <context:component-scan base-package="com.xjj.activemq"/>
    <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
        <property name="connectionFactory">
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="tcp://192.168.216.128:61616"/>
            </bean>
        </property>
        <property name="maxConnections" value="100"/>
    </bean>
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queue"/>
    </bean>
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-active-topic"/>
    </bean>
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"/>
        <!-- 根据不同情况,选择注入queue还是topic -->
        <property name="defaultDestination" ref="destinationTopic"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
        </property>
    </bean>
</beans>
package com.xjj.activemq;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;

/**
 * @author 徐敬杰
 */
@Component
public class SpringActiveMQ_Producer {
    @Autowired
    private JmsTemplate jmsTemplate;
    public static void main(String[] args){
        ApplicationContext ctx=new ClassPathXmlApplicationContext("application.xml");
        SpringActiveMQ_Producer springActiveMQ_producer=(SpringActiveMQ_Producer)ctx.getBean("springActiveMQ_Producer");
        springActiveMQ_producer.jmsTemplate.send(new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage textMessage=session.createTextMessage("spring和activemq的整合");
                return textMessage;
            }
        });
        System.out.println("添加成功");
    }
}
package com.xjj.activemq;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;

/**
 * @author 徐敬杰
 */
@Component
public class SpringActiveMQ_Consumer {
    @Autowired
    private JmsTemplate jmsTemplate;
    public static void main(String[] args){
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
        SpringActiveMQ_Consumer springActiveMQ_consumer=(SpringActiveMQ_Consumer)applicationContext.getBean("springActiveMQ_Consumer");
        String value =(String) springActiveMQ_consumer.jmsTemplate.receiveAndConvert();
        System.out.println("收到的消息为:"+value);
    }
}

实现消费者无需启动,自动监听队列或者主题

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
       http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
       http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
    <context:component-scan base-package="com.xjj.activemq"/>
    <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
        <property name="connectionFactory">
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="tcp://192.168.216.128:61616"/>
            </bean>
        </property>
        <property name="maxConnections" value="100"/>
    </bean>
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queue"/>
    </bean>
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-active-topic"/>
    </bean>
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"/>
        <property name="defaultDestination" ref="destinationQueue"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"></bean>
        </property>
    </bean>
    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="jmsFactory"/>
       	<!-- 注意监听地址和模板的地址的一致性 -->
        <property name="destination" ref="destinationQueue"/>
        <property name="messageListener" ref="activeMQMessageListener"/>
    </bean>
</beans>
package com.xjj.activemq;

import org.springframework.stereotype.Component;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 * @author 徐敬杰
 */
@Component
public class ActiveMQMessageListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        if(message!=null && message instanceof TextMessage){
            TextMessage textMessage=(TextMessage)message ;
            try {
                System.out.println("OK,获取的信息为:"+((TextMessage) message).getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
}

6、SpringBoot整合ActiveMQ

1、生产者端的写法

  1. 创建SpringBoot选择
    在这里插入图片描述

  2. server:
      port: 7777
    
    spring:
      activemq:
        broker-url: tcp://192.168.216.128:61616
        user: admin
        password: admin
      jms:
        pub-sub-domain: false  #false为queue,true为topic
    
    myqueue: SpringBoot-ActiveMQ-Queue
    
  3. package com.xjj.springbootactivemq.config;
    
    import org.apache.activemq.command.ActiveMQQueue;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.jms.annotation.EnableJms;
    import org.springframework.stereotype.Component;
    
    import javax.jms.Queue;
    
    /**
     * @author 徐敬杰
     */
    @Component
    @EnableJms
    public class Config {
        @Value("${myqueue}")
        private String myQueue;
    
        @Bean
        public Queue queue(){
            return new ActiveMQQueue(myQueue);
        }
    
    }
    
  4. package com.xjj.springbootactivemq.util;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jms.core.JmsMessagingTemplate;
    import org.springframework.stereotype.Component;
    
    import javax.jms.Queue;
    import java.util.UUID;
    
    /**
     * @author 徐敬杰
     */
    @Component
    public class Procedure {
        @Autowired
        private JmsMessagingTemplate jmsMessagingTemplate;
        @Autowired
        private Queue queue;
        //需要使用Junit等单元测试方法调用使用
        public void produceMsg(){
            jmsMessagingTemplate.convertAndSend(queue,"SpringBoot和ActiveMQ整合"+ UUID.randomUUID().toString().substring(0,6));
        }
    }
    

2、消费者的写法

  1. 创建SpringBoot选择
    在这里插入图片描述

  2. server:
      port: 7777
    
    spring:
      activemq:
        broker-url: tcp://192.168.216.128:61616
        user: admin
        password: admin
      jms:
        pub-sub-domain: false  #false为queue,true为topic
    
    myqueue: SpringBoot-ActiveMQ-Queue
    
  3. package com.xjj.springbootactivemq.util;
    
    import org.springframework.jms.annotation.JmsListener;
    import org.springframework.stereotype.Component;
    
    import javax.jms.JMSException;
    import javax.jms.TextMessage;
    
    @Component
    public class Consumer {
        //直接启动主启动类即可开启监听功能
        @JmsListener(destination = "${myqueue}")
        public void receive(TextMessage message) throws JMSException{
            try {
                System.out.println("接收到了信息:"+message.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
    

3、Topic端的procedure

server:
  port: 7777

spring:
  activemq:
    broker-url: tcp://192.168.216.128:61616
    user: admin
    password: admin
  jms:
    pub-sub-domain: true  #false为queue,true为topic

mytopic: SpringBoot-ActiveMQ-Topic
package com.xjj.springbootactivemq.config;

import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;

import javax.jms.Queue;
import javax.jms.Topic;

/**
 * @author 徐敬杰
 */
@Component
@EnableJms
public class Config {
    @Value("${mytopic}")
    private String myTopic;

    @Bean
    public Topic topic(){
        return new ActiveMQTopic(myTopic);
    }

}
package com.xjj.springbootactivemq.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;

import javax.jms.Topic;

/**
 * @author 徐敬杰
 */
@Component
public class Procedure {
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;
    @Autowired
    private Topic topic;
    //需要使用junt等方法来调用测试
    public void sendMsg(){
        jmsMessagingTemplate.convertAndSend(topic,"SpringBoot和ActiveMQ的Topic的demo.....");
        System.out.println("发送成功");
    }
}

4、topic端的consumer

server:
  port: 6666

spring:
  activemq:
    broker-url: tcp://192.168.216.128:61616
    user: admin
    password: admin
  jms:
    pub-sub-domain: true  #false为queue,true为topic

mytopic: SpringBoot-ActiveMQ-Topic
package com.xjj.springbootactivemq.util;


import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

import javax.jms.JMSException;
import javax.jms.TextMessage;

@Component
public class Consumer {
	//直接启动主启动类即可使用
    @JmsListener(destination = "${mytopic}")
    public void listen(TextMessage textMessage) throws JMSException {
        if(textMessage!=null&&textMessage instanceof TextMessage){
            System.out.println("收到的消息为:"+textMessage.getText());
        }
    }
}

7、通讯协议

ActiveMQ支持的client-broker通讯协议有:TCP、NIO、UDP、SSL、Http(s)、VM。

其中的配置Transport Connector在配置文件的标签中

  • TCP,ActiveMQ中默认的消息协议就是openwire
    1. 这是默认的Broker配置,TCP的Client监听端口为61616
    2. 在网络传输数据前,必须序列化数据,消息是通过一个叫wire protocol的来序列化为字节流。默认情况下ActiveMQ把wire protocol叫做OpenWire,他的目的是促使网络上的效率和数据快速交互。
    3. TCP连接的URI形式如:tcp://hostname:port?key=value&key=value,后面的参数为可选项
    4. TCP传输的优点为:
      1. TCP协议传输可靠性高,稳定性强
      2. 高性能,字节流方式传递,效率很高
      3. 有效性,可用性,应用广泛,支持任何平台
  • NIO
    1. NIO协议和TCP协议类似,但是NIO更加侧重于底层的访问操作。它允许开发人员对同一资源可有更多的client调用和服务端有更多的负载。

    2. 适合使用NIO的场景

      1. 可能有大量的Client去连接到Broker上1,一般情况下,大量的Client去连接Broker是被操作系统的线程所限制的。因此,NIO的实现比TCP需要更少的线程去运行,所以建议使用NIO协议
      2. 可能对于Broker有一个很迟钝的网络传输,NIO比TCP提供更好的性能。
    3. NIO的连接形式:nio://hostname:port?key=value

    4. 修改为NIO连接有以下的步骤

      1. 往config文件中添加一行网络连接配置
      2. 将destination的地址中tcp改为nio,61616改为61618
    5. 一阶段为BIO+TCP也就是默认设置,二阶段为NIO+TCP也就是加上了NIO阶段,三阶段为NIO+TCP/Mqtt/stomp实现如下:然后java代码中destination中tcp或者nio可以随意写,而端口号则为61608

      <transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61608?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600&amp;org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&amp;org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50"/>
      

8、消息持久化

前面的消息的可靠性中有三点:持久、事务、签收。但这都是ActiveMQ自带的,如果ActiveMQ死了,那么这些都无用。所以外接消息持久化功能,持久到数据库里面。

为了避免意外宕机以后丢失信息,需要做到重启以后可以恢复消息队列,消息系统一般都采用持久化机制。ActiveMQ的消息持久化机制有JDBC、AMQ、KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的。

就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库等再试图将信息发送给接收者,成功则将消息从存储中删除,失败则继续尝试发送。

消息中心启动以后首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去。

1、连接Mysql

  1. 先在activemq/lib目录下新增一个mysql的连接驱动包

  2. 修改配置文件

    <persistenceAdapter> 
      <jdbcPersistenceAdapter dataSource="#mysql-ds"/> 
    </persistenceAdapter>
    

    dataSource指定要将引用的持久化数据库的bean名称,createTableOnStartup是否在启动的时候创建数据表,默认值是true,这样每次启动都会去创建数据表了,一般是第一次启动的时候设置为true之后改成false

  3. 配置MySQL连接池(粘贴在与之间)

    <bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://192.168.216.7:3306/activemq?relaxAutoCommit=true"/>
      <property name="username" value="root"/>
      <property name="password" value="1234"/>
      <property name="poolPreparedStatements" value="true"/>
    </bean>
    
  4. 如果出现问题可以去data目录下的日志文件中查看

    遇到了以下问题:windows下的数据库不允许远程访问,即拒绝了activemq的连接请求

    通过修改本地mysql来解决问题

    use mysql;
    update user set host = ‘%’ where user = ‘root’;
    flush privileges;
    然后重启mysql和activemq即可
    
  5. 出现以下三张表

    ACTIVEMQ_MSGS:消息表

    列名备注
    ID自增的数据库主键
    CONTAINER消息的Destination
    MSGID_PROD消息发送者的主键
    MSG_SEQ是发送消息的顺序,MSGID_PROD+MSG_SEQ可以组成JMS的MessageID
    EXPIRATION消息的过期时间,存储的是从1970-01-01到现在的毫秒数
    MSG消息本体的Java序列化对象的二进制数据
    PRIORITY优先级,从0-9,数字越大,优先级越高

    ACTIVEMQ_ACKS:用户存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存。

    列名备注
    CONTAINER消息的Destination
    SUB_DEST如果是使用Static集群,这个字段会有集群其他系统的信息
    CLIENT_ID每个订阅者都必须有一个唯一的客户端ID用以区分
    SUB_NAME订阅者名称
    SELECTOR选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性AND和OR操作。
    LAST_ACKED_ID记录消费过的消息的ID。

    ACTIVEMQ_LOCK:表activemq_lock在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker,其他的只能作为备份等待MasterBroker不可用,才可能成为下一个Maste Broker。这个表用于记录哪个Broker是当前的Master Broker。

2、持久化到数据库

在队列模型中:

当DeliveryMode设置为NON_PERSISTENCE时,消息被保存在内存中

当DeliveryMode设置为PERSISTENCE时,消息保存在broker的相应的文件或者数据库中。当数据被消费掉时,便会自动从broker中删除掉。改变的为msg表。

在主题队列中:

修改的为msg(传入的信息)和ack(订阅者的信息)表,同时信息被消费掉以后也不会被删除。

可能会遇到的问题:

  • 数据库jar包:记得需要使用到的相关jar文件放置到ActiveMQ安装目录的lib目录下。mysql-jdbc驱动的jar包和对应的数据库连接池jar包。
  • createTablesOnStartup属性:在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。在启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false。
  • 下划线问题:“java.lang.IllegalStateException:BeanFactory not initialized or already closed”这是因为操作系统的机器名中有"_"符号。请更改机器名并且重启后即可解决问题。

3、journal

这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库和读库。

ActiveMQ Journal,使用了高速缓存写入技术,大大提高了性能。

当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要的写入到DB中的消息。

当生产者生产了1000条信息,这1000条消息会被保存到journal中,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%以上的消息,那么这个时候只需要同步剩余的10%的消息到DB。如果消费者的消费速度很慢,那么这个时候journal文件可以使消息以批量方式写道DB。

<persistenceFactory>
    <journalPersistenceAdapterFactory
                                      journalLogFiles="4"
                                      journalLogFileSize="32768"
                                      useJournal="true"
                                      useQuickJournal="true"
                                      dataSource="#mysql-ds"
                                      dataDirectory="activemq-data"/>
</persistenceFactory>

改的为msg(传入的信息)和ack(订阅者的信息)表,同时信息被消费掉以后也不会被删除。

可能会遇到的问题:

  • 数据库jar包:记得需要使用到的相关jar文件放置到ActiveMQ安装目录的lib目录下。mysql-jdbc驱动的jar包和对应的数据库连接池jar包。
  • createTablesOnStartup属性:在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表。在启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false。
  • 下划线问题:“java.lang.IllegalStateException:BeanFactory not initialized or already closed”这是因为操作系统的机器名中有"_"符号。请更改机器名并且重启后即可解决问题。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值