消息中间件原理及JMS简介

现今,越来越多的企业面临着各种各样的数据集成和系统整合,CORBA、DCOM、RMI等RPC中间件技术也应运而生,但由于采用RPC同步处理技术,在性能、健壮性、可扩展性上都存在着诸多缺点。而基于消息的异步处理模型采用非阻塞的调用特性,发送者将消息发送给消息服务器,消息服务器在合适的时候再将消息转发给接收者;发送和接收是异步的,发送者无需等待,二者的生命周期也可以不必相同,而且发送者可以将消息间接传给多个接收者,大大提高了程序的性能、可扩展性及健壮性,这使得异步处理模型在分布式应用上比起同步处理模型更具有吸引力。[5]

本文首先介绍了消息中间件的原理,然后介绍了目前流行的消息中间件产品和一些开源实现。最后详细分析了SUN及其伙伴公司提出的旨在统一各种消息中间件系统接口的规范(JMS)。

关键字:消息中间件,JMS,点对点,发布/订阅

一、中间件简介
1.1 中间件的定义

中间件(middleware)是基础软件的一大类,属于可复用的软件范畴。中间件在操作系统软件,网络和数据库之上,应用软件之下,总的作用是为处于自己上层的应用软件提供运行于开发的环境,帮助用户灵活、高效的开发和集成复杂的应用软件。[1]

IDC对中间件的定义为:中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源,中间件定位于客户机服务器的操作系统之上,管理计算机资源和网络通信。

因而中间件是指一类软件,是基于分布式处理的软件,最突出的特点是其网络通信功能。也可认为中间件是位于平台和应用之间的通用服务,这些服务具有标准的程序接口和协议。针对不同的操作系统和硬件平台,可以有符合接口和协议的多种实现。[1]



图1.1 中间件

1.2 中间件的分类

按照IDC的分类方法,中间件可分为六类:[1]

1) 终端仿真/屏幕转换

2) 数据访问中间件(UDA)

3) 远程过程调用中间件(RPC)

4) 消息中间件(MOM)

5) 交易中间件(TPM)

6) 对象中间件

然而在实际应用中,一般将中间件分为两大类:一类是底层中间件,用于支撑单个应用系统或解决一类问题,包括交易中间件、应用服务器、消息中间件、数据访问中间件等;另一类是高层中间件,更多的用于系统整合,包括企业应用集成中间件、工作流中间件、门户中间件等,他们通常会与多个应用系统打交道,在系统中层次较高,并大多基于前一类的底层中间件运行。

1.2.1 终端仿真/屏幕转换

此类中间件用于实现客户机图形用户接口与已有的字符接口方式的服务器应用程序之间的互操作,应用与早期的大型机系统,现在已很少使用。

1.2.2 数据访问中间件

此类中间件是为了建立数据应用资源互操作的模式,对异构环境下的数据库或文件系统实现联接。

1.2.3 远程过程调用中间件

此类中间件可以使开发人员在需要时调用位于远端服务器上的过程,屏蔽了在调用过程中的通信细节。一个应用程序使用RPC来远程执行一个位于不同地址空间里的过程,在效果上看和执行本地调用相同。

1.2.4 交易中间件

此类中间件是专门针对联机交易系统而设计的。联机交易系统需要处理大量并发进程,处理并发涉及到操作系统,文件系统,编程语言,数据通信,数据库系统,系统管理,应用软件等。而交易中间件根据分布式交易处理的标准及参考模型,对资源管理,交易管理和应用进行了实现,从而使得基于交易中间件开发应用程序更为简单。交易中间件基本上只适用于联机交易系统,是一种较为专用的中间件。

1.2.5 消息中间件

此类中间件是指利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。

消息中间件可以即支持同步方式,又支持异步方式。异步中间件比同步中间件具有更强的容错性,在系统故障时可以保证消息的正常传输。异步中间件技术又分为两类:广播方式和发布/订阅方式。由于发布/订阅方式可以指定哪种类型的用户可以接受哪种类型的消息,更加有针对性,事实上已成为异步中间件的非正式标准。目前主流的消息中间件产品有IBM的MQSeries,BEA的MessageQ和Sun的JMS等[1]。

1.2.6 对象中间件

传统的对象技术通过封装、继承及多态提供了良好的代码重用功能。但这些对象只存在与一个程序中,外界并不知道它们的存在,也无法访问它们。对象中间件提供了一个标准的构建框架,能使不同厂家的软件通过不同的地址空间,网络和操作系统实现交互访问。对象中间件的目标是为软件用户及开发者提供一种应用级的即插即用的互操作性。目前主流的对象中间件有OMG的CORBA,Microsoft 的COM以及IBM的SOM,Sun的RMI等。

1.3 中间件的特点

一般来讲,中间件具有以下一些特点:满足大量应用的需求,运行于多种硬件和操作系统平台,支持分布式计算,支持标准接口和协议。开发人员通过调用中间件提供的大量API,实现异构环境的通信,从而屏蔽异构系统中复杂的操作系统和网络协议。

由于标准接口对于可移植性和标准协议对于互操作性的重要性,中间件已成为许多标准化工作的主要部分。分布式应用软件借助中间件可以在不同的技术之间共享资源。

总的来说,中间件屏蔽了底层操作系统的复杂性,使程序开发人员面对一个简单而统一的开发环境,减少了程序设计的复杂性,将注意力集中与自己的业务上,不必再为程序在不同软件系统上的移植而重复工作,从而大大减少了技术上的负担。

二、消息中间件原理
面向消息的中间件(MOM),提供了以松散耦合的灵活方式集成应用程序的一种机制。它们提供了基于存储和转发的应用程序之间的异步数据发送,即应用程序彼此不直接通信,而是与作为中介的MOM通信。MOM提供了有保证的消息发送(至少是在尽可能地做到这一点),应用程序开发人员无需了解远程过程调用(PRC)和网络/通信协议的细节。

2.1 消息中间件简介

消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。

消息中间件适用于需要可靠的数据传送的分布式环境。采用消息中间件机制的系统中,不同的对象之间通过传递消息来激活对方的事件,完成相应的操作。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者。消息中间件能在不同平台之间通信,它常被用来屏蔽掉各种平台及协议之间的特性,实现应用程序之间的协同,其优点在于能够在客户和服务器之间提供同步和异步的连接,并且在任何时刻都可以将消息进行传送或者存储转发,这也是它比远程过程调用更进一步的原因。

如下图所示,应用程序A与应用程序B通过使用 MOM 的应用程序编程接口(API)发送消息进行通信。



MOM将消息路由给应用程B,这样消息就可以存在于完全不同的计算机上,MOM 负责处理网络通信。如果网络连接不可用,MOM会存储消息,直到连接变得可用时,再将消息转发给应用程序B。

灵活性的另一方面体现在,当应用程序A发送其消息时,应用程序B甚至可以不处于执行状态。MOM将保留这个消息,直到应用程序B开始执行并试着检索消息为止。这还防止了应用程序A因为等待应用程序B检索消息而出现阻塞。 这种异步通信要求应用程序的设计与现在大多数应用程序不同,不过,对于时间无关或并行处理,它可能是一个极其有用的方法。

2.2 消息中间件与分布式对象调用的比较

分布式对象调用,如CORBA,RMI和DCOM,提供了一种通讯机制,透明地在异构的分布式计算环境中传递对象请求,而这些对象可以位于本地或远程机器。它通过在对象与对象之间提供一种统一的接口,使对象之间的调用和数据共享不再关心对象的位置、实现语言及所驻留的操作系统。这个接口就是面向对象的中间件。

尽管面向对象的中间件是一种很强大的规范被广泛应用,但是面对大规模的复杂分布式系统,这些技术也显示出了局限性:

1.同步通信:客户发出调用后,必须等待服务对象完成处理并返回结果后才能继续执行。

2.客户和服务对象的生命周期紧密耦合:客户进程和服务对象进程都必须正常运行,如果由于服务对象崩溃或网络故障导致客户的请求不可达,客户会接收到异常。

为了解决这些问题,出现了面向消息的中间件,它较好地解决了以上的问题。

消息中间件作为一个中间层软件,它为分布式系统中创建、发送、接收消息提供了一套可靠通用的方法,实现了分布式系统中可靠的、高效的、实时的跨平台数据传输。消息中间件减少了开发跨平台和网络协议软件的复杂性,它屏蔽了不同操作系统和网络协议的具体细节,面对规模和复杂度都越来越高的分布式系统,消息中间件技术显示出了它的优越性:

1.采用异步通信模式:发送消息者可以在发送消息后进行其它的工作,不用等待接收者的回应,而接收者也不必在接到消息后立即对发送者的请求进行处理;

2.客户和服务对象生命周期的松耦合关系:客户进程和服务对象进程不要求都正常运行,如果由于服务对象崩溃或者网络故障导致客户的请求不可达,客户不会接收到异常,消息中间件能保证消息不会丢失。

2.3 消息中间件的传递模式

消息中间件一般有两种传递模型:点对点模型(PTP)和发布-订阅模型(Pub/Sub)[2]。

1. 点对点模型(PTP)

点对点模型用于消息生产者和消息消费者之间点到点的通信。消息生产者将消息发动到由某个名字标识的特定消费者。这个名字实际上对应于消息服务中的一个队列(Queue),在消息传动给消费者之前它被存储在这个队列中。队列可以是持久的,以保证在消息服务出现故障时仍然能够传递消息。

2. 发布-订阅模型(Pub/Sub)

发布-订阅模型用称为主题(topic)的内容分层结构代替了PTP模型中的惟一目的地,发送应用程序发布自己的消息,指出消息描述的是有关分层结构中的一个主题的信息。希望接收这些消息的应用程序订阅了这个主题。订阅包含子主题的分层结构中的主题的订阅者可以接收该主题和其子主题发表的所有消息。

下图展示了发布和订阅模型:[2]



多个应用程序可以就一个主题发布和订阅消息,而应用程序对其他人仍然是匿名的。MOM 起着代理(broker)的作用,将一个主题已发表的消息路由给该主题的所有订阅者。

2.4 消息中间件产品与JMS

从上个世纪90年代初,随着不同厂商消息中间件大量上市,消息中间件技术得到了长足的发展。目前,IBM和BEA的中间件产品在银行、证券、电信等高端行业,以及IT等行业中得到广泛应用。IBM凭借其在1999年推出的应用服务器WebSphere,扎根金融、证券等行业,在超大型以及系统整合型应用方面优势突出;BEA则是专门从事中间件开发的公司,它的应用服务器WebLogic在美国市场占有率超过60%,在国内电信及证券行业占据主要地位;Sun、Oracle、Sybase和Borland等厂商也都有自己的应用服务器;近年来,以金蝶、东方通等公司为代表的国产中间件产品也发展迅速。[3]

由于没有统一的规范和标准,基于消息中间件的应用不可移植,不同的消息中间件也不能互操作,这大大阻碍了消息中间件的发展。 Java Message Service(JMS, Java消息服务)是SUN及其伙伴公司提出的旨在统一各种消息中间件系统接口的规范。它定义了一套通用的接口和相关语义,提供了诸如持久、验证和事务的消息服务,它最主要的目的是允许Java应用程序访问现有的消息中间件。JMS规范没有指定在消息节点间所使用的通讯底层协议,来保证应用开发人员不用与其细节打交道,一个特定的JMS实现可能提供基于TCP/IP、HTTP、UDP或者其它的协议。

目前许多厂商采用并实现了JMS API,现在,JMS产品能够为企业提供一套完整的消息传递功能,下面是一些比较流行的JMS商业软件和开源产品。

1.IBM MQSeries

IBM MQ系列产品提供的服务使得应用程序可以使用消息队列进行相互交流,通过一系列基于Java的API,提供了MQSeries在Java中应用开发的方法。它支持点到点和发布/订阅两种消息模式,在基本消息服务的基础上增加了结构化消息类,通过工作单元提供数据整合等内容。

2.WebLogic

WebLogic是BEA公司实现的基于工业标准的J2EE应用服务器,支持大多数企业级JavaAPI,它完全兼容JMS规范,支持点到点和发布/订阅消息模式,它具有以下一些特点:

1) 通过使用管理控制台设置JMS配置信息;

2) 支持消息的多点广播;

3) 支持持久消息存储的文件和数据库;

4) 支持XML消息,动态创建持久队列和主题。

3.SonicMQ

SonicMQ是Progress公司实现的JMS产品。除了提供基本的消息驱动服务之外,SonicMQ也提供了很多额外的企业级应用开发工具包,它具有以下一些基本特征:

1) 提供JMS规范的完全实现,支持点到点消息模式和发布/订阅消息模式;

2) 支持层次安全管理;

3) 确保消息在Internet上的持久发送;

4) 动态路由构架(DRA)使企业能够通过单个消息服务器动态的交换消息;

5) 支持消息服务器的集群。

4.Active MQ

Active MQ是一个基于Apcache 2.0 licenced发布,开放源码的JMS产品。其特点为:

1) 提供点到点消息模式和发布/订阅消息模式;

2) 支持JBoss、Geronimo等开源应用服务器,支持Spring框架的消息驱动;

3) 新增了一个P2P传输层,可以用于创建可靠的P2P JMS网络连接;

4) 拥有消息持久化、事务、集群支持等JMS基础设施服务。

5.OpenJMS

OpenJMS是一个开源的JMS规范的实现,它包含以下几个特征:

1) 它支持点到点模型和发布/订阅模型;

2) 支持同步与异步消息发送;

3) 可视化管理界面,支持Applet;

4) 能够与Jakarta Tomcat这样的Servlet容器结合;

5) 支持RMI、TCP、HTTP与SSL协议。

三、消息中间件应用之JMS
3.1 JMS简介

Java Message Service 规范 1.1 声称:JMS 是一组接口和相关语义,它定义了 JMS 客户如何访问企业消息产品的功能。

在 JMS 之前,每一家 MOM 厂商都用专有 API 为应用程序提供对其产品的访问,通常可用于许多种语言,其中包括 Java 语言。JMS 通过 MOM 产品为 Java 程序提供了一个发送和接收消息的标准的、便利的方法。用 JMS 编写的程序可以在任何实现 JMS 标准的 MOM 上运行。

JMS 可移植性的关键在于:JMS API 是由 Sun 作为一组接口而提供的。提供了 JMS 功能的产品是通过提供一个实现这些接口的提供者来做到这一点的。开发人员可以通过定义一组消息和一组交换这些消息的客户机应用程序建立 JMS 应用程序。[4]

JMS1.0版本于1998年推出,最新的版本是2002发布的JMS 1.1规范。JMS支持消息中间件的两种传递模式:点到点模式和发布-订阅模式。在JMS 1.1以前的版本中,每一种都有自己的特定于该模式的一组客户机接口。JMS1.1版本提供了单一的一组接口,它允许客户机可以在两个模式中发送和接收消息。这些“模式无关的接口”保留了每一个模式的语义和行为,是实现 JMS 客户机的最好选择。

统一模式的好处是:

1) 使得用于客户机的编程更简单。

2) 队列和主题的操作可以是同一事务的一部分。

3) 为JMS提供者提供了优化其实现的机会。

3.2 JMS体系结构

3.2.1 JMS接口描述

JMS 支持两种消息类型PTP 和Pub/Sub,分别称作:PTP Domain 和Pub/Sub Domain,这两种接口都继承统一的JMS Parent 接口,JMS 主要接口如下所示:

JMS Parent
PTP Domain
Pub/Sub Domain

ConnectionFactory
QueueConnectionFactory
TopicConnectionFactory

Connection
QueueConnection
TopicConnection

Destination
Queue
Topic

Session
QueueSession
TopicSession

MessageProducer
QueueSender
TopicPublisher

MessageConsumer
QueueReceiver
TopicSubscriber


以下是对这些接口的简单描述:

ConnectionFactory:连接工厂,JMS 用它创建连接

Connection:JMS 客户端到JMS Provider 的连接

Destination:消息的目的地

Session:一个发送或接收消息的线程

MessageProducer: 由Session 对象创建的用来发送消息的对象

MessageConsumer: 由Session 对象创建的用来接收消息的对象

3.2.2 JMS消息模型

JMS 消息由以下几部分组成:消息头,属性,消息体。[4]

消息头(header):JMS消息头包含了许多字段,它们是消息发送后由JMS提供者或消息发送者产生,用来表示消息、设置优先权和失效时间等等,并且为消息确定路由。
属性(property):由消息发送者产生,用来添加删除消息头以外的附加信息。
消息体(body):由消息发送者产生,JMS中定义了5种消息体:ByteMessage、MapMessage、ObjectMessage、StreamMessage和TextMessage。
3.3 JMS编程实践

广义上说,一个JMS应用是几个JMS 客户端交换消息,开发JMS客户端应用由以下几步构成:[2]

1) 用JNDI 得到ConnectionFactory对象;

2) 用JNDI 得到目标队列或主题对象,即Destination对象;

3) 用ConnectionFactory创建Connection 对象;

4) 用Connection对象创建一个或多个JMS Session;

5) 用Session 和Destination 创建MessageProducer和MessageConsumer;

6) 通知Connection 开始传递消息。

3.3.1 消息生产者编程

消息生产者程序如下:[2]

package org.jms.test;
import java.io.*;
mport javax.jms.*;
import javax.naming.*;
public class Sender {
    public static void main(String[] args) {
        new Sender().send();
    }
    public void send() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            //Prompt for JNDI names
            System.out.println("Enter ConnectionFactory name:");
            String factoryName = reader.readLine();
            System.out.println("Enter Destination name:");
            String destinationName = reader.readLine();
            //Look up administered objects
            InitialContext initContext = new InitialContext();
            ConnectionFactory factory =
                (ConnectionFactory) initContext.lookup(factoryName);
            Destination destination = (Destination) initContext.lookup(destinationName);
            initContext.close();
            //Create JMS objects
            Connection connection = factory.createConnection();
            Session session =
                connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer sender = session.createProducer(queue);
            //Send messages
            String messageText = null;
            while (true) {
                System.out.println("Enter message to send or 'quit':");
                messageText = reader.readLine();
                if ("quit".equals(messageText))
                    break;
                TextMessage message = session.createTextMessage(messageText);
                sender.send(message);
            }
            //Exit
            System.out.println("Exiting...");
            reader.close();
            connection.close();
            System.out.println("Goodbye!");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}
 

3.3.2 消息消费者编程

消息消费者程序如下:[2]

package compute;
import java.io.*;
import javax.jms.*;
import javax.naming.*;
public class Receiver implements MessageListener {
    private boolean stop = false;
    public static void main(String[] args) {
        new Receiver().receive();
    }
    public void receive() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            //Prompt for JNDI names
            System.out.println("Enter ConnectionFactory name:");
            String factoryName = reader.readLine();
            System.out.println("Enter Destination name:");
            String destinationName = reader.readLine();
            reader.close();
            //Look up administered objects
            InitialContext initContext = new InitialContext();
            ConnectionFactory factory =
                (ConnectionFactory) initContext.lookup(factoryName);
            Destination destination = (Destination) initContext.lookup(destinationName);
            initContext.close();
            //Create JMS objects
            Connection connection = factory.createConnection();
            Session session =
                connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageConsumer receiver = session.createConsumer(queue);
            receiver.setMessageListener(this);
            connection.start();
            //Wait for stop
            while (!stop) {
                Thread.sleep(1000);
            }
            //Exit
            System.out.println("Exiting...");
            connection.close();
            System.out.println("Goodbye!");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
    public void onMessage(Message message) {
        try {
            String msgText = ((TextMessage) message).getText();
            System.out.println(msgText);
            if ("stop".equals(msgText))
                stop = true;
        } catch (JMSException e) {
            e.printStackTrace();
            stop = true;
        }
    }
}


以上程序都较为简单,基本上为自解释的。

四、消息中间件总结
消息中间件自从产生以来发展迅速,在分布式联机事务处理环境中,它担当通讯资源管理器(CRM)的角色,为分布式应用提供实时、高效、可靠的、跨越不同操作系统、不同网络的消息传递服务,同时消息中间件减少了开发跨平台应用程序的复杂性。在要求可靠传输的系统中可以利用消息中间件作为一个通讯平台,向应用提供可靠传输功能来传递消息和文件。


-------------------------------------------------------------

XOA Tomcat中集成Apache ActiveMQ
Apache ActiveMQ官方文档有参考资料

调用者将消息放入消息队列
配置Spring提供的Message Driven Bean(MDB) 风格的Bean消费消息,完成异步调用 可参考Spring官方文档

面向接口的编程原则
单一职责原则 (SRP)
开放关闭原则(OCP)
Liskov 可替换原则(LSP)
依赖性注入原则(DIP)

业务层实现使用业务接口模式 - Business Interface

业务层必须抽象出定义业务方法的父接口,称之为业务接口
面向接口编程原则的体现
强制编译期检查接口签名实现
可使用Spring 的IoC实现业务层实现的替换!

主要是实现XOA stub 和 XOA skeleton的过程
首先要把业务层实现套上XOA Rose Controller的壳,发布为XOA REST Service

业务代理 Business Delegate

创建一个代理层,通过封装用于发现、代理Xoa Client层的调用代码,隐藏XOA Client API的复杂性。
业务代理层调用的是业务层的接口,只是业务层的一个远程代理,所以应该实现业务接口!
Delegate是对远程调用语义的屏蔽

客户端业务代理和服务实现实现相同的业务接口
Spring 的IoC
使用依赖注入来描述对象间的交互关系
控制反转大法
把原来WEB层注入的业务层接口实现替换为Delegate即可
原系统的依赖注入关系
<bean id="userService" class="com.xiaonei.test.service.impl.UserServiceImpl" />
<bean name="/user" class="com.xiaonei.test.web.controller.UserController">
    <property name="userService" ref="userService">
</bean>


XOA后系统中的注入关系变化
<bean id="userService" class="com.xiaonei.test.service.impl.UserServiceDelegate" />
<bean name="/user" class="com.xiaonei.test.web.controller.UserController">
    <property name="userService" ref="userService">
</bean>


Spring中MDB Container的配置
<!--链接工厂-->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="vm://localhost"/>
</baen>
<bean id="userListener" class="com.xiaonei.test.jms.UserListener" />

<!--队列-->
<bean id="userQueue" class="org.apache.activemq.command.ActiveMQQueue">
   <constructor-arg index="0" value="userQueue"/>
</bean>

<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
   <property name="connectionFactory" ref="connectionFactory"/>
   <property name="destination" ref="userQueue"/>
   <property name="messageListener" ref="userListener" />
</bean>
 


你可能感兴趣的:(消息中间件)