ActiveMQ简单使用

·  阅读 418

Active MQ

为什么要使用MQ

本文档代码位置码云

未使用MQ之前

1.下游系统新增一些功能,上游系统需要修改大量的代码

2.当大量请求过来时,可能会直接冲垮业务系统

3.当调用的链路请求时,如果有某一个请求响应过慢,会导致请求积压,使业务系统宕机

使用MQ之后

1.下游系统增加新功能时,无需通知上游系统,减少代码的修改----解耦

2.大量请求过来时,MQ充当中间的缓冲,减少对业务系统的压力----削峰

3.当进行多链路调用时,只需将请求异步发送过去,无需等待是否处理完成---异步请求

安装ActiveMQ(Linux)

1.下载

2.上传文件到/opt(第三方文件的目录) 目录下

3.解压缩 tar -zxvf apache-activemq-5.15.9-bin.tar.gz

4.在根目录下创建一个文件夹 mkdir activeMQ

5.将文件复制到新建的文件下cp -r apache-activemq-5.15.9 /activeMQ/

启动

/activeMQ/apache-activemq-5.15.9/bin到这个目录下 执行./activemq start activeMQ

启动失败

INFO: Loading '/activeMQ/apache-activemq-5.15.9//bin/env'
ERROR: Configuration variable JAVA_HOME or JAVACMD is not defined correctly.
       (JAVA_HOME='', JAVACMD='java')

复制代码

修改bin 目录下env的配置

JAVA_HOME="/usr/jdk1.8.0_281" //jdk安装目录
export JAVA_HOME
复制代码
启动成功
INFO: Loading '/opt/apache-activemq-5.15.9//bin/env'
INFO: Using java '/usr/jdk1.8.0_281/bin/java'
INFO: Starting - inspect logfiles specified in logging.properties and log4j.properties to get details
INFO: pidfile created : '/opt/apache-activemq-5.15.9//data/activemq.pid' (pid '10697')

复制代码

activeMQ的默认服务端口号是61616

查看进程ps -ef|grep activemq 或者netstat -anp|grep 61616

1.
root     10697     1  3 00:07 pts/0    00:00:05 /usr/jdk1.8.0_281/bin/java -Xms64M -Xmx1G -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=/opt/apache-activemq-5.15.9//conf/login.config -Dcom.sun.management.jmxremote -Djava.awt.headless=true -Djava.io.tmpdir=/opt/apache-activemq-5.15.9//tmp -Dactivemq.classpath=/opt/apache-activemq-5.15.9//conf:/opt/apache-activemq-5.15.9//../lib/: -Dactivemq.home=/opt/apache-activemq-5.15.9/ -Dactivemq.base=/opt/apache-activemq-5.15.9/ -Dactivemq.conf=/opt/apache-activemq-5.15.9//conf -Dactivemq.data=/opt/apache-activemq-5.15.9//data -jar /opt/apache-activemq-5.15.9//bin/activemq.jar start

2.
tcp6       0      0 :::61616                :::*                    LISTEN      10697/java  
复制代码

重新加载./activemq restart 停止./activemq stop

在浏览器中访问

http://服务器ip:8161/admin 8161 是activeMQ的管理端口 默认的用户名密码都是admin

如果是阿里云服务器需要在阿里云控制台开放端口(8161/61616)

如果开始后还是无法访问

firewall-cmd --zone=public --add-port=61616/tcp --permanent 
								--permanent为永久生效,没有此参数centos重启后该配置将失效
firewall-cmd --zone=public --add-port=8161/tcp --permanent
firewall-cmd --reload
firewall-cmd --list-ports //查看开放的端口

出现下面这样在访问就可以了
6379/tcp 61616/tcp 8161/tcp
复制代码

ActiveMQ 的简单使用

新建maven工程,整合依赖

<dependencies>
        <!--activeMQ 要用的依赖-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.16</version>
        </dependency>
        <!--============-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
复制代码

消费者代码

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:22
 */
public class ActiveMQProducer {
    //activemq的服务地址  这里使用的是tcp协议,源码里可以看到
    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="queue01";
    public static void main(String[] args) throws JMSException {
        //创建连接工厂 ,,按照定的url地址给定默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);
        //通过使用消息生产者messageProducer生产3条消息发送到队列中
        for (int i = 1; i <=3 ; i++) {
            //创建消息   一个字符串消息
            TextMessage textMessage = session.createTextMessage("msg---->" + i);
            //通过messageProducer 发布消息
            messageProducer.send(textMessage);
        }
        //关闭资源
        messageProducer.close();
        session.close();
        connection.close();
        System.out.println("消息发送到MQ成功");
    }
}
复制代码

消费者代码

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:48
 */
public class ActiveMQConsumer {

    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="queue01";
    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        while (true){
            //从队列中获取消息 receive未设置最大时间 是阻塞的,
            TextMessage textMessage = (TextMessage) messageConsumer.receive();
            if (textMessage !=null){
                System.out.println("消费者接受到消息---->"+textMessage.getText());
            }else {
                break;
            }
        }
        messageConsumer.close();
        session.close();
        connection.close();
    }
}
复制代码

消费者监听器方式的代码

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

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

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:48
 */
public class ActiveMQConsumerListener {

    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="queue01";
    public static void main(String[] args) throws JMSException, IOException {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        //通过监听的机制消费消息
        messageConsumer.setMessageListener((message)->{
            if (message !=null && message instanceof TextMessage){
                TextMessage textMessage = (TextMessage) message;
                try {
                    System.out.println("消费者接受到消息---->"+textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
        //不关闭控制台  如果不加这句话,在下面可能在连接的时候直接关闭了,造成无法消费的问题
        System.in.read();
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

复制代码
receive 
	同步阻塞方式,订阅或者接收者调用消费者messageConsumer的receive()方法来接受消息,receive()方法在能够接受到消息之前(或超时之前)将一直阻塞
Listener(监听器)
	异步非阻塞方式(监听器 onMessage()),订阅者或接收者通过messageConsumer的setMessageListener(new MessageListener())注册一个监听器,当消息到达后,系统会自动调用MessageListener的onMessage(Message message)方法
复制代码

问题 (都是在同一个队列中,并且使用的是监听器方式)

  • 先启动生产者 ,然后启动1号消费者,在启动2号消费者,2号消费者可以消费消息吗?

​ 答案:1号消费可以消费,2号不能消费,因为1号已经把所有可以消费的都消费完了,但是2号会一直监听

  • 先启动消费者,在生产6条消息,消费情况是什么样子的?

    答案:会一人一半的消费(类似轮询)

点对点消息传递(队列)总结

  • 每个消息只能有一个消费者,1对1的关系

  • 消息的生产者和消费者之间没有时间上的相关性,无论消费者在生产者发送消息时是否处在运行状态,消费者都可以提取消息

  • 消息被消费后不会存储在队列,所有消费者不会消费到已经被消费的消息

    topic生产者代码

    package com.mango.avtiveMQ;
    
    import org.apache.activemq.ActiveMQConnectionFactory;
    
    import javax.jms.*;
    
    /**
     * @Author:  MangoCookieMilk
     * @Date:     2021/2/28 15:21
     */
    public class ActiveMQProducerTopic {
        //activemq的服务地址  这里使用的是tcp协议,源码里可以看到
        public static final String ACTIVE_URL="tcp://*****:61616";
        public static final String TOPIC_NAME="topic01";
        public static void main(String[] args) throws JMSException {
            //创建连接工厂 ,,按照定的url地址给定默认的用户名和密码
            ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
            //通过连接工厂获取connection连接 并启动访问
            Connection connection = activeMQConnectionFactory.createConnection();
            connection.start();
            //创建会话session  需要两个参数,第一个事务,第二个签收
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            //创建目的地(选择是队列还是主题)
            Topic topic = session.createTopic(TOPIC_NAME);
            //创建消息的生产者
            MessageProducer messageProducer = session.createProducer(topic);
            //通过使用消息生产者messageProducer生产3条消息发送到队列中
            for (int i = 1; i <=3 ; i++) {
                //创建消息   一个字符串消息
                TextMessage textMessage = session.createTextMessage("topic---->" + i);
                //通过messageProducer 发布消息
                messageProducer.send(textMessage);
            }
            //关闭资源
            messageProducer.close();
            session.close();
            connection.close();
            System.out.println("topic消息发送到MQ成功");
        }
    }
    复制代码

    topic消费者代码

    package com.mango.avtiveMQ;
    
    import org.apache.activemq.ActiveMQConnectionFactory;
    
    import javax.jms.*;
    import java.io.IOException;
    
    /**
     * @Author:  MangoCookieMilk
     * @Date:     2021/2/28 15:21
     */
    public class ActiveMQConsumerTopic {
    
        public static final String ACTIVE_URL="tcp://*****:61616";
        public static final String TOPIC_NAME="topic01";
        public static void main(String[] args) throws JMSException, IOException {
            ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
            //通过连接工厂获取connection连接 并启动访问
            Connection connection = activeMQConnectionFactory.createConnection();
            connection.start();
            //创建会话session  需要两个参数,第一个事务,第二个签收
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            //创建目的地(选择是队列还是主题)
            Topic topic = session.createTopic(TOPIC_NAME);
            //创建消息的消费者
            MessageConsumer messageConsumer = session.createConsumer(topic);
            //通过监听的机制消费消息
            messageConsumer.setMessageListener((message)->{
                if (message !=null && message instanceof TextMessage){
                    TextMessage textMessage = (TextMessage) message;
                    try {
                        System.out.println("消费者接受到消息topic---->"+textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            });
            //不关闭控制台  如果不加这句话,在下面可能在连接的时候直接关闭了,造成无法消费的问题
            System.in.read();
            messageConsumer.close();
            session.close();
            connection.close();
        }
    }
    复制代码

topi主题模式下,无论有多少消费者,只要订阅了这个topic,消费者都会拿到同样的数据

发布/订阅总结

  • 生产者将消息发布到topic中,每个消息可以有多个消费者,1对多的关系

  • 生产者与消费者之间有时间上的相关性,订阅某一个主题的消费者只能消费自他订阅之后发布的消息

  • 生产者生产时,topic不保存消息,它是无状态的,加入无人订阅就去生产,就会产生一条废消息,所以一般时先启动消费者,在启动生产者

两种队列模式的对比

topic模式队列queue模式队列
工作模式订阅/发布模式,如果当前没有订阅者,消息将会被丢弃;如果有多个订阅者,那么这些订阅者都会收到消息负载均衡模式,如果当前没有消费者,消息不会丢弃;如果有一个消费者,会把所以的消息消费;如果有多个消费者,会均匀的让多个消费者消费,,会要求ack(签收模式)信息
有无状态无状态queue数据默认是存储在MQ服务器上以文件的方式保存,如ActiveMQ是存放在$AMQ_HOME/data/kr-store/data下;也可以配置成DB存储
传递完整性没有订阅者,消息被丢弃消息不会丢弃
处理效率由于消息需要按照订阅者的数量进行复制,所有处理性能会随着订阅者的数量增加而明显降低,并且要结合不同的消息协议自身的性能差异也不同由于一条消息只发送给一个消费者,所有就算消费者再多,性能不会明显的降低,不过不同消息协议具体的性能也是有差异的

JMS

JMS 是什么

JMS(Java Message Service) Java消息服务,是指两个应用程序之间进行异步通信的API,他为标准消息协议和消息服务提供了一组通用接口,包括创建,发送,读取消息等。用于支持Java应用程序的开发。在JavaEE中,当两个应用程序使用JMS进行通信时,他们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果

JMS的组成:

  • JMS provider 实现JMS接口和规范的中间件,MQ服务器

  • JMS producer 消息生产者,创建和发送消息的客户端应用

  • JMS consumer 消息消费者,接受和处理消息的客户端应用

  • JSM message 具体的消息

    • 消息头
      • JMS Destination 消息发送的目的地,主要就是指Queue和Topic
      • JMS DeliveeryMode 消息发送时的模式
        • **(持久模式)**一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者(具体的MQ服务)出现故障,该消息不会丢失,他会在服务恢复之后再次传递
        • **(非持久模式)**一条非持久消息:最多只会传送一次,这就意味着MQ服务初心故障,该消息会永远丢失
      • JMS Expiration 消息的过期时间
        • 可以设置消息在一定时间后过期,默认是永不过期
        • 消息过期时间,等于Destination的send()方法中的timeToLive值加上发送时刻的GMT时间值
        • 如果timeToLive 值等于0,则JMSExpiration 被设置为0,表示该消息永不过期
        • 如果发送后,在消息过期时间之后消息还没有被发送到指定的目的地,该消息被清除
      • JMS Priority 消息的优先级
        • 从0-9十个级别,0-4是普通消息,5-9是加急消息
        • JMS不要求MQ严格按照这十个优先级发送消息,但是必须保证加急消息要先于普通消息到达
        • 默认的消息级别是4级
      • JMS MessageID 唯一识别每个消息的标识,由MQ产生
    • 消息属性
      • 如果需要除消息头字段以外的值,可以设置消息属性
      • 识别/去重/重点标注等操作非常重要消息的方法
      • 消息属性是以属性名和属性值对的形式制定的。可以将属性看作是消息头的扩展,属性指定一些消息头没有包括的附加消息,比如可以在属性里指定消息的选择器
      • 消息的属性就像是可以分配给一条消息的附加消息头一样。允许开发者添加有关消息不透明附加信息
      • 他们还可以用于暴露消息选择器在消息过滤时使用的数据
      • setXXXProperty()/getXXXProperty(); XXX代表的时java的类型,如String,int ,double,boolean
    • 消息体
      • 封装具体的消息数据
      • 5种消息体格式
        • TextMessage : 普通字符串消息,包含一个String
        • MapMessage :一个Map类型的消息,key为String类型,值为Java的基本类型
        • ByteMessage :二进制消息,包含一个byte[]
        • StreamMessage : Java数据流消息,用标准流操作来顺序的填充和读取
        • ObjectMessage :对象消息,包含一个可序列化的Java对象
      • 发送和接受的消息体类型必须一致对应

    JMS 的可靠性

    • PERSISTENT:持久性

      • 参数设置
        • 非持久 messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
          • 非持久化:当服务器宕机,消息不存在
        • 持久 messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
          • 持久化:当服务器宕机,消息依然存在
      • 持久的Queue
        • 队列默认是持久化的传送模式,此模式保证这些消息只会被传递一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素
        • 可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送他们之前不会丢失这些消息
      • 持久的Topic
        • 一定要先运行一次消费者,等于向MQ注册,订阅这个主题
        • 在运行生产者发送消息,无论消费者是否在线,都会收到消息,如果不在线,下次连接的时候会把没有收到的消息都接受过来
    • 事务

      • producer 提交时的事务
        • false
          • 只要执行send()方法,就会进入到队列中
          • 关闭事务,那第二个签收参数的设置需要有效
        • true
          • 先执行send()方法,在执行commit(),消息才会真正的提交到队列中
          • 消息需要批量发送,需要缓冲区处理
      • 事务偏向生产者,签收偏向消费者
    • Acknowledge:签收

      • 非事务

        • 自动签收(默认):Session.AUTO_ACKNOWLEDGE
        • 手动签收 : Session.CLIENT_ACKNOWLEDGE ,客户端调用acknowledge()方法签收
        • 允许重复消息 :Session.DUPS_OK_ACKNOWLEDGE
      • 事务

        • 生产者事务开启,只有commit后才能将全部消息变为已消费
      • 签收和事务关系

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

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

持久化topic生产者代码

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 17:33
 * topic 持久化生产者
 */
public class ActiveMQProducerTopicPersist {

    //activemq的服务地址  这里使用的是tcp协议,源码里可以看到
    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String TOPIC_NAME="topic-1";
    public static void main(String[] args) throws JMSException {
        //创建连接工厂 ,,按照定的url地址给定默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();

        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Topic topic = session.createTopic(TOPIC_NAME);
        //创建消息的生产者
        MessageProducer messageProducer = session.createProducer(topic);
        //设置传递模式为持久化模式
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
        connection.start();
        //通过使用消息生产者messageProducer生产3条消息发送到队列中
        for (int i = 1; i <=3 ; i++) {
            //创建消息   一个字符串消息
            TextMessage textMessage = session.createTextMessage("topic---->" + i);
            //通过messageProducer 发布消息
            messageProducer.send(textMessage);
        }
        //关闭资源
        messageProducer.close();
        session.close();
        connection.close();
        System.out.println("topic消息发送到MQ成功");
    }
}

复制代码

持久化topic消费者代码

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

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

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 17:27
 * topic持久化消费者
 */
public class ActiveMQConsumerTopicPersist {
    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String TOPIC_NAME="topic-1";
    public static void main(String[] args) throws JMSException, IOException {
        System.out.println("z3");
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.setClientID("z3");
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Topic topic = session.createTopic(TOPIC_NAME);
        //订阅者订阅了那个主题
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark.....");
        connection.start();
        //监听主题
        Message message = topicSubscriber.receive();
        while (null != message){
            TextMessage textMessage = (TextMessage) message;
            System.out.println("收到的持久化topic"+textMessage.getText());
            message = topicSubscriber.receive(5000L);
        }
        session.close();
        connection.close();
    }
}
复制代码

生产者事务的代码(队列模式)

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:22
 */
public class ActiveMQProducer_TX {
    //activemq的服务地址  这里使用的是tcp协议,源码里可以看到
    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="tx";
    public static void main(String[] args) throws JMSException {
        //创建连接工厂 ,,按照定的url地址给定默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);

        //通过使用消息生产者messageProducer生产3条消息发送到队列中
        for (int i = 1; i <=3 ; i++) {
            //创建消息   一个字符串消息
            TextMessage textMessage = session.createTextMessage("msg---->" + i);
            //通过messageProducer 发布消息
            messageProducer.send(textMessage);
        }
        //关闭资源
        messageProducer.close();
        //开启事务后需要手动在session关闭前,手动提交,关闭事务会自动提交
        try {
            //如果都成功提交数据
            session.commit();
        }catch (Exception e){
            e.printStackTrace();
            //有失败的回滚
            session.rollback();
        }finally {
            if (session!=null){
                session.close();
            }
        }
        connection.close();
        System.out.println("tx消息发送到MQ成功");
    }
}
复制代码

消费者事务代码(队列模式)

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:48
 */
public class ActiveMQConsumer_TX {

    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="tx";
    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        while (true){
            //从队列中获取消息  receive未设置最大时间 是阻塞的,
            TextMessage textMessage = (TextMessage) messageConsumer.receive();
            if (textMessage !=null){
                System.out.println("tx消费者接受到消息---->"+textMessage.getText());
            }else {
                break;
            }
        }
        messageConsumer.close();
        //消费者如果事务开启,不commit的话会产生数据重复消费的问题,所以如果开启事务必须要提交
        session.commit();
        session.close();
        connection.close();
    }
}
复制代码

非事务下签收生产者代码(队列模式)

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:22
 */
public class ActiveMQProducer_TX {
    //activemq的服务地址  这里使用的是tcp协议,源码里可以看到
    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="tx";
    public static void main(String[] args) throws JMSException {
        //创建连接工厂 ,,按照定的url地址给定默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);

        //通过使用消息生产者messageProducer生产3条消息发送到队列中
        for (int i = 1; i <=3 ; i++) {
            //创建消息   一个字符串消息
            TextMessage textMessage = session.createTextMessage("msg---->" + i);
            //通过messageProducer 发布消息
            messageProducer.send(textMessage);
        }
        //关闭资源
        messageProducer.close();
        //开启事务后需要手动在session关闭前,手动提交,关闭事务会自动提交
        session.close();
        connection.close();
        System.out.println("tx消息发送到MQ成功");
    }
}
复制代码

非事务下签收消费者代码(队列模式)

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:48
 */
public class ActiveMQConsumer_TX {

    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="tx";
    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        while (true){
            //从队列中获取消息  receive未设置最大时间 是阻塞的,
            TextMessage textMessage = (TextMessage) messageConsumer.receive();
            if (textMessage !=null){
                System.out.println("tx消费者接受到消息---->"+textMessage.getText());
                //手动签收需要调用这个方法,要不然会产生消息重复消费的问题
                textMessage.acknowledge();
            }else {
                break;
            }
        }
        messageConsumer.close();
        session.close();
        connection.close();
    }
}
复制代码

事务下签收生产者代码(队列模式)

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:22
 */
public class ActiveMQProducer_TX {
    //activemq的服务地址  这里使用的是tcp协议,源码里可以看到
    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="tx";
    public static void main(String[] args) throws JMSException {
        //创建连接工厂 ,,按照定的url地址给定默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);

        //通过使用消息生产者messageProducer生产3条消息发送到队列中
        for (int i = 1; i <=3 ; i++) {
            //创建消息   一个字符串消息
            TextMessage textMessage = session.createTextMessage("msg---->" + i);
            //通过messageProducer 发布消息
            messageProducer.send(textMessage);
        }
        //关闭资源
        messageProducer.close();
        //开启事务后需要手动在session关闭前,手动提交,关闭事务会自动提交
        session.commit();
        session.close();
        connection.close();
        System.out.println("tx消息发送到MQ成功");
    }
}
复制代码

事务下签收消费者代码(队列模式)

package com.mango.avtiveMQ;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 13:48
 */
public class ActiveMQConsumer_TX {

    public static final String ACTIVE_URL="tcp://*****:61616";
    public static final String QUEUE_NAME="tx";
    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
        //通过连接工厂获取connection连接 并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //创建会话session  需要两个参数,第一个事务,第二个签收
        Session session = connection.createSession(true, Session.CLIENT_ACKNOWLEDGE);
        //创建目的地(选择是队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        while (true){
            //从队列中获取消息  receive未设置最大时间 是阻塞的,
            TextMessage textMessage = (TextMessage) messageConsumer.receive();
            if (textMessage !=null){
                System.out.println("tx消费者接受到消息---->"+textMessage.getText());
                //手动签收需要调用这个方法,要不然会产生消息重复消费的问题
                //如果开启了事务就会认为已经签收了
                //textMessage.acknowledge();
            }else {
                break;
            }
        }
        messageConsumer.close();
        //消费者如果事务开启,不commit的话会产生数据重复消费的问题,所以如果开启事务必须要提交
        session.commit();
        session.close();
        connection.close();
    }
}
复制代码

JMS 点对点总结

点对点模型是基于队列的,生产者发消息到队列,消费者从队列接受消息,队列的存在使得消息可以进行异步传输

  • 如果在session关闭时有部分消息以已经收到,但是没有签收,那当消费者下次连接到相同的队列时,浙西消息还会被再次接收
  • 队列可以长久的保存消息,知道消费者收到消息。消费者并不需要因为担心消息会丢失而时刻和队列保持激活的状态,充分体现了异步传输模式的优势

JMS 发布/订阅总结

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

  • 主题可以被当作是消息的传输媒介,发布者发布消息到topic上,订阅者从topic上订阅消息

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

  • 订阅

    • 非持久订阅

      • 非持久订阅只有当客户端处于激活状态的时候,也就是个MQ保持连接的状态的才能收到这个主题的消息
      • 如果消费者处于离线状态,生产者发送的主题消息将会被丢失,消费者永远不会收到
      • 先要订阅注册才能接受到发布,只给订阅者发送消息
    • 持久订阅

      • 客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID保存所有发送到主题的消息,当客户端再次连接到MQ时会根据消费者的ID得到所有自己处于离线时发送的主题消息
    • 非持久订阅下,不能恢复或重新派送一个未签收的消息

    • 持久订阅下,可以恢复或者重新派送一个未签收的消息

    如果所有消息必须被接受,就要用持久订阅。

    可以容忍消息被丢失,可以使用非持久订阅

ActiveMQ的Broker

Broker 是什么

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

在ActiveMQ中启动指定配置文件的服务

 ./activemq start xbean:file://activeMQ/apache-activemq-5.15.9/conf/activemq02.xml

启动成功后
INFO: Loading '/activeMQ/apache-activemq-5.15.9//bin/env'
INFO: Using java '/usr/jdk1.8.0_281/bin/java'
INFO: Starting - inspect logfiles specified in logging.properties and log4j.properties to get details
INFO: pidfile created : '/activeMQ/apache-activemq-5.15.9//data/activemq.pid' (pid '20547')

复制代码

嵌入式的Broker

pom.xml中添加一个依赖

不添加会报这个java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.ObjectMapper
<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
 </dependency>
复制代码

代码

package com.mango.avtiveMQ;

import org.apache.activemq.broker.BrokerService;

/**
 * @Author:  MangoCookieMilk
 * @Date:     2021/2/28 19:20
 */
public class EmbedBroker {
    public static void main(String[] args) throws Exception {
        //ActiveMQ支持基于VM中通信基于嵌入式的broker
        BrokerService brokerService = new BrokerService();
        brokerService.setUseJmx(true);
        brokerService.addConnector("tcp://localhost:61616");
        brokerService.start();
    }
}
复制代码

启动成功后

INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[F:\IDEAFile\ActiveMQ\activemq-data\localhost\KahaDB]
 INFO | JMX consoles can connect to service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi
 INFO | Page File: activemq-data\localhost\KahaDB\db.data. Recovering pageFile free list due to prior unclean shutdown..
 INFO | Page File: activemq-data\localhost\KahaDB\db.data. Recovered pageFile free list of size: 0
 INFO | KahaDB is version 6
 INFO | PListStore:[F:\IDEAFile\ActiveMQ\activemq-data\localhost\tmp_storage] started
 INFO | Apache ActiveMQ 5.15.9 (localhost, ID:LAPTOP-LSVM550B-50890-1614512436781-0:1) is starting
 INFO | Listening for connections at: tcp://127.0.0.1:61616
 INFO | Connector tcp://127.0.0.1:61616 started
 INFO | Apache ActiveMQ 5.15.9 (localhost, ID:LAPTOP-LSVM550B-50890-1614512436781-0:1) started
 INFO | For help or more information please see: http://activemq.apache.org
复制代码
分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改