JMS是用来为发送者和接收者解耦的;
消息通过一个进程发送给代理,然后代理在另外一个进程异步的接收消息,一种可以利用JMS来实现的系统架构被称为请求/应答。
概括的说:一个请求/应答场景包括一个发送消息(请求)并期望接收消息返回值(应答)的应用程序。通常,这样的系统被设计成CS架构,服务端和客户端通过网络传输协议(TCP,UDP等等)同步的进行通信。这种架构方式在可扩展方面具有明显的限制,很难获得长远发展。消息系统正是为此而生,通过基于消息的请求/应答设计模式能够设计出易于扩展的系统主要以异步处理方式实现。
请求/应答系统:注意,客户端包含消息生产者(producer)和消息消费者(consumer),并且工作者(worker)也包含消息生产者(producer)和消息消费者(consumer)。后面将解释客户端和工作者(worker)。
首先,消息生产者创建一个以JMS消息格式封装的请求并在消息中设置一些重要的属性,包括correlation ID(通过消息的JMSCorrelationID属性设置)和reply destination(响应发送目的地,通过JMSReplyTo属性设置)。correlation ID属性非常重要,因为在请求数量非常多时需要使用这个属性来关联请求和应答。属性指定应答发往的目的地(通常是一个临时的JMS目的地,因为reply destination比较消耗资源)。接下来,客户端配置,一个消息消费者监听响应消息目的地(reply destination)。
其次,一个工作者(woker)接收到请求,并处理请求,然后发送一个响应消息到请求消息的JMSReplyTo属性指定的目的中。响应消息必须用原始请求消息correlation ID的属性值来设置JMSCorrelationID属性,当客户端收到响应消息后,可以通过correlation ID关联到初始的请求。
这种结构如何实现高可扩展性,想象一个场景:单一的工作者无法处理大量并发的请求负载时怎么办?当然没问题:可以添加工作者来平衡负载。这些工作者甚至分布到自不同的主机,这也是这种可扩展性设计中最重要的部分。因为工作者并不是在争夺相同主机上的资源,所以唯一的限制是代理中消息的最大吞吐量,它比使用普通的客户端服务器架构能达到的最大吞吐量要大得多。并且,ActiveMQ可以进行水平和垂直扩展。
下面让我们看看请求/应答程序的基本实现.
实现服务和工作者(worker)
首先,需要关注的是系统中使用的消息代理。先要启动代理,以便两边程序都启动时可以连接到代理。为方便说明本例中使用一个嵌入式代理。其次,需要启动系统中的工作者(worker)。工作者由消息监听器组成,用来接收处理消息和发送消息响应。
在请求/响应实例创建中一个代理,消费者以及生产者
public void start() throws Exception { createBroker(); setupConsumer(); } private void createBroker() throws Exception { broker = new BrokerService(); broker.setPersistent(false); broker.setUseJmx(false); broker.addConnector(brokerUrl); broker.start(); } private void setupConsumer() throws JMSException { ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl); Connection connection; connection = connectionFactory.createConnection(); connection.start(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Destination adminQueue = session.createQueue(requestQueue); producer = session.createProducer(null); producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); consumer = session.createConsumer(adminQueue); consumer.setMessageListener(this); } public void stop() throws Exception { producer.close(); consumer.close(); session.close(); broker.stop(); }
从代码中可以看到,start()方法调用一个方法创建并启动一个嵌入式代理,另外一个方法用于启动工作者. createBroker()方法使用BrokerService类来创建一个嵌入式代理.setupConsumer()方法通过创建 JMS所需的所有对象来发送和接收消息,这些JMS对象包括:一个连接,一个session,一个消息目的地,一个消息消费者和一个生产者。 创建消息生产者的时候没有设置默认的消息目的地,因为该生产者会将消息发送到每个消息的 JMSReplyTo属性所指定的目的地中。下面再详细看下请求/响应中的监听者,看看它是如何处理每个请求的:
public void onMessage(Message message) { try{ TextMessage response = this.session.createTextMessage(); if (message instanceof TextMessage) { TextMessage txtMsg = (TextMessage) message; String messageText = txtMsg.getText(); response.setText(handleRequest(messageText)); } response.setJMSCorrelationID(message.getJMSCorrelationID()); producer.send(message.getJMSReplyTo(), response); } catch (JMSException e) { e.printStackTrace(); } } public String handleRequest(String messageText) { return "Response to '" + messageText + "'"; }
消息监听器创建一个新消息,并设置合适的correlation ID,然后将消息发送到响应消息队列。很简单但是很重要,尽管在这个消息监听器的实现中没做什么惊天动地的事情,但是它展示了工作者完成器任务的必要的基本步骤。根据需求,可以在监听器中添加其他任意额外的操作或者数据库访问操作。
启动服务很简单:创建一个server实例并调用start()方法。main方法容纳了server的的所有功能,如下面的代码清单所示:
public static void main(String[] args) throws Exception { Server server = new Server(); server.start(); System.out.println(); System.out.println("Press any key to stop the server"); System.out.println(); System.in.read(); server.stop(); }
一旦server启动完成,worker就正常运行了,这样所有准备接收客户端请求的工作已经就绪。
实现客户端:客户端要做到工作是初始化发送到代理的请求。这是整个请求/应答过程的起点,并且通常在一个业务逻辑处理过程中触发。这个过程可能是接受订单,履行订单,整合各类业务系统,财务状况中的买入卖出等,不管是什么情况,请求/响应过程从发送一个消息开始。发送一个消息到代理需要标准的连接(connection),session,消息目的地(destination)以及消息生产者(producer),它们都是在client的start()方法中创建的。下面的的代码清单中提供了完整的
示例:
启动和停止响应/应答系统客户端的方法
public void start() throws JMSException { ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl); connection = connectionFactory.createConnection(); connection.start(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Destination adminQueue = session.createQueue(requestQueue); producer = session.createProducer(adminQueue); producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); tempDest = session.createTemporaryQueue(); consumer = session.createConsumer(tempDest); consumer.setMessageListener(this); } public void stop() throws JMSException { producer.close(); consumer.close(); session.close(); connection.close(); }
消息生产者发送消息到请求队列中,然后消息消费者监听新创建的临时队列。下面的代码中展示了实现客户端的真正逻辑:
public void request(String request) throws JMSException { System.out.println("Requesting: " + request); TextMessage txtMessage = session.createTextMessage(); txtMessage.setText(request); txtMessage.setJMSReplyTo(tempDest); String correlationId = UUID.randomUUID().toString(); txtMessage.setJMSCorrelationID(correlationId); this.producer.send(txtMessage); } public void onMessage(Message message) { try{ System.out.println("Received response for: " + ((TextMessage) message).getText()); } catch(JMSException e) { e.printStackTrace(); } }
所示的request()方法使用请求内容创建一个消息并设置JMSReplyTo属性值,接着发送这个消息到临时队列,最后设置correlation ID 属性值。上述3个步骤很重要.在这个例子中,是使用一个随机的UUID值来设置correlation ID的,也还可以使用其他任何ID生成器来生成这个ID。
接下就可以发送一个请求了,启动客户端也可以像启动sever一样,简单的使用一个main方法即可,下面是代码清单:
启动请求/应答系统客户端
public static void main(String[] args) throws Exception { Client client = new Client(); client.start(); int i = 0; while (i++ < 10) { client.request("REQUEST-" + i); } Thread.sleep(3000); //wait for replies client.stop(); }
如前文所述,这个是一个简单的请求/应答系统的实现。因此,启动客户端以后,会发送10个请求到代理。运行这个实例程序需要两个终端:一个用于运行server,另一个用于client,必须先运行server。sever通过Server类来实现,client通过Client类实现。因为这两个类都是通过main方法初始化的,所以运行它们很容易。注意:到当client启动后,送了10个请求用于激活请求/响应进程,然后收到了来自worker的响应。尽管这个例子很简单,但是日后必将是你在其他业务中实现请求/响应系统的参考。
使用请求/应答模式,代理将每秒钟收到的来自无数的客户端的成千上万个请求全部分发到不同的主机中处理。 在生产系统中,会使用更多的代理实例用于备份,失效转移以及负载均衡。这些代理也会被分布于很多的主机上。处理如此多请求的唯一方法是使用多工作者(worker)。因为消息发送者发送消息的速度可能比消息消费者接收并处理消息的速度快的多。所以就需要大量的工作者(worker),这些工作者同样也分布于大量的主机上。
使用多工作者的好处是任何的工作者都可以根据需要进行启用或者停用,而整个系统不会收到影响。消息生产者和工作者会正常处理消息,即使她们当中的一些已经崩溃了,也不会影响系统运行。这正是那些大型系统可以处理海量负载的原因--使用前文介绍过的基于请求/应答模式的异步消息系统.
JMS的API可以说是繁琐的,因为它要求开发者书写大量的初始化代码用于初始化必要的JMS对象,包括connection, session, producer, consumer等等。使用Spring框架通过提供可靠的API来帮助开发者移除(类似于JMS对象初始化)的那些固定的代码,以便简化整个配置过程。这正式使用Spring框架带来的好处。