消息中间件(3)——MQ之ActiveMQ(3)——JMS

目录

1.什么是JMS

2.四种MQ的落地产品的对比

3.JMS的四大组成元素及特点

4.消息可靠性

4.1 持久性

4.2 事务

4.3 签收

4.4 多结点集群

5.点对点和发布订阅的总结

5.1 点对点总结

5.2 发布订阅总结


1.什么是JMS

在解释JMS之前,我们先来看什么是JavaaSE和JavaEE

JavaSE是Java这门编程语言的基础语法部分

JavaEE是一套使用Java进行企业级应用开发的大家一致遵循的13个核心规范工业标准,JavaEE平台提供了一个基于组件的方法来加快设计、开发、装配及部署企业级应用程序

1JDBC(Java Database)数据库连接
2JNDI(Java Naming and Directory Interfaces)  Java的命名和目录接口
3EJB (Enterprise JavaBean) 
4RMI(Remote Method Invoke)远程方法调用
5

Java IDL(Interface Description Languag)/

CORBA(Common Object Broker Architecture)

接口定义语言/公用对象请求代理程序体系结构
6JSP(Java Server Pages) 
7Servlet 
8XML(Extensible Markup Language)可扩展性标记语言
9JMS(Java Message Service)Java消息服务
10JTA(Java Transaction API)Java事务API
11JTS(Java Transaction Service)Java事务服务
12JavaMail 
13JAF(JavaBean Activation Framework) 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

接下来解释什么是Java消息服务(JMS)?

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

2.四种MQ的落地产品的对比

特性

ActiveMQ

RabbitMQ

RocketMQ

Kafka

单机吞吐量

万级,吞吐量比RocketMQ和Kafka要低了一个数量级

万级,吞吐量比RocketMQ和Kafka要低了一个数量级

10万级,RocketMQ也是可以支撑高吞吐的一种MQ

10万级别,这是kafka最大的优点,就是吞吐量高。

 

一般配合大数据类的系统来进行实时数据计算、日志采集等场景

topic数量对吞吐量的影响

 

 

topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降

 

这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic

topic从几十个到几百个的时候,吞吐量会大幅度下降

 

所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源

时效性

ms级

微秒级,这是rabbitmq的一大特点,延迟是最低的

ms级

延迟在ms级以内

可用性

高,基于主从架构实现高可用性

高,基于主从架构实现高可用性

非常高,分布式架构

非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用

消息可靠性

有较低的概率丢失数据

有较低的概率丢失数据

经过参数优化配置,可以做到0丢失

经过参数优化配置,消息可以做到0丢失

消息重复可能有可控制理论上有重复可能有

功能支持

MQ领域的功能极其完备

基于erlang开发,所以并发能力很强,性能极其好,延时很低

MQ功能较为完善,还是分布式的,扩展性好

功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准

优劣势总结

非常成熟,功能强大,在业内大量的公司以及项目中都有应用

 

偶尔会有较低概率丢失消息

 

而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本

 

而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用

 

erlang语言开发,性能极其好,延时很低;

 

吞吐量到万级,MQ功能比较完备

 

而且开源提供的管理界面非常棒,用起来很好用

 

社区相对比较活跃,几乎每个月都发布几个版本分

 

在国内一些互联网公司近几年用rabbitmq也比较多一些

 

但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。

 

而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。

 

而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。

接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障

 

日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景

 

而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控

 

社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码

 

还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的

kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展

 

同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量

 

而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略

 

这个特性天然适合大数据实时计算以及日志收集

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.JMS的四大组成元素及特点

四大组成:

  • JMS provider:实现JMS接口和规范的消息中间件,也就是MQ服务器,如ActiveMQ,RocketMQ等等
  • JMS producer:消息生产者,创建和发送JMS消息的客户端应用
  • JMS consumer:消息消费者,接收和处理JMS消息的客户端应用
  • JMS message:消息

接下来详细介绍JMS message

一个消息由三部分组成:消息头、消息体、消息属性

消息头:

  • JMSDestination
    • 消息发送的目的地,主要是Queue和Topic
  • JMSDeliveryMode
    • 持久和非持久模式
    • 一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递
    • 一条非持久的消息:最多会传送一次,这就意味着服务器出现故障,该消息将永远丢失
  • JMSExpiration
    • 可以设置消息在一定时间后过期,默认永不过期
    • 消息过期时间,等于Destination的send方法中的timeToLive值加上发送时刻的GMT时间值
    • 如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期
    • 如果发送后,在消息过期时间之后消息还没有被发送到目的地,则消息被清除
  • JMSPriority
    • 消息优先级,从0-9十个级别,0到4是普通消息,5到9是加急消息
    • JMS不要求MQ严格按照这10个优先级发送消息,但必须保证加急消息要先于普通消息到达,默认是4级
  • JMSMessageID
    • 唯一识别每个消息的标识,由MQ产生

消息体:封装消息的数据

  • 5种消息体格式
    • TextMessage:普通的字符串消息,包含一个String
    • MapMessage:一个Map类型的消息,key为String类型,而值为Java的基本类型
    • BytesMessage:二进制数组消息,包含一个byte[]
    • StreamMessage:Java数据流消息,用标准流操作来顺序的填充和读取
    • ObjectMessage:对象消息,包含一个可序列化的Java对象
  • 发送和接收的消息体类型必须一致

消息属性:

  • 如果需要除消息头字段以外的值,那么可以使用消息属性
  • 识别/去重/重点标注等操作非常有用的方法
  • 它们是以属性名和属性值对的形式制定的,可以将属性是作为消息头的扩展,属性指定一些消息头没有包括的附加信息,比如可以在属性里指定消息选择器
  • 消息的属性就像可以分配给一条消息的附加消息头一样,它们允许开发者添加有关消息的不透明附加信息,它们还用于暴露消息选择器在消息过滤时使用的数据
  • 例如
    • 生产者
    • 消费者

4.消息可靠性

消息的可靠性有4个角度

  • 持久性
  • 事务
  • 签收
  • 多节点集群

4.1 持久性

参数说明:

  • 非持久性:
    • messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
    • 当服务器宕机,消息不存在
    • 例如:
      • 对于队列,当我们把生产者的持久化模式设置为非持久化模式时,服务器宕机重启后,消息丢失
  • 持久性:
    • messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
    • 当服务器宕机,消息依然存在

持久化的队列:

对于队列,当我们把生产者的持久化模式设置为持久化模式时,服务器宕机重启后,消息仍然存在

当我们不手动设置持久化模式的时候,

  • 结论:
    • 队列的默认传送模式是持久化模式,此模式保证这些消息只被传送一次和成功使用一次,对于这些消息,可靠性是优先考虑的因素
    • 可靠性的另一个重要方面是确保持久性消息传递至目标之后,消息服务在向消费者传送它们之前不会丢失这些数据

持久化的Topic:

生产者

package cn.cqu.activemq.topic;

import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;

public class JmsProducer {

    private static final String ActiveMQ_URL = "tcp://192.168.43.63:61616";
    private static final String TOPIC_NAME = "Topic_Persist";
    public static void main(String[] args) throws JMSException {


        //1.创建连接工厂,按照给定的Url地址,采用默认用户名和密码
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQ_URL);
        //2.通过连接工厂,获得Connection连接并启动访问(此处为javax.jms.Connection)
        Connection connection = connectionFactory.createConnection();

        //3.创建会话Session
        //两个参数:第一个:事务  第二个:签收
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

        //4.创建目的地(队列还是主题)
        Topic topic = (Topic) session.createTopic(TOPIC_NAME);

        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(topic);
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //连接启动放在这里是为了启动持久化的topic
        connection.start();


        //6.通过使用messageProducer生产3条消息发送到MQ的队列当中
        for (int i = 0; i < 3; i++) {
            //7.通过Session创建Message
            TextMessage textMessage = session.createTextMessage("msg----"+i); //可以理解为发送一个字符串给队列
            //8.通过messageProducer发送给队列
            messageProducer.send(textMessage);
        }
        //9.关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("消息发布完成");

    }
}

消费者

package cn.cqu.activemq.topic;

import org.apache.activemq.ActiveMQConnectionFactory;

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

public class JmsConsumer01 {

    private static final String ActiveMQ_URL = "tcp://192.168.43.63:61616";
    private static final String TOPIC_NAME = "Topic_Persist";

    public static void main(String[] args) throws JMSException, IOException {

        System.out.println("消费者1");

        //1.创建连接工厂,按照给定的Url地址,采用默认用户名和密码
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQ_URL);
        //2.通过连接工厂,获得Connection连接并启动访问(此处为javax.jms.Connection)
        Connection connection = connectionFactory.createConnection();
        connection.setClientID("消费者1");
        //3.创建会话Session
        //两个参数:第一个:事务  第二个:签收
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

        //4.创建目的地(队列还是主题)
        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();
    }
}

先启动消费者

再启动生产者

接下来我们演示持久化

先启动消费者

  • 这时候消费者订阅了持久化topic

然后关闭消费者

这时启动生产者

  • 有一个消费者曾经注册过,生产者发送了3条消息,此时消费者1在离线状态

然后再次启动消费者

  • 可以发现消息被消费者重新接收到

总结:

  • 对于持久化的topic,只要先运行一次消费者,等于向MQ进行了注册,订阅了这个主题,然后再运行生产者发送消息,无论消费者是否在线,都会接收到,不在线的话,下次连接的时候,会把没有收过的消息都接收下来
  • 以上这种就类似于微信公众号,只要你订阅了公众号后发的消息,即便你不在线,在你上线以后消息也会马上推送给你

4.2 事务

  • 事务偏生产者,签收偏消费者

  • false
    • 只要执行send,就进入到队列中
    • 关闭事务,那第2个签收参数的设置需要有效
  • true
    • 先执行send再执行commit,消息才被真正地提交到队列中
    • 消息需要批量发送,需要缓冲区处理

生产者的事务:

如果设置为true,没有执行commit

  • 可以看到消息即便被send,也没有发送到队列

所以只要为true,一定要使用commit提交

  • commit以前,所以send的消息都会放在缓冲当中,在commit时,一起将它们提交,这样就可以实现批量地提交

为什么要加事务?

  • 为了保证,批量的send在正常的情况下会一起提交,异常的情况可以回滚,整个操作具有一致性
  • 没有事务,就会每次send都提交,无法保证一致性
        try {
            session.commit();
        }catch (Exception e){
            session.rollback();
            e.printStackTrace();
        }finally {
            session.close();
        }

消费者的事务

如果设置为true,没有执行commit

可以看到明明消费了,在MQ而言,它还是未消费的
当我们关闭消费者再次运行一次

可以发现还会再次消费,即出现了重复消费

所以也要在消费之后,进行提交,保证MQ知道消息已被消费,即提交所有的消费记录给MQ,当有一个没有消费成功,我们也可以进行回滚

而前面将事务置为false,是每消费一个,就会提交给MQ,这样的单个操作本身就是原子的

4.3 签收

以下所使用到的生产者和消费者的代码:

生产者:

package cn.cqu.activemq.queue;


import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;

public class JmsProducer {

    private static final String ActiveMQ_URL = "tcp://192.168.43.63:61616";
    private static final String QUEUE_NAME = "queue01";
    public static void main(String[] args) throws JMSException {


        //1.创建连接工厂,按照给定的Url地址,采用默认用户名和密码
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQ_URL);
        //2.通过连接工厂,获得Connection连接并启动访问(此处为javax.jms.Connection)
        Connection connection = connectionFactory.createConnection();
        connection.start();
        //3.创建会话Session
        //两个参数:第一个:事务  第二个:签收
        Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);

        //4.创建目的地(队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);

        //6.通过使用messageProducer生产3条消息发送到MQ的队列当中
        for (int i = 0; i < 3; i++) {
            //7.通过Session创建Message
            TextMessage textMessage = session.createTextMessage("msg----"+i); //可以理解为发送一个字符串给队列
            //8.通过messageProducer发送给队列
            messageProducer.send(textMessage);
        }

        session.commit();
        //9.关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("消息发布完成");

    }
}

消费者:

package cn.cqu.activemq.queue;

import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;

public class JmsConsumer {

    private static final String ActiveMQ_URL = "tcp://192.168.43.63:61616";
    private static final String QUEUE_NAME = "queue01";

    public static void main(String[] args) throws JMSException, IOException {
        //1.创建连接工厂,按照给定的Url地址,采用默认用户名和密码
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQ_URL);
        //2.通过连接工厂,获得Connection连接并启动访问(此处为javax.jms.Connection)
        Connection connection = connectionFactory.createConnection();
        connection.start();
        //3.创建会话Session
        //两个参数:第一个:事务  第二个:签收
        Session session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE);

        //4.创建目的地(队列还是主题)
        Queue queue = session.createQueue(QUEUE_NAME);

        //5.创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);

        while (true){
            TextMessage textMessage = (TextMessage) messageConsumer.receive(4000L);
            if(textMessage != null){
                System.out.println("消费者接收消息:"+textMessage.getText());
            }else {
                break;
            }

        }

        session.commit();
        //等待上述代码执行完,当我们在键盘随便输入一个字符的时候,这个方法执行完

        messageConsumer.close();

        session.close();
        connection.close();
    }
}

非事务下:

  • 签收模式有3种:
    • 自动签收(默认):即
    • 手动签收:即
      • 客户端需要调用acknowledge方法手动签收
      • 演示:
        • 消费者设置为手动签收
        • 先启动生产者
        • 再启动消费者
        • 关闭消费者再次启动消费者
        • 可以看到设置为手动签收,没有调用acknowledge方法的话,会出现重复消费
        • 使用acnowledge方法对每条消息进行手动签收
        • 再此按照以上步骤,直接看最终结果为:
    • 允许重复消息:即

事务下的签收:

  • 演示1:
    • 有事务并且commit但手动签收,不用acknowledge方法进行签收
    • 先启动生产者,再多次启动消费者,同上
    • 即没有出现重复消费
    • 所以在事务中只要commit后,就会自动签收(即使我们设置了手动签收)
  • 演示2:
    • 有事务但没有commIt,手动签收,使用acknowledge方法签收
    • 使用上述同样的方法,可以发现重复签收

总结:签收和事务的关系

  • 在事务性会话中,当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息会被再次传送
  • 在非事务性会话中,消息何时被确认取决于创建会话时的签收模式

4.4 多结点集群

见我的后续博文,待补充链接

5.点对点和发布订阅的总结

5.1 点对点总结

  • 点对点模型是基于队列的,生产者发消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能,和我们给朋友发短信类似
    • 1.如果在Session关闭时有部分消息已被收到但没有被签收(acknowledged),那当消费者下次连接到相同的队列时,这些消息还会再次接收
    • 2.队列可以长久地保存消息直到消费者收到消息,消费者不需要因为担心消息丢失而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势

5.2 发布订阅总结

  • JMS Pub/Sub模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作topic
  • 主题可以被认为是消息的中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息
  • 主题是的消息订阅者和消息发布者保持互相独立,不需要接触即可保证消息的传送

非持久订阅:

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

持久订阅:

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

当所有的消息必须被接收,则用持久订阅。当丢失消息能够被容忍,则可以用非持久订阅。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值