理解面向消息的中间件和 JMS

时间:2022-01-22 23:41:20

本章知识点:

  • 企业消息和面向消息的中间件
  • 理解 Java Message Service(JMS)
  • 使用 JMS APIs 发送和接收消息
  • 消息驱动 bean 的一个例子

简介

一般来说,掌握了企业级消息的历史背景,你就可以更好地理解 ActiveMQ 背后的思想。讨论完企业级消息后,我们将为你展示几个小例子来讲述 JMS。本章的宗旨是带你回顾企业级消息和 JMS 规范。如果你对这些主题已经非常熟悉了,那么你可以跳过本章直接阅读下一章。
曾几何时,每个软件开发者都需要处理应用间的相互通信以及传输数据。对于这类问题,虽然有各式各样的解决方案,但也是取决于你的一致性要求,怎么实现通信从来都是一项重要的需考虑点。商业应用通常要求一致性,这也就影响了最终方案的性能,伸缩性,可靠性等指标。我们今天使用的很多系统都要求这样的一致性,包括:ATMs,航空公司票务预定系统,信用卡系统,销售点系统以及电信系统等。没有这些系统的话,我们今天又是过着怎样的生活呢?
现在想想这些系统是极大地改变了我们的生活,为我们提供了很多的便利。这些系统拥有非常高的可靠性和安全性。这些应用的背后,是由分布式系统,通过事件驱动和使用消息来进行通信组成的。

什么是面向消息的中间件?

这节没啥好废话的。

什么是 Java Message Service?

不得不说,当年的太阳微系统公司真牛逼,开发了 Java 语言不说,还整出了那么多业界规范。
在 JMS 规范中定义的概念有:

  • JMS client:100% 用纯 Java 写的应用用于发送和接收消息;
  • Non-JMS client:使用 JMS provider 的原生客户端 API 写的应用,用于代替 JMS client 来发送和接收消息;
  • JMS producer:创建并发送消息的客户端应用;
  • JMS consumer:接收并处理消息的客户端应用;
  • JMS provider:JMS 接口的实现,一般也是用纯 Java 写的;
  • JMS messge:JMS 中最基本的概念;被 JMS 客户端发送和接收;
  • JMS domains:消息分发的两种风格,即:点对点(point-to-point)和发布/订阅(publish/subscribe);
  • Administered objects:JMS 对象的默认配置,也包含特定提供者的配置数据。客户端一般通过 JNDI 来访问这些配置;
  • Connection factory:客户端使用连接工厂创建到 JMS provider 的连接;
  • Destination:消息被存放的位置,生产者发送消息到这里,消费者从这里消费消息;

JMS 规范

JMS clients

JMS clients 使用 JMS API 来和 JMS provider 进行交互。就像使用 JDBC API 访问关系型数据库一样,JMS clients 使用标准的 JMS API 访问消息服务。许多 JMS providers(包括 ActiveMQ)都提供了 JMS 规范以外的附赠特性,但是使用纯 JMS client 是无法使用这些附赠特性的。想要使用一个 JMS provider 的额外特性,必须得使用特定的 JMS client,但使用了特定的 JMS client 之后就不能再兼容其他 JMS provider了。
JMS 客户端使用 MessageProducer 和 MessageConsumer 接口来发送和接收消息。因此,JMS provider 必须得实现这些接口。发送消息的 JMS client 就是生产者,接收消息的 JMS client 就是消费者。同一个 JMS client 完全可以即当作生产者,也当作消费者。

JMS 生产者

JMS client 使用 JMS 中的 MessageProducer 类来发送消息到一个 destination。当使用 Session.createProducer() 方法来创建一个 producer 时,producer 需要设置一个默认的地址。但是这个地址可以使用 MessageProducer.send() 方法来覆盖。MessageProducer 接口如下:

void setDisableMessageID(boolean value) throws JMSException;

boolean getDisableMessageID() throws JMSException;

void setDisableMessageTimestamp(boolean value) throws JMSException;

boolean getDisableMessageTimestamp() throws JMSException;

void setDeliveryMode(int deliveryMode) throws JMSException;

int getDeliveryMode() throws JMSException;

void setPriority(int defaultPriority) throws JMSException;

int getPriority() throws JMSException;

void setTimeToLive(long timeToLive) throws JMSException;

long getTimeToLive() throws JMSException;

Destination getDestination() throws JMSException;

void close() throws JMSException;

void send(Message message) throws JMSException;

void send(Message message, int deliveryMode, int priority, long timeToLive)
throws JMSException;

void send(Destination destination, Message message) throws JMSException;

void send(
Destination destination,
Message message,
int deliveryMode,
int priority,
long timeToLive)
throws JMSException;

JMS 消费者

JMS clients 使用 JMS 中的 MesssageConsumer 类来从一个 destination 消费消息。MessageConsumer 既可以使用 receive() 方法来同步消费消息,也可以通过提供一个 MessageListener 的实现来异步消费消息。MessageListener.onMessage() 方法将在有消息到达 destination 时被调用。MessageConsumer 接口如下:

package javax.jms;

/**
* @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Wed, 25 Oct 2006) $
*/
public interface MessageConsumer {
String getMessageSelector() throws JMSException;

MessageListener getMessageListener() throws JMSException;

void setMessageListener(MessageListener listener) throws JMSException;

Message receive() throws JMSException;

Message receive(long timeout) throws JMSException;

Message receiveNoWait() throws JMSException;

void close() throws JMSException;
}

MessageConsumer 类本身无法设置 destination。作为替代的是,我们在使用Session.createConsumer()方法创建消费者时,可以设置 destination。

Non-JMS clients

正如前面提到的,一个 non-JMS 客户端使用一个 JMS 提供者的原生客户端 API ,而不是使用 JMS API。这两者之间有很大区别,因为使用原生客户端 API 可以提供一些额外的特性。一些 non-JMS APIs 包含了除了 Java RMI 之外的 CORBA IIOP 协议或者其他的一些原生协议。一般来说,在 JMS 规范诞生之前的消息生产者都会提供一个原生客户端 API,但是许多 JMS 提供者也会提供一个 non-JMS client API。

The JMS provider

一个 JMS provider 是实现了 JMS API 消息中间件。这种实现可以通过标准的 JMS API 来访问消息中间件。

The JMS message

消息是 JMS 规范中最重要的概念。一个消息由消息头和消息体 payload 组成,其中消息头的内容是此消息的元数据,消息体的内容可以为任意类型的内容,包括文本类型和二进制类型。消息体一般很简单灵活,复杂的是消息头。

消息头

JMS 消息支持一套标准的消息头字段列表,JMS 标准 API 提供了访问这些头字段的方法。消息头将被 send() 方法自动调用:

  • JMSDestination:即消息的 destination。对于一个需要从多个 destination 消费消息的客户端,这个字段将是非常有用的;
  • JMSDeliveryMode:JMS 支持两种类型的分发模式:持久型和非持久型。默认的分发模式是持久型。持久型的消息只会分发一次,且必须分发一次;非持久型消息最多被分发一次,如果消息丢失则分发零次。分发模式可以由生产者设置,也可以由每个消息单独设置。
  • JMSExpiration:消息的超时时间。超时时间的值可以由 MessageProducer.setTimeToLive() 方法设置,也可以由 MessageProducer.send() 方法设置。此值默认为 0,表示从不过期。
  • JMSMessageID:消息的唯一标识。设置了这个字段会产生额外的开销,所以可以通过 MessageProducer.setDisableMessageId() 方法设置其为 null 来降低开销。
  • JMSPriority:消息优先级。优先级高的消息将先被分发。
  • JMSTimestamp:代表了生产者发送这条消息的时间,和 JMSMessageID 一样,会产生额外的开销,如果不需要可以通过调用 Producer.setDisableMessageTimestamp() 方法将其设置为 0。

客户端可选消息头

  • JMSCorrelationID:由于关联当前消息到之前的消息。这个头部字段通常用于关联一个响应消息到相应的请求消息。JMSCorrelationID 的值可以是下面几种:一个特定的提供者指定的消息 ID,一个应用指定的字符串,一个提供者提供的原生的字节数组。
  • JMSReplyTo:用于提供一个响应消息需要发送的 destination。这个头部字段通常用于请求、响应模式的消息。
  • JMSType:消息类型。如果消息体是 Java 类型,则此字段什么都不做。

JMS 提供者可选消息头

  • JMSRedelivered:分发出去的消息未收到确认是否重新发送。

消息属性消息头

这些消息头属于自定义消息头。

消息选择器

选择器可以用于消费者中只接收含有特定属性且这些属性的值符合选择器条件的消息。

消息体

JMS 规范定义了六种消息类型:

  1. Message:最基本的消息,此消息没有消息体,只有消息头和消息属性;
  2. TextMessage:消息体是字符串;
  3. MapMessage:消息体是想 Map 一样的键值对集合;
  4. ByetsMessage:消息体是字节数组;
  5. StreamMessage:消息体是 Java 原始类型的流;
  6. ObjectMessage:消息体是序列化后的 Java 对象;

JMS domains

终于到了我一直糊涂的两种模式:点对点和发布/订阅了。

点对点模式

点对点模式就是使用了队列,可以有多个生产者生产消息,该队列也可以有多个消费者,但每个消息只会被一个消费者消费掉,提供者通过轮询来发送每一个消息到每一个消费者。

发布/订阅模式

发布订阅模式就牛逼了,应该是和每个订阅的消费者都建立了一个队列,然后每个到达的消息都会被分发给每个消费者。还有更牛逼的一种是持久化订阅模式,也就是说就算某个消费者掉线了,提供者也得负责把该分发给该消费者的消息持久化保留起来。当这个消费者再次上线后,还是可以接收到它掉线期间提供者分发的消息。
那这里就会有一个问题,如果有个消费者持久化订阅了某个主题,那么不管以后这个消费者是否存在,提供者都得为它保留着未分发的消息啦?当然不是这样的,如果不想提供者一直为它保留消息,需要这个消费者自己退订这个主题啦!

Administered objects

JMS 规范定义了两种类型的 administered objects:ConnectionFactory 和 Destination。

使用 JMS APIs 创建 JMS 应用

两个很简单的小例子。

读完第二章,才发现这本书真的很好很强大啊,真是学习 JMS,ActiveMQ 必读之物啊!