前言
在进行AciveMQ讲解之前,我们需要先来了解一下JMS,因为它完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。
1.1JMS简介
Java消息服务(Java Message Service,JMS)是一个Java标准。定义了使用消息代理的通用API。在JMS出现之前,每个消息代理都有私有的API,这就使得不同代理之间的消息代码很难通用。但是借助JMS,所有遵从规范的实现都使用通用的接口,就类似于JDBC为数据库操作提供了通用的接口一样。
1.2JMS消息模型
JMS支持两种消息通信模型:点对点模型和发布/订阅模型。
- 点对点消息模型
在点对点模型中,每一条消息都有一个发送者和一个接收者,如下图所示,当消息代理得到消息时,它将消息放入一个队列中。
当接收者请求队列中的下一条消息时,消息会从队列中取出,并投递给接收者。因为消息投递后会从队列中删除,这样就可以保证消息只能投递给一个接收者。
尽管消息队列中的每一条消息只被投递给一个接收者,但是并不意味着只能使用一个接收者从队列中获取消息。事实上,通常可以使用几个接收者来处理队列中的消息。
在点对点的消息中,如果有多个接收者监听队列,我们也无法知道某条特定的消息会由哪一个接收者处理。这种不确定性实际上有很多好处,因为我们只需要简单的为队列添加新的监听器就能提高应用的消息处理能力。
- 发布订阅消息模型
在发布订阅消息模型中,消息会发送给一个主题。与队列类似,多个接收者都可以监听一个主题。但是,与队列不同的是,消息不再是只投递给一个接收者,而是主题的所有订阅者都会接收到此消息的副本,如下图所示
发布订阅消息模型与杂志发行商和杂志订阅者很相似。杂志出版后,发送给邮局,然后所有的订阅者都会收到杂志的副本。然而对于发布订阅模式来讲,发布者并不知道谁订阅了它的消息。发布者只知道它的消息要发送到一个特定的主题,而不知道谁在监听这个主题。也就是说,发布者并不知道消息是如何被处理的。
1.3JMS编程模型
- 连接工厂(ConnectionFactory)
客户端使用一个连接工厂对象连接到JMS服务提供者,它创建了JMS服务提供者和客户端之间的连接。例如ActiveMQ提供的ActiveMQConnectionFactory。
- 连接(Connection)
JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。如果我们有一个ConnectionFactory对象,可以使用它来创建一个连接。
- 会话(Session)
JMS Session是生产和消费消息的一个单线程上下文。会话用于创建消息生产者(producer)、消息消费者(consumer)和消息(message)等。会话提供了一个事务性的上下文,在这个上下文中,一组发送和接收被组合到了一个原子操作中。
- 目的地(Destination)
目的地是客户用来指定它生产的消息的目标和它消费的消息的来源的对象。JMS1.0.2规范中定义了两种消息传递域:点对点(PTP)消息传递域和发布/订阅消息传递域。 点对点消息传递域的特点如下:每个消息只能有一个消费者。消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,它都可以提取消息。发布/订阅消息传递域的特点如下:每个消息可以有多个消费者。生产者和消费者之间有时间上的相关性。订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求。持久订阅允许消费者消费它在未处于激活状态时发送的消息。在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)。
- 消息生产者(MessageProducer)
消息生产者是由会话创建的一个对象,用于把消息发送到一个目的地。
- 消息消费者(MessageConsumer)
消息消费者是由会话创建的一个对象,它用于接收发送到目的地的消息。消息的消费可以采用以下两种方法之一:同步消费。通过调用消费者的receive方法从目的地中显式提取消息。receive方法可以一直阻塞到消息到达。异步消费。客户可以为消费者注册一个消息监听器,以定义在消息到达时所采取的动作。
- 消息(Message)
JMS消息由以下三部分组成的:
消息头,通常包含一些消息的描述信息
– JMSDestination:消息的目的地,Queue或者Topic。
– JMSDeliveryMode:消息的发送模式:持久化(persistent)或非持久化(non-persistent)。前者表示消息在被消费之前,如果JMS提供者挂掉了,重新启动后消息仍然存在。后者在这种情况下表示消息会被丢失。
– JMSMessageID:一个字符串用来唯一标示一个消息。
– JMSTimestamp:当调用send()方法的时候,JMSTimestamp会被自动设置为当前事件。
– JMSCorrelationID:通常用来关联多个Message。例如需要回复一个消息,可以把JMSCorrelationID设置为所收到的消息的JMSMessageID。
– JMSReplyTo:有时消息生产者希望消费者回复一个消息,JMSReplyTo为一个Destination,表示需要回复的目的地。
– JMSRedelivered:如果这个值为true,表示消息是被重新发送了。因为有时消费者没有确认他已经收到消息或者JMS提供者不确定消费者是否已经收到。
– JMSType:表示消息体的结构,和JMS提供者有关。
– JMSExpiration:表示一个消息的有效期。只有在这个有效期内,消息消费者才可以消费这个消息。默认值为0,表示消息永不过期。
– JMSPriority:消息的优先级。0-4为正常的优先级,5-9为高优先级。
消息属性。如果需要除消息头字段以外的值,那么可以使用消息属性。
消息体。JMS定义的消息类型有
- TextMessage:普通字符串消息。
- MapMessage:键值对消息。
- BytesMessage:字节数据。
- StreamMessage:Java数据流消息。
- ObjectMessage:一个可序列化的对象。
1.4JMS可靠性机制
- 确认JMS消息
消息只有在被确认之后,才认为已经被成功地消费了。消息的成功消费通常包含三个阶段:客户接收消息、客户处理消息和消息被确认。 在事务性会话中,当一个事务被提交的时候,确认自动发生。在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)。该参数有以下三个可选值:
Session.AUTO_ACKNOWLEDGE。当客户成功的从receive方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息。
Session.CLIENT_ACKNOWLEDGE。客户通过消息的acknowledge方法确认消息。需要注意的是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认。
Session.DUPS_ACKNOWLEDGE。该选择只是会话迟钝的确认消息的提交。如果JMS Provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS Provider必须把消息头的JMSRedelivered字段设置为true。
- 持久性
JMS 支持以下两种消息提交模式:
PERSISTENT。指示JMS Provider持久保存消息,以保证消息不会因为JMS Provider的失败而丢失。
NON_PERSISTENT。不要求JMS Provider持久保存消息。
- 优先级
可以使用消息优先级来指示JMS Provider首先提交紧急的消息。优先级分10个级别,从0(最低)到9(最高)。如果不指定优先级,默认级别是4。需要注意的是,JMS Provider并不一定保证按照优先级的顺序提交消息。
- 消息过期
可以设置消息在一定时间后过期,默认是永不过期。
- 持久订阅
首先消息生产者必须使用PERSISTENT提交消息。客户可以通过会话上的createDurableSubscriber方法来创建一个持久订阅,该方法的第一个参数必须是一个topic,第二个参数是订阅的名称。
- 本地事务
在一个JMS客户端,可以使用本地事务来组合消息的发送和接收。JMS Session接口提供了commit和rollback方法。事务提交意味着生产的所有消息被发送,消费的所有消息被确认;事务回滚意味着生产的所有消息被销毁,消费的所有消息被恢复并重新提交,除非它们已经过期。
1.5实战Queue
生产者代码
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,ActiveMQConnectionFactory.DEFAULT_PASSWORD,"tcp://localhost:61616");
Connection connection=null;
Session session =null;
try {
//通过ConnectionFactory工厂对象创建一个connection链接
connection = connectionFactory.createConnection();
connection.start();
//通过Connection对象创建Session会话,用于接收消息,参数1为是否启用事务,参数2为签收模式,一般设置自动签收。
session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
//通过Session创建Destination对象
Destination destination =session.createQueue("queue1");
//通过Session对象创建消息的发送和接收(生产者和消费者)MessageProducer和MessageConsumer
MessageProducer messageProducer = session.createProducer(destination);
//使用MessageProducer的setDeliveryMode为其设置持久化特性和非持久化特性
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//最后使用JMS规范的TextMessage形式创建数据,用messageProducer.send()发送数据
for(int i=1;i<=5;i++){
TextMessage textMessage = session.createTextMessage();
textMessage.setText("我是消息内容,请消费我"+i);
messageProducer.send(textMessage);
System.out.println("生产者:"+textMessage.getText());
}
} catch (JMSException e) {
e.printStackTrace();
if(connection!=null){
connection.close();
}
}
消费者代码
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,ActiveMQConnectionFactory.DEFAULT_PASSWORD,"tcp://localhost:61616");
Connection connection=null;
try {
connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
Destination destination =session.createQueue("queue1");
while(true){
TextMessage msg = (TextMessage)messageConsumer.receive();
if(msg==null) break;
System.out.println("收到的内容:"+msg.getText());
}
} catch (JMSException e) {
e.printStackTrace();
if(connection!=null){
connection.close();
}
}
从控制台可以看出,5条消息被成功消费了。
1.6实战Topic
代码如下
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection connection = factory.createConnection();
connection.start();
//创建一个Topic
Topic topic= new ActiveMQTopic("testTopic");
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//注册消费者1
MessageConsumer comsumer1 = session.createConsumer(topic);
comsumer1.setMessageListener(new MessageListener(){
public void onMessage(Message m) {
try {
System.out.println("Consumer1 get " + ((TextMessage)m).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
//注册消费者2
MessageConsumer comsumer2 = session.createConsumer(topic);
comsumer2.setMessageListener(new MessageListener(){
public void onMessage(Message m) {
try {
System.out.println("Consumer2 get " + ((TextMessage)m).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
//创建一个生产者,然后发送多个消息。
MessageProducer producer = session.createProducer(topic);
for(int i=0; i<10; i++){
producer.send(session.createTextMessage("Message:" + i));
}
1.7总结
本篇文章介绍了JMS,并且对点对点模式和发布订阅模式进行了实战。下篇文章我们将介绍ActiveMQ的其他特性,集群以及与spring的集成。
1.8参考资料
https://my.oschina.net/90888/blog/825120