Weblogic 服务器提供了非常强大的JMS消息中间件平台,其性能优越,远超同类JMS产品比IBM 的MQ也快很多。并且,Weblogic JMS提供了非常强大的消息传递功能使得基于该平台的Java 消息解决方案非常的完整。本文,旨在阐述WebLogic JMS 服务器的一些比较重要的特性,以及如何配合在一起使用。
- 本文所要介绍的WebLogic JMS特性有:
DQ (Distributed Queue):分布式Queue。通过配置DQ可以提供消息队列的集群式部署,提高一个队列的Capacity以及提供HA支持。对于客户端透明。
SAF 存储转发:可以在两个WebLogic服务器(相同版本)之间,从一段发送JMS消息到另一端,并保证发送能到达终端的Destination。如果,两台服务器之间的网络连接暂不可用,则会将消息保存至发送方的本地存储中。用户可以设定多种转发策略和Qos设定。
UOW(Unit-Of-Work):WLS可以将多条消息标签为一个工作单元(UOW),只有当该UOW中所有的消息都被发送到队列中后,Consumer端才能获取该工作单元的所有消息。并且,所有属于该UOW的消息,保证只有一个Consumer能进行处理。
- 客户用例
在某家工厂构建的一个门店和总部数据中心系统之间的一个异步信息同步的解决方案中,采用了UOW+SAF+DQ的方式来传送大文件的切片,并在终端还原文件。由于文件被切片传送,因此,根据JMS 队列传输的原理,默认配置的JMS DQ会将文件切片分散接收在集群的各个节点的QUEUE上,并且每个节点上配置的消息驱动EJB(MDB)也不能保证只有一个实例能获取文件的全部切片,这样的默认行为令还原大文件切片变为一个需要在多个MDB之间协调完成的事务,处理过程复杂,效率也不高。并且通过SAF来克服在VPN网络环境中可能的各类异常,保证消息传递的质量。
针对该用户的这种需求,提出的解决方案是使用UOW来将一个文件的切片打包为一个工作单元的方式来传送。
方案示意图如下:
上图中的UOW1、UOW2、UOW3表示的是代表一个大文件切片的一组/一个工作单元消息(多条)。大文件会先经过JAVA程序进行切片,然后通过JMS API和UOW的API来发送到一个本地的WebLogic服务器的队列上。本地队列被配置为通过SAF转发到远程的一个DQ中。远程的DQ接收到消息后,会根据UOW的ID来将一组消息(统一UOW ID的消息)作为一个整体,分配到集群中的某一个实例。在该集群中部署一个MDB EJB。该EJB的实例会收到属于一个文件的所有切片,然后进行还原处理。由于使用了UOW,可以保证每个文件的所有切片,有且只有一个MDB实例可以进行处理。
- 配置过程简述
- 首先安装远端WebLogic服务器并配置好集群。
- 分别创建三个JMS Server在三个集群节点上。
- 创建一个JMS Module,并在JMS Module中定义一个子部署target到cluster上。
- 创建一个Distributed Queue, target到定义好的子部署上,记得在高级配置区域中选择工作单元 (UOW) 消息处理策略为:单个消息传递。 该配置启动UOW支持。
- 安装本地WebLogic服务器
- 创建SAF存储转发代理。
- 创建SAF导入目的地,并在SAF远程上下文定义中,配置远程集群的URL,例如:t3://172.16.100.1:7003,172.16.100.1:7004,172.16.100.1:7005。
- 配置子部署并将SAF导入目的地target到子部署上。
详细部署请参考WLS相关文档。
- 示例代码
UOW 发送:
import java.util.UUID;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Hashtable;
import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import weblogic.jms.extensions.WLMessageProducer;
/** This example shows how to establish a connection
* and send messages to the JMS queue. The classes in this
* package operate on the same JMS queue. Run the classes together to
* witness messages being sent and received, and to browse the queue
* for messages. The class is used to send messages to the queue.
*/
public class QueueSendUOW
{
// Defines the JNDI context factory.
public final static String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory";
// Defines the JMS context factory.
public final static String JMS_FACTORY="dizzyworldConnectionFactory";
// Defines the queue.
public final static String QUEUE="dizzyworldDistributedQueue";
private QueueConnectionFactory qconFactory;
private QueueConnection qcon;
private QueueSession qsession;
private QueueSender qsender;
private Queue queue;
private TextMessage msg;
/**
* Creates all the necessary objects for sending
* messages to a JMS queue.
*
* @param ctx JNDI initial context
* @param queueName name of queue
* @exception NamingException if operation cannot be performed
* @exception JMSException if JMS fails to initialize due to internal error
*/
public void init(Context ctx, String queueName)
throws NamingException, JMSException
{
qconFactory = (QueueConnectionFactory) ctx.lookup(JMS_FACTORY);
qcon = qconFactory.createQueueConnection();
qsession = qcon.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
queue = (Queue) ctx.lookup(queueName);
qsender = qsession.createSender(queue);
msg = qsession.createTextMessage();
qcon.start();
}
/**
* Sends a message to a JMS queue.
*
* @param message message to be sent
* @exception JMSException if JMS fails to send message due to internal error
*/
public void send(String message, String strUOW, int SeqNo, boolean isLast) throws JMSException {
msg.setText(message);
msg.setStringProperty("JMS_BEA_UnitOfWork", strUOW);
msg.setIntProperty("JMS_BEA_UnitOfWorkSequenceNumber", SeqNo);
msg.setBooleanProperty("JMS_BEA_IsUnitOfWorkEnd", isLast);
qsender.send(msg, DeliveryMode.NON_PERSISTENT, 7, 0);
}
/**
* Closes JMS objects.
* @exception JMSException if JMS fails to close objects due to internal error
*/
public void close() throws JMSException {
qsession.commit();
qsender.close();
qsession.close();
qcon.close();
}
/** main() method.
*
* @param args WebLogic Server URL
* @exception Exception if operation fails
*/
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.out.println("Usage: java QueueSendUOW WebLogicURL");
return;
}
InitialContext ic = getInitialContext(args[0]);
QueueSendUOW qs = new QueueSendUOW();
qs.init(ic, QUEUE);
readAndSend(qs);
qs.close();
}
private static void readAndSend(QueueSendUOW qs)
throws IOException, JMSException
{
BufferedReader msgStream = new BufferedReader(new InputStreamReader(System.in));
String line=null;
UUID UOW = null;
boolean quitNow = false;
int seqNumber = 1;
UOW = UUID.randomUUID();
while (! quitNow) {
System.out.print("Enter message (\"quit\" to quit): \n");
line = msgStream.readLine();
if (line != null && line.trim().length() != 0) {
quitNow = line.equalsIgnoreCase("quit");
if ( ! quitNow ) {
qs.send( line, String.valueOf(UOW), seqNumber, false );
} else {
qs.send( line, String.valueOf(UOW), seqNumber, true );
}
System.out.println("JMS Message Sent: " + line + "\n");
seqNumber += 1;
}
}
}
private static InitialContext getInitialContext(String url)
throws NamingException
{
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
return new InitialContext(env);
}
}
MDB 接收:
package mdb;
import java.util.ArrayList;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.TextMessage;
@MessageDriven(mappedName = "dizzyworldDistributedQueue")
public class MessageDrivenEJBBean implements MessageListener {
private static final boolean VERBOSE = true;
private int count =0;
private MessageDrivenContext m_context;
private int m_tradeLimit;
public void onMessage(Message msg) {
try {
System.out.println(count++);
System.out.println(msg.getClass());
if (msg instanceof ObjectMessage) {
ArrayList msgList =
(ArrayList)(((ObjectMessage)msg).getObject());
int numMsgs = msgList.size();
log("Received [" + numMsgs + "] Messages");
log("UOW id: " +
(((TextMessage)msgList.get(0)).getStringProperty("JMS_BEA_UnitOfWork")));
for (int i = 0; i < numMsgs; i++) {
log("Message[" + i + "] " +
((TextMessage)msgList.get(i)).getText());
}
System.out.println();
}
} catch (JMSException jmse) {
// TODO: Add catch code
jmse.printStackTrace();
}
}
private void log(String s) {
if (VERBOSE)
System.out.println(s);
}
}
5. 测试
设定相关类路径并启动发送客户端,示例:java – cp %CLASSPATH% QueueSendUOW t3://172.16.100.102:7001
启动后输入一条或多条消息,输入’quit’完成一个UOW。
将MDB部署到集群中,在WLS的输出信息中,检查消息接收。