MOM,面向消息中间件的交互模式
各个系统间,可以认为是独立的,消息通过中间件传递。中间件类似一个路由器,决定消息的去处等等。
集中式
如上,可见,对中间件的依赖感觉还是挺严重的。当然实际应用中可能是存在多个如上图所示的结构群组成。
分步式
IP多播允许应用程序连接一个或多个IP多播组,但是依然存在一个路由功能的,用来作为广播分发等功能存在。
以上两种方式经常是混合存在的。
JMS的两种模式
分为点对点和发布/订阅模式。
点对点消息生产者称为发送者,消息消费者称为接收器。具有唯一消费者特性,并且消费者可以在生产者之前或者运行,消费者能获取到在其运行前的消息,只要该消息还未过期,还未被其他消费者获取。
发布订阅模式,消息生产者称为发布者,消息消费者称为订户,可以支持一个主题的内容被多个消费者订阅和获取,但是消费者只能获取在其运行之后的消息,之前的消息无法获取。广播中心在获取到消息后会自动推送到各个客户端。
点对点依赖于queue(队列),而发布订阅依赖于Topic(主题)
相关接口
JMS主要接口有7个,分别是
• ConnectionFactory
• Destination
• Connection
• Session
• Message
• MessageProducer
• MessageConsumer
需要注意的是事物控制和通讯是通过Session而不是Connection来进行的。Connection往往一个就可以了,会建立Session池。
以上是在使用JNDI来管理JMS的时候,可以从JNDI获取ConnectionFactory和Destination
ConnectionFactory 接口(连接工厂)
用户用来创建到JMS提供者的连接的被管对象。JMS客户通过可移植的接口访问连接,这样当下层的实现改变时,代码不需要进行修改。管理员在JNDI名字空间中配置连接工厂,这样,JMS客户才能够查找到它们。根据消息类型的不同,用户将使用队列连接工厂,或者主题连接工厂。
Connection 接口(连接)
连接代表了应用程序和消息服务器之间的通信链路。在获得了连接工厂后,就可以创建一个与JMS提供者的连接。根据不同的连接类型,连接允许用户创建会话,以发送和接收队列和主题到目标。
Destination 接口(目标)
目标是一个包装了消息目标标识符的被管对象,消息目标是指消息发布和接收的地点,或者是队列,或者是主题。JMS管理员创建这些对象,然后用户通过JNDI发现它们。和连接工厂一样,管理员可以创建两种类型的目标,点对点模型的队列,以及发布者/订阅者模型的主题。
Destination有2个子接口,Queue和Topic
MessageConsumer 接口(消息消费者)
由会话创建的对象,用于接收发送到目标的消息。消费者可以同步地(阻塞模式),或(非阻塞)接收队列和主题类型的消息。
MessageProducer 接口(消息生产者)
由会话创建的对象,用于发送消息到目标。用户可以创建某个目标的发送者,也可以创建一个通用的发送者,在发送消息时指定目标。
Message 接口(消息)
是在消费者和生产者之间传送的对象,也就是说从一个应用程序传送到另一个应用程序。一个消息有三个主要部分:
消息头(必须):包含用于识别和为消息寻找路由的操作设置。
一组消息属性(可选):包含额外的属性,支持其他提供者和用户的兼容。可以创建定制的字段和过滤器(消息选择器)。
一个消息体(可选):允许用户创建五种类型的消息(文本消息,映射消息,字节消息,流消息和对象消息)。
消息接口非常灵活,并提供了许多方式来定制消息的内容。
Session 接口(会话)
表示一个单线程的上下文,用于发送和接收消息。由于会话是单线程的,所以消息是连续的,就是说消息是按照发送的顺序一个一个接收的。会话的好处是它支持事务。如果用户选择了事务支持,会话上下文将保存一组消息,直到事务被提交才发送这些消息。在提交事务之前,用户可以使用回滚操作取消这些消息。一个会话允许用户创建消息生产者来发送消息,创建消息消费者来接收消息。
基本概念
jms的实现,称为jms provider,可以认为是jms的服务器
点对点模式的API
与JMS基础API类似,基本上都是基础的子接口
• QueueConnectionFactory
• Queue
• QueueConnection
• QueueSession
• Message
• QueueSender
• QueueReceiver
如上是在JNDI管理的方式的交互
发布订阅模式的
• TopicConnectionFactory
• Topic
• TopicConnection
• TopicSession
• Message
• TopicPublisher
• TopicSubscriber
SOA
JMS是实现SOA的很好的手段,应用之间通过JMS作为消息传递,隔离具体实现,实际上WEBSERVICE,HTTP等方式也是可以实现,只是对于事物控制等等没有那么强大。
事件驱动体系结构
如当员工离职,产生一个事件源,广播到系统各处,各处之间进行处理。对于发布订阅模式的JMS很适合。
企业整合
企业系统之间,可能使用的语言不一样,对应的消息协议也不一样,可能需要再之间进行桥接。
企业间的整合
这方面技术选择很多,webservice,http,jms都是不错选择。
跨地域的整合
这个问题,JMS提供了更安全可靠的特性,而webservice,http等都不具有,当然通过改进方案,其他技术方案也是可选择的。
传统的RPC模式
如上,各个系统之间进行交互,当订单系统开始,需要调用CRM,CRM又会调用库存系统,等等,每次调用间存在的等待,阻塞等时间消耗,比如当CRM服务提供者,暂停了几分钟的运行,那么整个信息都将失败。
企业JMS结构
订单开始,发送一个CRM请求的消息,并继续自己的下一步,无需等待,而且当CRM停止运行了一段时间,而订单系统则继续进行。当然对JMS server的依赖就会很大了。JMSserver要是挂了,就呵呵吧,因此对JMS Server的容错处理,难度和技术点要求比较高。一般情况下JMS SERVER会把收到的消息进行持久化,以确保消息不会因为JMS本身暂停而造成消息丢失。
例子使用activemq
简单的例子,非JNDI
package lyx;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
*
*
* 描述:消息生产者
*
* @author liyixing
* @version 1.0
* @since 2014-9-24 下午10:18:18
*/
public class Client {
public static void main(String[] args) throws JMSException {
String l = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(l);
Connection connection = factory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("lyx");
MessageProducer producer = session.createProducer(destination);
Message message = session.createTextMessage("hello");
producer.send(message);
producer.close();
session.close();
connection.close();
}
}
package lyx;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
*
*
* 描述:消息消费者
*
* @author liyixing
* @version 1.0
* @since 2014-9-24 下午10:18:41
*/
public class Consumer {
/**
* 描述:
*
* @param args
* @author liyixing 2014-9-24 下午10:13:08
* @throws JMSException
*/
public static void main(String[] args) throws JMSException {
String l = "tcp://localhost:61616";// 地址
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(l);// 连接器
Connection connection = connectionFactory.createConnection();// 创建连接
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);// 打开会话
String destinationName = "lyx";
Destination dest = session.createQueue(destinationName);// 消息目的地
MessageConsumer consumer = session.createConsumer(dest);
connection.start();
Message message = consumer.receive();
TextMessage textMessage = (TextMessage) message;
String text = textMessage.getText();
System.out.println("消息: " + text);
consumer.close();
session.close();
connection.close();
}
}
//上面的代码中,可以试试先把JMS中间件启动,运行Client后把JMS关闭,重启一次,再运行Consumer,Consumer依然能获取到数据,这说明activemq默认情况下,是会对消息进行持久化的。
通过发布订阅模式,来实现的聊天功能,使用的是ActiveMQ,该框架本身就支持JNDI,只需要在classpath的根目录下面增加一个文件
jndi.properties
#该文件是ActiveMQ自己解析的。
java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url=tcp://localhost:61616
java.naming.security.principal=system
java.naming.security.credentials=manager
connectionFactoryNames=lyxcf
topic.topic1=jms.topic1
package lyx.chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
*
*
* 描述:聊天室
*
* @author liyixing
* @version 1.0
* @since 2014-9-25 下午4:26:20
*/
public class Chat implements MessageListener {
private TopicConnection connection;
private TopicSession session;// 发布者
private TopicPublisher publisher;
private String userName;
/**
*
* 构造函数
*
* @param factoryName
* 本例子使用JNDI,因此是只JNDI的名字
* @param topicName
* 相关主题名字
* @param userName
* 相关用户名
* @throws NamingException
* @throws JMSException
*/
public Chat(String factoryName, String topicName, String userName)
throws NamingException, JMSException {
this.userName = userName;
// JNDI中获取连接工厂
InitialContext initialContext = new InitialContext();
TopicConnectionFactory factory = (TopicConnectionFactory) initialContext
.lookup(factoryName);
// 生成连接
connection = factory.createTopicConnection();
// 创建会话
// 发布者
session = connection
.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);//false意味着不开启事物,我们选择AUTO_ACKNOWLEDGE,表示该消息是接收客户端后,它会自动确认。 false的时候值可以忽略该传什么值,支持的模式有Session.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE, and Session.DUPS_OK_ACKNOWLEDGE
// 订阅者
// TopicSession subSession = connection.createTopicSession(false,
// Session.AUTO_ACKNOWLEDGE);
// 获取主题信息
Topic topic = (Topic) initialContext.lookup(topicName);
// 创建发布者
publisher = session.createPublisher(topic);
// 创建订阅者
TopicSubscriber subscriber = session.createSubscriber(topic);
// 设置消息监控
subscriber.setMessageListener(this);
connection.start();
}
/**
* 接收到消息
*
* @see javax.jms.MessageListener#onMessage(javax.jms.Message)
*/
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("收到信息:"
+ textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
/**
*
* 描述:发送
*
* @param msg
* @throws JMSException
* @author liyixing 2014-9-25 下午4:48:42
*/
public void send(String msg) throws JMSException {
TextMessage textMessage = session.createTextMessage();
textMessage.setText(userName + ":" + msg);
publisher.send(textMessage);
}
public void close() throws JMSException {
connection.close();
}
public static void main(String[] args) throws NamingException,
JMSException, IOException {
Chat chat = new Chat(args[0], args[1], args[2]);
BufferedReader commandLine = new java.io.BufferedReader(
new InputStreamReader(System.in));
while (true) {
String s = commandLine.readLine();
if (s.equalsIgnoreCase("exit")) {
chat.close();
System.exit(0);
} else
chat.send(s);
}
}
}
TopicConnectionFactory接口
一共只有两个重载的方法,用于创建TopicConnection。
package javax.jms;
/**
* @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Wed, 25 Oct 2006) $
*/
public interface TopicConnectionFactory extends ConnectionFactory {
TopicConnection createTopicConnection() throws JMSException;
TopicConnection createTopicConnection(String userName, String password)
throws JMSException;
}
TopicConnection
通过TopicConnectionFactory 创建,connection = factory.createTopicConnection();
它是继承自Connection,比较重要的三个方法是
public interface Connection {
void start() throws JMSException;
void stop() throws JMSException;
void close() throws JMSException;
........
}
start方法打开网络流,并开始等待消息的到来。
stop会暂时性的不接受消息,但流未关闭,知道下次start的调用
close会关闭流,并且会关闭所有相关的需要关闭的对象,包括TopicSession,TopicPublisher,TopicSubscriber.
TopicSession
在获得了TopicConnection就可以获取TopicSession,它是用来创建消息Message,发布者TopicPublisher和订阅者TopicSubscriber的工厂,以及控制事物,如上的例子,使用的是一个session同时创建了订阅者和发布者,实际上更加推荐的方式的创建两个session,分别用来处理发布端和订阅端,只有一个session,会受到网络限制,线程限制等问题。如上面聊天的例子,使用的是一个session,那么如果发布者进行发布的时候,网络堵塞了1分钟,那么订阅者也必须等待一分钟。
另外session还用来创建session。
Topic
可以简单的当做标示,有一个name,用来作为主题名。
之所以不直接使用String作为主题名,是为了避开不同的JMS实现,或者不同的命名规范,而造成不可移植问题。
TopicPublisher
消息发布者,通过session创建,创建的时候需要以主题对象为参数。通过publish方法进行发布。只是传递消息,而不等待接收者,因此它主要负责消息发送就可以了。
TopicSubscriber
订阅器
上面例子使用TopicSubscriber subscriber = session.createSubscriber(topic);
另外一个更完整的重载是TopicSubscriber subscriber =
subSession.createSubscriber(chatTopic, null, true);
1.相关主题,第二个是消息选择器(可以认为是条件),第三个是表示是否接收自己发布的消息。JMS中间件收到消息后会主动推送到订阅者,而订阅者无需轮训查问。
另外订阅是基于事件驱动模式的,可以为其设置一个
package javax.jms;
public interface MessageListener {
public void onMessage(Message message);
}
监听者的实例。
Message
消息对象,有三个部分,头,属性,消息体
他有很多子集
TextMessage 文本
ObjectMessage java对象
BytesMessage 二进制
StreamMessage 流模式
MapMessage 键值对模式,但是值必须是java原生类型或者其包装类