ActiveMQ学习记录(一)
对应ActiveMQ这系列的文章,LZ大多是学习一些大神的博客,然后将这些东西汇聚起来。那么我们话不多说,开始学习ActiveMQ吧。
首先介绍下一些概念性的东西:
JMS
首先来说较早以前,也就是没有JMS的那个时候,很多应用系统存在一些缺陷:
1.通信的同步性
client端发起调用后,必须等待server处理完成并返回结果后才能继续执行2.client 和 server 的生命周期耦合太高
client进程和server服务进程都必须可用,如果server出现问题或者网络故障,那么client端会收到异常3.点对点通信
client端的一次调用只能发送给某一个单独的服务对象,无法一对多
JMS,即Java Message Service,通过面向消息中间件(MOM:Message Oriented Middleware)的方式很好的解决了上面的问题。大致的过程是这样的:发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题中,在合适的时候,消息服务器会将消息转发给接受者。在这个过程中,发送和接受是异步的,也就是发送无需等待,而且发送者和接受者的生命周期也没有必然关系;在pub/sub模式下,也可以完成一对多的通信,即让一个消息有多个接受者。
需要注意的是,JMS只是定义了Java访问消息中间件的接口,其实就是在包javax.jms中,你会发现这个包下除了异常定义,其他都是interface。我们可以扫一眼,比如Message:
我想你应该发现了,JMS只给出接口,然后由具体的中间件去实现,比如ActiveMQ就是实现了JMS的一种Provider。这些消息中间件都符合JMS规范。说起规范,自然要定义一些术语:
Provider/MessageProvider:生产者
Consumer/MessageConsumer:消费者
PTP:Point To Point,点对点通信消息模型
Pub/Sub:Publish/Subscribe,发布订阅消息模型
Queue:队列,目标类型之一,和PTP结合
Topic:主题,目标类型之一,和Pub/Sub结合
ConnectionFactory:连接工厂,JMS用它创建连接
Connnection:JMS Client到JMS Provider的连接
Destination:消息目的地,由Session创建
Session:会话,由Connection创建,实质上就是发送、接受消息的一个线程,因此生产者、消费者都是Session创建的
初步来看,Session非常核心,因为很多东西都是它创建的,在后文中可以通过代码来进一步认识这些术语。
这里先了解下在MQ中只有两种消息模式,就是上面说的点对点和订阅消息模式,何为点对点,何为订阅消息呢。解释如下:
点对点的消息模式:主要建立在一个队列上面,当连接一个列队的时候,发送端不需要知道接收端是否正在接收,可以直接向ActiveMQ发送消息,发送的消息,将会先进入队列中,如果有接收端在监听,则会发向接收端,如果没有接收端接收,则会保存在activemq服务器,直到接收端接收消息,点对点的消息模式可以有多个发送端,多个接收端,但是一条消息,只会被一个接收端给接收到,哪个接收端先连上ActiveMQ,则会先接收到,而后来的接收端则接收不到那条消息。
订阅消息模式:同样可以有着多个发送端与多个接收端,但是接收端与发送端存在时间上的依赖,就是如果发送端发送消息的时候,接收端并没有监听消息,那么ActiveMQ将不会保存消息,将会认为消息已经发送,换一种说法,就是发送端发送消息的时候,接收端不在线,是接收不到消息的,哪怕以后监听消息,同样也是接收不到的。这个模式还有一个特点,那就是,发送端发送的消息,将会被所有的接收端给接收到,不类似点对点,一条消息只会被一个接收端给接收到。
Start
对于访问ActiveMQ web控制台的用户名、密码在哪里配置的?URL当中的端口是在哪里配置的?
用户名密码配置在conf/jetty-realm.properties文件,格式如***解说的很明白,格式是:用户名 : 密码 ,角色名
URL端口等配置在conf/jetty.xml中
好了,现在启动好Active后,来写一个HelloWorld的Demo来感受下ActiveMQ,具体流程就是这边写一个生产者用于发送消息,一个消费者来接收消息。具体步骤如下
第一步:创建ConncectionFactory连接工厂
事实上,这里有安全问题, 也就是任何人一旦知道MQ的地址,就可以连接访问了,所以我们可以在activemq.xml中配置指定的用户、密码才能访问ActiveMQ。
关于broker_bind_url,默认是tcp://localhost:61616,说明采用的是TCP协议,端口是61616端口,ActiveMQ不仅仅支持TCP,还有其他协议,可以开启这些端口,如下:
第二步:创建Conncection
Session,用于发送和接收消息,而且是单线程的,支持事务的,如果Session开启事务支持,那么Session将保存一组信息,要么commit到MQ,要么回滚这些消息。Session可以创建MessageProducer/MessageConsumer。
第四步:创建Destination
所谓消息目标,就是消息发送和接受的地点,要么queue,要么topic。注意后面生产者消费者要发在同一个Queue里面,这里就是MY_QUEUE这个队列。
第五步:创建MessageProducer(生产者)/MessageConsumer(消费者)
第六步:在生产端设置持久化方式
第七步:定义消息对象,并发送
生产者和消费者之间传递的对象主要是由3部分构成:消息头(路由)+消息属性(消息选择器,以后介绍)+消息体(JMS规范的五种类型消息)
第八步:生产者释放连接
必须close connection,只有这样ActiveMQ才会释放资源!消费者的代码和上面非常类似,只不过就是创建MessageConsumer进行receive而已,注意receive()/receive(long)/receiveNoWait(),这些说明消费者可以采用阻塞模式、非阻塞模式接受消息。不过这边我使用另一种更好的方式,让消费者实现一个消息的监听,然后监听到消息的话就接收并处理。
第九步:消费者实现监听
第十步:测试
启动Consumer后启动Producer,出现如下结果
同时在WEB端管控台也可以看出
Messages Enqueued:表示生产了多少条消息,记做P
Messages Dequeued:表示消费了多少条消息,记做C
Number Of Consumers:表示在该队列上还有多少消费者在等待接受消息
Number Of Pending Messages:表示还有多少条消息没有被消费,实际上是表示消息的积压程度,就是P-C
其他介绍:
在说说Session:在通过Connection创建Session的时候,需要设置2个参数,一个是否支持事务,另一个是签收的模式。我们重点说一下签收模式:
什么是签收?通俗点说,就是消费者接受到消息后,需要告诉消息服务器,我收到消息了。当消息服务器收到回执后,本条消息将失效。因此签收将对PTP模式产生很大影响。如果消费者收到消息后,并不签收,那么本条消息继续有效,很可能会被其他消费者消费掉!
AUTO_ACKNOWLEDGE:表示在消费者receive消息的时候自动的签收 CLIENT_ACKNOWLEDGE:表示消费者receive消息后必须手动的调用acknowledge()方法进行签收DUPS_OK_ACKNOWLEDGE:签不签收无所谓了,只要消费者能够容忍重复的消息接受,当然这样会降低Session的开销
在实际中,我们应该采用哪种签收模式呢?CLIENT_ACKNOWLEDGE,采用手动的方式较自动的方式可能更好些,因为接收到了消息,并不意味着成功的处理了消息,假设我们采用手动签收的方式,只有在消息成功处理的前提下才进行签收,那么只要消息处理失败,那么消息还有效,仍然会继续消费,直至成功处理!
关于消息的priority/ttl/deliveryMode:
消息有优先级及存活时间,在MessageProducer进行send的时候,存在多个重载方法,我们来看一下:
在上面的code当中,我们创建生产者的时候,指定了Destination,设置了持久化方式,实际上这些都可以不必指定的,而是到send的时候指定。而且在实际业务开发中,往往根据各种判断,来决定将这条消息发往哪个Queue,因此往往不会在MessageProducer创建的时候指定Destination。
TTL,消息的存活时间,一句话:生产者生产了消息,如果消费者不来消费,那么这条消息保持多久的有效期 priority,消息优先级,0-9。0-4是普通消息,5-9是加急消息,消息默认级别是4。注意,消息优先级只是一个理论上的概念,并不能绝对保证优先级高的消息一定被消费者优先消费!也就是说ActiveMQ并不能保证消费的顺序性! deliveryMode,如果不指定,默认是持久化的消息。如果可以容忍消息的丢失,那么采用非持久化的方式,将会改善性能、减少存储的开销。
最后附上所有代码:
public class MQUtils {
private static String MQ_USER = ActiveMQConnectionFactory.DEFAULT_USER;
private static String MQ_PASSWORD = ActiveMQConnectionFactory.DEFAULT_PASSWORD;
private static String MQ_BROKE_URL = ActiveMQConnectionFactory.DEFAULT_BROKER_BIND_URL;
private static Session session = null;
private static final String MY_QUEUE = "myqueue";
private static ConnectionFactory createConnectionFactory(){
//创建连接工厂ConnectionFactory,需要账号密码,这里使用默认的账号密码
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
MQ_USER,MQ_PASSWORD,MQ_BROKE_URL
);
return connectionFactory;
}
private static Connection createConnection() throws JMSException {
//构造从工厂得到连接对象
Connection connection = createConnectionFactory().createConnection();
//注意,需要start下,不过测试过这个步骤不写也是可以的,但是最好写上
connection.start();
return connection;
}
public static Session createSession() throws JMSException {
if(session == null ){
//前面2步骤操作就是为了创建Session(上下文环境对象)做准备的
//创建Session有一些配置参数,比如是否启用事务,签收模式是什么(这里先设置为自动签收模式auto),具体以后详述
session = createConnection().createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
}
return session;
}
public static Destination createDestination() throws JMSException {
//通过Session创建Destination对象, 在PTPT模式下是queue,在pub/sub模式下是topic
//private static final String MY_QUEUE = "myqueue";
Destination destination = createSession().createQueue(MY_QUEUE);
return destination;
}
public static TextMessage createTextMessage(String message) throws JMSException {
//需要使用session创建,证明session的重要性
TextMessage textMessage = createSession().createTextMessage();
textMessage.setText(message);
return textMessage;
}
public static void close() throws JMSException {
if(session != null){
session.close();
}
}
}
public class Producer {
public static void main(String[] args) throws JMSException {
//通过session(MQUtils.createSession())创建 发送消息的生产者
MessageProducer producer = MQUtils.createSession().createProducer(MQUtils.createDestination());
//设置持久化/非持久化特性,如果非持久化,那么意味着MQ重启后消息将会丢失
//如果持久化到kahdb/leveldb/jdbc方式的浒,意味着消息持久化
//DeliveryMode.PERSISTENT 当activemq关闭的时候,队列数据将会被保存
//DeliveryMode.NON_PERSISTENT 当activemq关闭的时候,队列里面的数据将会被清空
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//定义JMS规范的消息类型,这里先使用简单的TextMessage
TextMessage textMessage = MQUtils.createTextMessage("Hello ActiveMQ!!");
producer.send(textMessage);
//关闭连接
MQUtils.close();
}
}
public class Consumer {
public static void main(String[] args) throws JMSException {
//通过session(MQUtils.createSession())创建 发送消息的消费者
MessageConsumer consumer = MQUtils.createSession().createConsumer(MQUtils.createDestination());
//实现一个消息的监听器
//实现这个监听器后,以后只要有消息,就会通过这个监听器接收到
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
//获取到接收的数据
String text = ((TextMessage)message).getText();
System.out.println("Consumer get : " + text);
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
第一篇就到这里END了,Thanks!
参考博文:https://www.jianshu.com/p/ecdc6eab554c