title:ActiveMQ实践
date:2017年10月27日00:03:29
在介绍了一些JMS规范和概念之后,我们开始在Java程序中使用ActiveMQ。
首先,我们编写一个发送者程序。
package com.wangcc.activemq.cluster.queue;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class QueueSender {
public static void main(String[] args) throws Exception {
//1
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://10.*.*.66:61616");
//2
Connection connection = connectionFactory.createConnection();
// 默认是关闭,所以这里需要开启
//3
connection.start();
/*
* 当没有事务的时候,是由签收模式来决定是否签收的
*
*/
//4
Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
//5
Destination destination = session.createQueue("my-queue");
//6
MessageProducer producer = session.createProducer(destination);
for (int i = 0; i < 3; i++) {
//7
TextMessage textMessage = session.createTextMessage();
textMessage.setText("KOBEeee" + i);
//8
producer.send(textMessage);
}
//9
session.commit();
session.close();
connection.close();
}
}
1.创建ActiveMQConnectionFactory链接工厂。当没有手动的去更改用户名和密码时(如何更改密码已经在之前的博客linux下安装activemq中说过了),不需要在新建ActiveMQConnectionFactory时显示指定用户名和密码,而只需要指定url链接。
2.通过链接工厂创建链接ActiveMQ消息服务器的链接,注意链接默认是关闭的。(Connection代表了应用程序和消息服务器之间的通信链路)
3.手动开启链接,在实际使用中,何时开启链接是有讲究的。
4.通过链接connection创建session,session是发送或接收消息的一个会话,代表这接收或发送消息的上下文环境,是发送或接收消息的核心对象。注意创建session的时候我们需要指定两个属性,一个是否开启事务,一个是指定签收模式,这里开启事务,签收模式随意。具体什么是签收模式后面会细讲。
5.通过Sesion创建Destination对象,当PTP模式下是队列,在pub/sub模式下是主题(TOPIC),指定发送的目的地。
6.通过Session创建发送消息的生产者
7.定义JMS规范的消息类型,填写消息内容
8.生产者发送消息。
9.session.commit()在commit的时候真正发送消息,而且是3条消息一起发送,使用事务,可以批量发送。
最后释放链接。
写好发送QUEUE程序后,我们直接运行该程序,注意,此时接收方程序还尚未编写。
这时候我们去访问控制台,发现队列中新增了个叫my-queue的队列,并且显示有三个消息进入队列了。
此时我们编写接收方。
package com.wangcc.activemq.cluster.queue;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* @ClassName: QueueReceiver
* @Description:
*
* QUEUE默认持久化,默认持久化方式kahadb,所以在发送端发送消息的时候,消费者不要求一定在线
* 即使当ActiveMQ宕机了之后,重启AcitveMQ后,消费者还是能取到之前生产者发送的消息
*
*
*
* @author wangcc
* @date 2017年10月26日 下午6:58:15
*
*/
public class QueueReceiver {
public static void main(String[] args) throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://10.*.*.66:61616");
Connection connection = connectionFactory.createConnection();
connection.start();
// 当有事务时,是由session.commit事务提交与否来确定是否消费者确认签收,我们测试下,我们不commit,我们可以无数次接受到该消息
Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageConsumer messageConsumer = session.createConsumer(destination);
int i = 0;
while (i < 3) {
TextMessage textMessage = (TextMessage) messageConsumer.receive();
System.out.println("接收的消息:" + textMessage.getText());
i++;
}
session.commit();
session.close();
connection.close();
}
}
- 我们首先在创建Session的时候使用事务,然后运行程序,我们会发现,我们能够接收到三条消息,然后程序停止运行。
这说明一点,对于QUEUE类型的消息,生产者和消费者之间是没有时间相关性的,QUEUE队列的发送是默认持久化的,当生产者发送消息之后,ActiveMQ就会将消息存起来,然后等到消费者上线之后去收消息,MQ就会把消息给消费者,消费者无需在生产者发送消息之前存在。但是还有一点需要明确,就是一个队列形式发送的消息只能被一个消费者消费,如果被一个消费者消费之后呢,另一个消费者就没有办法在消费那条消息了,注意了,是一个队列形式的消息对应一个消费者,而不是一个队列对应一个消费者,一个队列可以包含多条信息,也就意外着可能有多个消费者去分享这个消息,但是每一条都只能被一个消费者消费。这一个点和后面讲的TOPIC是不同的。
我们再次运行接收方程序,我们发现没有任何的消息再次被接收了。现在我们将上述程序中的session.commit();给注释掉,这个时候我们再次启动发送方发送三条消息,然后我们运行接收方,运行第一次,接收三条消息,没问题,然后再运行第二次,仍然可以接收三条消息,第三次第四次等等,你发现,每次都能再接收消息,这是为什么呢?这是因为当有事务时,是由session.commit事务提交与否来确定是否消费者确认签收
那什么叫做签收呢,也就是说消费者消费之后向MQ告知我已经收到这个消息,你可以把这个消息给干掉了,免得浪费你的空间。一旦告知MQ已经接收到这个消息后,再次请求这个消息,MQ就不会再发给你了。
接着我们将创建Session时的事务关掉,这个时候签收模式就会有创建Session时指定的第二个参数来决定。
第二个参数一共有三个可选择的值:
- Session.AUTO_ACKNOWLEDGE,当客户成功的从receive方法返回或者从MessageListener.onMessage中返回的时候,会话自动确认客户收到的信息。
- Session.CLIENT_ACKNOWLEDGE,客户通过调用消息的acknowledge方法确认消息。需要注意的是,在这种模式中,确认是在会话层进行的,确认一个消息自动确认所有已被会话消费的消息。例如,如果一个消费者消费了10个消息,然后确认第五个信息,那么所有的10个消息都会被确认。
- Session.DUPS_OK_ACKNOWLEDGE,签不签收无所谓了,只要消费者能够容忍重复的消息接受,当然这样会降低Session的开销
那么在实际的开发中呢,我们是推荐使用CLIENT_ACKNOWLEDGE接收端手动确认的,因为我们成功的接收并不代表我们接收端成功的处理了消息,我们最好是在客户端正确的处理完消息后,再由客户端手动确认签收比较好。
这里我们可以测试一下CLIENT_ACKNOWLEDGE
将接收端的程序稍微改一下
while (i < 3) { TextMessage textMessage = (TextMessage) messageConsumer.receive(); System.out.println("接收的消息:" + textMessage.getText()); if (i == 1) { textMessage.acknowledge(); } i++; }
发送端执行一次,发送三条消息,然后执行接收端,成功收到三条消息,然后再执行一次接收端,你会发现你这次只接收到了一条消息。这是为什么呢?
当第一次执行接收端程序时,当i=1时,执行了 textMessage.acknowledge();进行了手动的签收,此时消费者一共接收了两条消息,这个时候也就代表着消费者确认了两条消息,因为上面说了,在CLIENT_ACKNOWLEDGE模式中,确认是在会话层进行的,确认一个消息自动确认所有已被会话消费的消息。那么也就是说队列中还有一个消息未被确认,这是运行第二次接收端,当这个循环执行第一次的时候,会接收到之前还没确认接收的一条消息。之后你继续运行n次接收端,都只会接收到这一条消息。