消息中间件--JMS--ActiveMQ--02

本文详细介绍了JMS规范,包括JMS的组成结构、特点以及消息的可靠性。重点讲解了消息持久化、事务和签收机制。同时,探讨了Spring如何整合ActiveMQ,包括使用JmsTemplate和MessageListener。最后,提到了SpringBoot整合ActiveMQ的示例和对比总结。
摘要由CSDN通过智能技术生成

上接: 消息中间件–JMS–ActiveMQ–01

消息中间件–JMS–ActiveMQ–02

6、JMS规范

6.1、什么是JMS

​ JMS,全称Java Message Service,类似于JDBC、JNDI,是JavaEE体系中的一种,规定了两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。

​ 在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。这个中间产品就是上文介绍的MQ,它有许多中落地的产品,详细的对比可以参见第3节。
在这里插入图片描述

6.2、JMS的组成结构和特点

​ 上图是JMS规范的架构图,主要的核心组件一下四个:

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

Consumer:消息消费者,接收和处理JMS消息的客户端应用

JMS服务器(MQ):实现JMS接口和规范的消息中间件,也就是我们说的MQ服务器

Message:封装消息的实体,由三部分组成,消息头、消息体和消息属性。

消息头:对一条消息进行基本属性的设置,常用的消息头属性有:

JMSDestination:消息发送的目的地,主要指Queue和Topic

JMSDeliveryMode:分发模式,用来设置是否持久化消息,默认持久化

JMSExpiration:消息的过期时间,用于持久化消息的过期设置,过期删除消息,默认不过期

JMSPriority:消息优先级,从0-9十个级别,0-4是普通消息5-9是加急消息。JMS不要求MQ严 格按照这十个优先级发送消息,但必须保证加急消息要先于普通消息到达。默认 是4级。

JMSMessageID:每条消息的唯一标识,通常又由MQ产生,也可以使用自定义的唯一标识。

消息体:消息的正文部分,发送和接收的消息体类型必须一致对应。根据格式可以分为以下5种:

TextMessage:普通字符串消息(常用)

MapMessage:一个Map类型的消息,key为String,value为java的基本数据类型(常用)

BytesMessage:二进制数组信息,包含一个byte[]

StreamMessage:java数据流信息,用标准流操作来顺序填充和读取

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

消息属性:消息的拓展属性,属于对消息头的补充。可以将请求头看成一些基本的消息属性,将消 息属性视为自定义的补充,可以根据实际需要进行k-v键值对的设置。

​ 消息头既可以使用MessageProducer进行全局设置,也可以使用Message进行独立设置;

​ 消息体和消息属性因为是针对具体的消息的,所以只能在Message中进行设置。

6.3、JMS的可靠性

​ 对于JMS而言,它是通过消息中间件完成的信息交流,那么JMS怎么保证消息不丢失、不被重复消费、确认被成功消费呢?换句话说,JMS必须要保证上游到下游的消息能够不丢失,准确送达并且不被重复消费。

​ 为了解决这些问题,JMS使用Persistent(持久化)、Trancaction(事务)和Acknowledge(签收)来落地实现。

6.3.1、消息持久化

​ Persistent:持久化,指MQ是否需要将消息进行持久化储存,没有进行持久化保存的数据一旦宕机就会丢失。有两种持久化方式:

​ DeliveryMode.NON_PERSISTENT:1,代表不持久化

​ DeliveryMode.PERSISTENT:2,代表持久化,Queue默认采用这种方式

​ 持久化代码(都是在消息生产者侧进行设置):

//针对消息生产者进行设置,此生产者生产的消息默认都采用这种持久化方式,如果不设置,采用默认模式
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
    
//针对消息进行设置,对某一条消息特殊化设置持久化方式,如果不设置,则使用消息生产者的传递模式
textMessage.setDeliveryMode(DeliveryMode.NON_PERSISTENT)

​ 下面分Queue和Topic进行代码演示:

Queue

​ 生产者:

public class JmsProduce {
   
    private static final String ACTIVEMQ_URL = "tcp://192.168.234.133:61616";
    private static final String QUEUE_NAME = "queue01";
    
    public static void main(String[] args) throws JMSException {
   
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的生产者,并统一设置传递模式
        MessageProducer messageProducer = session.createProducer(queue);
        messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
        //6.启动连接
        connection.start();
        //7.通过使用消息生产者,生产三条消息,发送到MQ的队列里面
        for (int i = 0; i < 3; i++) {
   
            //8.创建消息
            TextMessage textMessage = session.createTextMessage("msg---hello" + i);//理解为一个字符串
            //9.通过messageProducer发送给MQ队列
            messageProducer.send(textMessage);
        }
        //10.关闭资源
        messageProducer.close();
        session.close();
        System.out.println("****消息发布到MQ队列完成");
    }
}

​ 消费者代码不变,参考5.1;

Topic

​ 生产者:

package com.demo.activemq.persist;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * 持久化Topic生产者
 */
public class JmsProducer_Topic_Persist {
   
    private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    private static final String ACTIVEMQ_TOPIC_NAME = "Topic-Persist";

    public static void main(String[] args) throws JMSException {
   
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,持久化的topic必须在生产者创建并设置持久化完成后调用start
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME);
        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(topic);
        //6.设置生产者生产持久化的Topic
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //7.启动连接(启动连接必须在设置完传递模式后)
        connection.start();
        //8.通过使用持久化Topic消息生产者,生产三条消息,发送到MQ的队列里面
        for (int i = 0; i < 3; i++) {
   
            //9.通过session创建消息
            TextMessage textMessage = session.createTextMessage("msg-persist" + i);
            //10.使用指定好目的地的消息生产者发送消息
            messageProducer.send(textMessage);
        }
        //11.关闭资源
        messageProducer.close();
        session.close();
        connection.close();
        System.out.println("****TOPIC_NAME消息发布到MQ完成");
    }
}

​ 消费者:

/**
 * 持久化Topic消费者
 */
public class Jms_Topic_Consumer_Persist {
   
    private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    private static final String ACTIVEMQ_TOPIC_NAME = "Topic-Persist";

    public static void main(String[] args) throws JMSException, IOException {
   
        System.out.println("我是3号消费者王五");
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection,设置connectionID
        Connection connection = activeMQConnectionFactory.createConnection();
        //必须要进行id的设置,否则会报错,因为需要区别每一个订阅者
        connection.setClientID("王五");
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Topic topic = session.createTopic(ACTIVEMQ_TOPIC_NAME);
        //5.通过session创建持久化订阅
        //注意:与非持久化操作不同的是,此处使用的不是MessageConsumer,而是TopicSubscriber
        //因为Topic采用的是发布/订阅模式,当订阅者完成订阅,并且暂时离线的情况下,MQ应当在订阅者上线后将持久化的Topic消息推送给订阅者。
        //MessageConsumer和TopicSubscriber的区别在于,前者只能消费在线期间生产者推送的消息,后者不仅可以消费在线期间生产者推送的消息,也能在重新上线时重新消费不在线期间生产者推送的消息,但是前提是生产者设置了持久化传递模式。
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "我是王五");
        //6.启动连接
        connection.start();
        //7.接收消息
        topicSubscriber.setMessageListener(message -> {
   
            if (message instanceof TextMessage) {
   
                TextMessage textMessage = (TextMessage) message;
                try {
   
                    System.out.println("收到的持久化订阅消息: " + textMessage.getText());
                } catch (JMSException e) {
   
                    e.printStackTrace();
                }
            }
        });

        /**
         * 一定要先运行一次消费者,类似于像MQ注册,我订阅了这个主题
         * 然后再运行主题生产者
         * 无论消费着是否在线,都会接收到,在线的立即接收到,不在线的等下次上线把没接收到的接收
         */
    }
}

Queue和Topic持久化总结:

​ 持久化影响的是MQ对消息的储存方式,默认请款修改Queue和Topic方式的都是采用持久化方式。持久化的消息在MQ关机后不会丢失,非持久化的消息在MQ关机后会丢失,类似于Redis。

​ 两种模式的持久化方式是一致的:

//针对消息生产者进行设置,此生产者生产的消息默认都采用这种持久化方式,如果不设置,采用默认模式
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
    
//针对消息进行设置,对某一条消息特殊化设置持久化方式,如果不设置,则使用消息生产者的传递模式
textMessage.setDeliveryMode(DeliveryMode.NON_PERSISTENT)

​ 对于Queue来讲,它是一种一对一的推送模式,对生产者和消费者的存在顺序没有要求,无论是否持久化,只要数据没有丢失,MQ都会在消费者可用的时候将消息推送过去。因为未持久化的消息会保存在MQ的内存中,不会丢失。类似于给朋友发短信。并且当消费者确认签收之后,MQ就会将该条消息删除。

​ 而对于Topic来讲,它是一种一对多的推送模式,要求订阅者必须在生产者之前存在,没有被订阅的Topic消息会被认定为废消息,从而被删除。类似于微信公众号。订阅者订阅该主题有两种方式:

使用MessageConsumer订阅:暂时订阅,此订阅仅在消费者在线期间有效,也只会接收生产者在其在线期间推送的消息。此时是否持久化Topic都不影响消费者的消费能力。

使用TopicSubscriber订阅:持久化订阅,此订阅永久有效,即使MQ宕机也不会失效,消费者向MQ注册一个身份ID识别号,MQ做持久化保存,当消费者离线时,订阅不会被取消,MQ为此ID保存所有离线期间收到的Topic消息,当消费者重新上线后,MQ会把储存的消息重新推送给消费者,所以这种订阅模式可以接收订阅之后生产者推送的所有消息(在线期间+离线期间),但是如果生产者没有设置Topic消息的持久化,订阅者就不能获得其离线期间生产者推送的消息,因为非持久化的Topic消息不会向Queue那样被MQ保存在内存中,没有被订阅的Topic消息无论是否持久化,都会被认定为废消息,从而被删除。所以,**TopicSubscriber必须配合Topic的持久化才能发挥最大效能。**Topic的消息签收之后也不会被删除。
在这里插入图片描述

注意:无论哪一种模式的订阅,都要保证订阅者先启动!!!

​ 为了保证JMS的可靠性,Queue通常采用持久化传递方式,Topic通常采用持久化传递+TopicSubscriber订阅方式,这样可以保证信息不丢失,并且可以被消费者完全消费,保证了JMS的可靠性。

6.3.2、消息事务和消息签收

​ 消息事务:指客户端(生产者/消费者)与MQ服务器的一次会话期间,发送或者接收消息的一连串操作是一个整体,要么全部成功要么全部失败。

​ 消息签收:指消费者从MQ服务器接收到消息并成功处理之后,给MQ服务器的一个反馈,类似于快递包裹的前后,代表着一次完整消息链的完结,MQ在收到签收消息之后,将释放消息资源,防止重复消费。

​ 消息事务可以应用在生产者端,也可以应用在消费者端;而消息签收只应用在消费者端。

​ 如果在消费者端开启了事务,则消息的签收与否与消息签收的设置无关,完全取决于事务是否被提交。

​ 设置事务和签收的代码:

 Session session = connection.createSession(true, Session.CLIENT_ACKNOWLEDGE);

​ 无论是在消费者端还是生产者端,事务和签收的设置都是在创建session的时候设置的,它们的取值有一下几种:

transacted

​ true ---------- 开启事务,需要手动提交

​ fasle ---------- 不开启事务,自动提交

ack_mode

​ Session.SESSION_TRANSACTED --------- 0,表示根据事务是否提交决定是否签收

​ Session.AUTO_ACKNOWLEDGE --------- 1,表示自动签收

​ Session.CLIENT_ACKNOWLEDGE --------- 2,表示手动签收

​ Session.DUPS_OK_ACKNOWLEDGE --------- 3,表示允许重复签收

​ 下面展示生产者端和消费者端的代码:

生产者端:

package com.demo.activemq.acknowledge;

import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
 
public class Jms_Transaction_AUTOACK_Producer {
   
    private static final String ACTIVEMQ_URL = "tcp://192.168.234.133:61616";
    private static final String ACTIVEMQ_QUEUE_NAME = "Queue-ACK-NoTransaction";
 
    public static void main(String[] args) throws JMSException {
   
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        Connection connection = activeMQConnectionFactory.createConnection();
  		//开启事务,因为生产者端不涉及到消息的获取,所以签收模式可以任意,没有实际意义
        Session session = connecti
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值