一,异步消息
像RMI和Hession/Burlap这些远程调用机制都是同步的,当客户端调用远程方法时,客户端必须等到远程方法完成后,才可以继续执行,但是有些操作是不需要等待,这时就可以用异步消息。
有两个主要概念,消息代理(message broker)和目的地(destination),当一个应用消息发送时,会将消息发送给消息代理,消息代理可以确保消息被投递到目的地,同时解放发送者,使其可以继续进行其他的业务。
不同的消息系统会有不同的消息路由模式,但是有两种通用的目的地,队列(queue)和主题(topic),每种类型与特定的消息模型关联,分别是点对点模型和发布/订阅模型。
点对点模型:每条消息只会发送给一个接收者,消息投递后就会从队列中删除,所以可以确保每条消息只有一个接收者,但是并不意味着一个队列只可以有一个接收者,一个队列可以有多个接收者。
发布/订阅模型:主题的所有订阅者都可以接收到消息的副本。
二,异步消息相比同步rpc的优点
1,无需等待
2,面向消息和解耦,与面向方法调用rpc通信不同,发送异步消息是以数据为中心的,客户端没有与特定的方法签名绑定
3,与远程服务位置解耦(位置独立),同步rpc服务一般需要网络地址定位,在使用消息后,客户端只需要知道通过那个队列或主题来发送消息,服务只需要能够从队列或主题中获取消息即可。基于此(位置独立),在点对点模型中,当服务过载时,可以添加服务实例来监听相同的队列就可以增加处理能力;在发布/订阅模型中,多个服务可以订阅同一个主题,但是每个服务的处理逻辑可以不同。例如,一组服务共同监听新员工消息这个主题,其中一个服务可以在工资系统中增加该员工,一个系统可以将新员工添加到HR门户中,还有一个服务可以为新员工分配可访问系统权限。
4,确保投递,即使消息发送时服务无法使用,消息也会存储起来,直到服务可用为止。
三,Java消息服务JMS(Java Message Service)
JMS是Java EE的标准/规范之一,这种规范指出:消息发送应该是一步的,非阻塞的,发送者和接收者可以说是互不影响。jms只是java ee中定义的一组标准的API,它自身并不是一个消息服务系统,它是消息传送服务的一个抽象,也就是它定义了消息传送的接口但是并没有具体的实现,也就是说jms只是一组接口而已。
而ActiveMQ就是JMS规范的具体实现,它是apache下的一个项目,采用java开发;就像JDBC抽象了关系数据库的访问,JPA抽象了对象与关系数据库的映射,JMS的具体实现由不同的消息中间件厂商提供,而Apache ActiveMQ就是一个,所以ActiveMQ是消息服务系统,而JMS不是。
四,以下是一个使用ActiveMQ的队列消息demo
消息发送端:
package com.uiao.activemq;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;
import javax.jms.*;
/**
* 点对点消息发送端
*
* Created by uiao on 2018/3/10.
*/
public class QueueProducer {
public static Logger logger = Logger.getLogger(QueueProducer.class);
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageProducer messageProducer;
// 在这可以根据用户名和密码以及url使用有参的构造函数创建链接工厂
connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
destination = session.createQueue("mq-queue");
messageProducer = session.createProducer(destination);
//DeliveryMode.PERSISTENT 当activemq关闭的时候,队列数据将会被保存
//DeliveryMode.NON_PERSISTENT 当activemq关闭的时候,队列里面的数据将会被清空
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
sendMessage(session, messageProducer);
messageProducer.setTimeToLive(1000000);
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception {
for (int i = 0; i < 20; i++) {
Thread.sleep(100);
// 消息转化器可以自定义或者用jmsMessagingTemplate
TextMessage textMessage = session.createTextMessage("第 " + i + " 条文本消息");
// 传递序列化对象,传递javaBean,TopicProducer就是用的这个
MqBean bean = new MqBean();
bean.setAge(24);
bean.setName("uiao");
ObjectMessage objectMessage = session.createObjectMessage(bean);
// 传递流,用来传递文件
StreamMessage streamMessage = session.createStreamMessage();
// 传递map
MapMessage mapMessage = session.createMapMessage();
// 传递文件
BytesMessage bytesMessage = session.createBytesMessage();
logger.info("发送消息:" + textMessage.getText());
messageProducer.send(textMessage);
}
}
}
Session的方法createSession(boolean var1, int var2) 第一个参数为true是表示支持事务,后面的自动确认为Session.SESSION_TRANSACTED
第一个参数为false时,var2的是可以为Session.AUTO_ACKNOWLEDGE,Session.CLIENT_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE其中一个。
消息接收端:
package com.uiao.activemq;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;
import javax.jms.*;
/**
* 点对点消息接收端
*
* Created by uiao on 2018/3/10.
*/
public class QueueConsumer {
private static Logger logger = Logger.getLogger(QueueConsumer.class);
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageConsumer messageConsumer;
connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); // 需要接收端确认
destination = session.createQueue("mq-queue");
messageConsumer = session.createConsumer(destination);
// 不使用监视器的情况
while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive(1000); // 设置超时时间,超过不等待消息
if (textMessage != null) {
logger.info("收到的消息:" + textMessage.getText());
textMessage.acknowledge();
} else {
break;
}
}
// 使用监视器的情况
/*messageConsumer.setMessageListener(new MessageListener() {
// 实现了一个监听器
@Override
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
//MqBean mqBean = (MqBean) ((ObjectMessage) message).getObject();
if (null != message) {
logger.info("收到的消息:" + textMessage.getText());
}
// 如果session设置为Session.CLIENT_ACKNOWLEDGE,要加上这一步
message.acknowledge();
} catch (Exception e) {
e.printStackTrace();
}
}
});*/
} catch (Exception e) {
e.printStackTrace();
}
}
}
打开浏览器,进入管理页面http://localhost:8161/admin/queues.jsp
密码可以在apache-activemq-5.15.4\conf\jetty-realm.properties设置:
输入密码后进入管理页面
执行完QueueProducer发送端后,可以看到该队列有0个接受端,总共有20条消息,其中0条消息已经处理了,20条消息尚未处理。
然后我们开两个QueueConsumer接收端,再一次执行发送端后查看后台
发送端控制台打印:
接收端1控制台打印:
接收端2控制台打印:
可以看到我们起的两个接收端服务几乎平均的把消息接收了
再看一下管理页面:
接收端从0个变成了2个,消息总数和处理的消息也从20变为40。
主题和队列在使用上一致,只不过主题使用Topic实现Destination接口。
除此之外,针对冗长和复杂的JMS代码,我们还可以使用Spring提供的JMS模板JmsTemplate来创建连接,获得会话,以及发送和接受消息;JmsTemplate还可以处理所有的抛出的JMSException异常。
五,几个常见问题
1,消息确认机制
消息只有被确认之后,才认为是被成功消费,然后消息才会从队列或主题中删除。
客户端接收消息 = 》 消息处理 =》 消息确认
四种确认机制:
(1)、Session.AUTO_ACKNOWLEDGE;客户(消费者)成功从receive方法返回时,或者从MessageListener.onMessage方法成功返回时,会话自动确认消息,然后自动删除消息.
(2)、Session.CLIENT_ACKNOWLEDGE;客户通过显式调用消息的acknowledge方法确认消息,。 即在接收端调用message.acknowledge();方法,否则,消息是不会被删除的.
(3)、Session. DUPS_OK_ACKNOWLEDGE ;不是必须确认,是一种“懒散的”消息确认,消息可能会重复发送,在第二次重新传送消息时,消息头的JMSRedelivered会被置为true标识当前消息已经传送过一次,客户端需要进行消息的重复处理控制。
(4)、 Session.SESSION_TRANSACTED;事务提交并确认。
2,丢消息怎么办?
可以设置持久化消息,messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
使用acknowledge()方法,客户端返回确认信息。
3,事务消息
创建回话Session使用transacted = true,事务消息在发送和接收后必须显示的调用session.commit(),事务消息会自动确认。与设置的确认机制无关
3,消息接收方式
receive方法接收,一次即结束;使用监听器接收,实现onMessage方法,作为一个服务实时监测。
项目地址:https://gitee.com/uiaoadf/myActiveMQ
https://www.jianshu.com/p/b1a5acac15ef
《Spring 实战》