深入浅出 JMS(一) - JMS 基本概念

时间:2022-01-14 05:58:02

深入浅出 JMS(一) - JMS 基本概念

一、JMS 是个什么鬼

JMS 是 Java Message Service 的简称,即 Java 消息服务。什么是消息服务呢,我们来看一下 Oracle 官方的定义:

The Java Message Service (JMS) API is a messaging standard that allows application components based on the Java Platform Enterprise Edition (Java EE) to create, send, receive, and read messages. It enables distributed communication that is loosely coupled, reliable, and asynchronous.

翻译过来就是说,JMS 是一个消息标准,这种标准允许基于 JavaEE 的应用组件可以创建,发送,接受及读取消息。通过 JMS 可以实现松耦合,可依赖及异步的分布式通信。那么在这一段话中我们应该注意两点,即首先这是一个标准,类似于 Servlet 标准,并不是一个具体的实现。其次,这是一种分布式的通信机制,并且是异步的。

JMS 只定义了 Java 中访问消息中间件的接口,并没有给予实现,实现 JMS 接口的消息中间件称为 JMS Provider。如 Apache 的 ActiveMQ、以及阿里巴巴的 RocketMQ。

二、为什么要学习,使用 JMS

在 Jave 中,如果两个应用程序之间对各自都不了解,甚至这两个程序可能部署在不同的大洲上,那么它们之间如何发送消息呢?

当前,CORBA、DCOM、RMI 等 RPC 中间件技术已广泛应用于各个领域。但是面对规模和复杂度都越来越高的分布式系统,这些技术也显示出其 局限性:

  1. 同步通信:客户发出调用后,必须等待服务对象完成处理并返回结果后才能继续执行;
  2. 客户和服务对象的生命周期紧密耦合:客户进程和服务对象进程都必须正常运行,如果由于服务对象崩溃或者网络故障导致客户的请求不可达,客户会接收到异常;
  3. 点对点通信:客户的一次调用只发送给某个单独的目标对象。

面向消息的中间件(Message Oriented Middleware,MoM)较好的解决了以上问题。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者。

面向消息的中间件的优点:

  1. 发送和接收是异步的,发送者无需等待;
  2. 二者的生命周期未必相同;
  3. 发送消息的时候接收者不一定运行,接收消息的时候发送者也不一定运行;
  4. 一对多通信,对于一个消息可以有多个接收者。

举个例子,一个应用程序 A 部署在印度,另一个应用程序部署在美国,然后每当 A 触发某件事后,B 想从 A 获取一些更新信息。当然,也有可能不止一个 B 对 A 的更新信息感兴趣,可能会有 N 个类似 B 的应用程序想从 A 中获取更新的信息。在这种情况下,Jave 提供了最佳的解决方案 JMS,完美解决了上面讨论的问题。

JMS 同样适用于基于事件的应用程序,如聊天服务,它需要一种发布事件机制向所有与服务器连接的客户端发送消息。JMS 与 RMI 不同,发送消息的时候,接收者不需要在线。服务器发送了消息,然后就不管了;等到客户端上线的时候,能保证接收到服务器发送的消息。这是一个很强大的解决方案,能处理当今世界很多普遍问题。

三、JMS有什么优势

  1. 异步:JMS 天生就是异步的,客户端获取消息的时候,不需要主动发送请求,消息会自动发送给可用的客户端。

  2. 可靠:JMS 保证消息只会递送一次。大家都遇到过重复创建消息问题,而 JMS 能帮你避免该问题,只是避免而不是杜绝,所以在一些糟糕的环境下还是有可能会出现重复。

四、JMS 术语

  • 消息中间件(JMS Provider):指提供了对 JMS 协议的第三方组件,比如 ActiveMQ 就是一个消息中间件,另外比较知名的还有 KFA, Rabbit MQ 等;

  • 消息模式:分为点对点(Point-to-Point,即P2P)和发布/订阅(Pub/Sub),对应的数据结构分别是队列(Queue)和主题(Topic);

  • 消息(Message): 通信内容的载体,其结构主要分为消息头,属性和消息体,并且根据存储结构的不同分为好几种,后面会详细提到;

  • 消息生产者(Provider):产生消息的一方,在 P2P 模式下,指消息发送者(Sender),在 P/S 模式下指消息发布者(Publisher);

  • 消息消费者(Consumer):接收消息的一方,对应于两种模式分别是消息接收者(Receiver)和消息订阅者(Subscriber);

五、JMS 消息传送模型

P2P 模式(点对点消息模型)

深入浅出 JMS(一) - JMS 基本概念

涉及到的概念:

  1. 消息队列(Queue)
  2. 发送者(Sender)
  3. 接收者(Receiver)

每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。

P2P 的特点:

  1. 每个消息只有一个消费者(Consumer),即一旦被消费,消息就不再在消息队列中。
  2. 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列。
  3. 接收者在成功接收消息之后需向队列应答成功。

总结:如果你希望发送的每个消息都应该被成功处理的话,那么你需要 P2P 模式。

Pub/Sub 模式(发布/订阅消息传送模型)

深入浅出 JMS(一) - JMS 基本概念

涉及到的概念:

  1. 主题(Topic)
  2. 发布者(Publisher)
  3. 订阅者(Subscriber)

客户端将消息发送到主题。多个发布者将消息发送到 Topic,系统将这些消息传递给多个订阅者。

Pub/Sub 的特点:

  1. 每个消息可以有多个消费者。
  2. 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态。
  3. 为了缓和这样严格的时间相关性,JMS 允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。

总结:如果你希望发送的消息可以不被做任何处理、或者被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用 Pub/Sub 模型

六、消息的消费

在 JMS 中,消息的产生和消费是异步的。对于消费来说,JMS 的消费者可以通过两种方式来消费消息。

  • 同步:订阅者或接收者调用 receive 方法来接收消息,receive 方法在能够接收到消息之前(或超时之前)将一直阻塞。

    consumer = session.createConsumer(destination);
    Message message = consumer.receive();
  • 异步:订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的 onMessage 方法。

    consumer.setMessageListener(new MessageListener(){
    @Override
    public void onMessage(Message message) {
    TextMessage textMessage = (TextMessage)message;
    try {
    String value = textMessage.getText();
    System.out.println("value: "+value);
    } catch (JMSException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    });

七、JMS 编程接口

在 JMS 的标准协议里,有几个重要的接口,先简单罗列如下:

  1. ConnectionFactory(连接工厂):创建 Connection 的工厂,通过这个对象来创建一个到某个消息服务的连接。

    用户用来创建到JMs提供者的连接的被管对象。JMS 客户通过可移植的接口访问连接,这样当下层的实现改变时,代码不需要进行修改。管理员在 JNDI 名字空间中配置连接工厂,这样,JMS 客户才能够查找到它们。根据消息类型的不同,用户将使用队列连接工厂,或者主题连接工厂。

  2. Connection(连接) :一个具体的连接,由 ConnectionFactory 创建。

    连接代表了应用程序和消息服务器之间的通信链路。在获得了连接工厂后,就可以创建一个与 JMS 提供者的连接。根据不同的连接类型,连接允许用户创建会话,以发送和接收队列和主题到目标。

  3. Destination(目标) :消息存储的位置,发送者把消息发送到指定位置,消费者从指定位置取消息,那么这个指定位置可能是一个 topic 也可能是一个 queue,由这个来表示。

    目标是一个包装了消息目标标识符的被管对象,消息目标是指消息发布和接收的地点,或者是队列,或者是主题。JMS 管理员创建这些对象,然后用户通过 JNDI 发现它们。和连接工厂一样,管理员可以创建两种类型的目标,点对点模型的队列,以及发布者/订阅者模型的主题。

  4. Session(会话) :一个发送或接收消息的线程,由 Connection 创建的用于操作消息的接口,本接口可以直接用来创建消息的生产者对象。

    表示一个单线程的上下文,用于发送和接收消息。由于会话是单线程的,所以消息是连续的,就是说消息是按照发送的顺序一个一个接收的。会话的好处是它支持事务。如果用户选择了事务支持,会话上下文将保存一组消息,直到事务被提交才发送这些消息。在提交事务之前,用户可以使用回滚操作取消这些消息。一个会话允许用户创建消息生产者来发送消息,创建消息消费者来接收消息。

  5. MessageProducer(消息生产者) :消息的生产者,包括 QueueSender 和 TopicPublisher

    由会话创建的对象,用于发送消息到目标。用户可以创建某个目标的发送者,也可以创建一个通用的发送者,在发送消息时指定目标。

  6. MessageConsumer(消息消费者) :消息的消费者, 包括 QueueReceiver 和 TopicSubscriber

    会话创建的对象,用于接收发送到目标的消息。消费者可以同步地(阻模式),或异步(非阻寒)接收队列和主题类型的消息。

  7. MessageListener(消息监听器) :提供给消费者监听消息使用的,在添加了某个监听器之后,一旦消费到达,则会调用其 onMessage 方法。

  8. Message接口(消息):是在消费者和生产者之间传送的对象,也就是说从一个应用程序创送到另一个应用程序。

深入浅出 JMS(一) - JMS 基本概念

八、JMS 消息结构

JMS 客户端使用 JMS 消息与系统通讯,JMS 消息虽然格式简单但是非常灵活。一个消息有三个主要部分:

  • 消息头(必须):包含用于识别和为消息寻找路由的操作设置;
  • 一组消息属性(可选):包含额外的属性,支持其他提供者和用户的兼容。可以创建定制的字段和过滤器(消息选择器);
  • 一个消息体(可选):允许用户创建五种类型的消息(文本消息,映射消息,字节消思,流消息和对象消息)。

(1) 消息头

JMS 消息头预定义了若干字段用于客户端与JMS提供者之间识别和发送消息,预编译头如下:

  • JMSDestination:消息发送的目的地,主要是指 Queue 和 Topic,由 session 创建而由生产者的 send 方法设置。

  • JMSDeliveryMode:传送模式,有两种即久模式和非持久模式。一条持久性的消息应该被传输"一次仅仅一次",这就意味着如果 JMS 提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。一条非持久的消息最多会传递一次,这意味着服务器出现故障,该消息将永远丢失。由 session 创建而由消息生产者的 send 方法设置。

  • JMSMessageID:唯一识别每个消息的标识,由 JMS 消息生产者产生。由 send 方法设置。

  • JMSTimestamp:一个 JMS Provider 在调用 send() 方法时自动设置,它是消息被发送和消费者实际接收的时间差。由客户端设置。

  • JMSCorrelationI`:用来连接到另外一个消息,典型的应用是在回复消息中连接到原消息。在大多数情况下,JMSCorrelationID 用于将一条消息标记为对 JMSMessageID 标示的上一条消息的应答,不过,JMSCorrelationID 可以是任何值,不仅仅是 JMSMessageID。由客户端设置

  • JMSReplyTo:提供本消息回复消息的目的地址,由客户端设置。

  • JMSRedelivered:如果一个客户端收到一个设置了 JMSRedelivered 属性的消息,则表示可能客户端曾经在早些时候收到过该消息,但并没有签收(acknowledged)。如果该消息被重新传送,JMSRedelivered=true 否则 JMSRedelivered=flase 。由 JMS Provider 设置。

  • JMSType:消息类型的标识符,由客户端设置。

  • JMSExpiration:消息过期时间,等于 Destination 的 send 方法中的 timeToLive值加上发送时刻的GMT的时间值。如果timeToLive 值等于零,则 JMSExpiration 被设置为零,表示该消息永不过期。如果发送后,在消息过期时间之后消息还没有被发送到目的地,则该消息被清除。由 send 方法设置。

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

一个消息的消息头有这些属性,我们可以按照需要对这个消息的消息进行设计,在将这个消息使用消息生产者的 send() 方法发送到消息服务上。

(2) 消息属性

我们可以给消息设置自定义属性,这些属性主要是提供给应用程序的。对于实现消息过滤功能,消息属性非常有用,JMS API 定义了一些标准属性,JMS 服务提供者可以选择性的提供部分标准属性。

(3) 消息体

在消息体中,JMS API 定义了五种类型的消息格式,让我们可以以不同的形式发送和接受消息,并提供了对已有消息格式的兼容。不同的消息类型如下:

  1. Text message: javax.jms.TextMessage,表示一个文本对象。
  2. Object message: javax.jms.ObjectMessage,表示一个JAVA对象。
  3. Bytes message: javax.jms.BytesMessage,表示字节数据。
  4. Stream message:javax.jms.StreamMessage,表示java原始值数据流。
  5. Map message: javax.jms.MapMessage,表示键值对。