java消息服务之JMS简介
回顾JavaEE是什么
JavaEE是一套使用java进行企业级应用开发的大家一致遵循的13个核心规范工业标准。JavaEE平台提供了一个基于组件的方法来加快设计、开发、装配及部署企业应用程序。
- JDBC(java Database)数据库连接
- JNDI(java Naming and Directory interfaces)java的命名和目录接口
- EJB(Enterprise JavaBean)
- RMI(Remote Method Invoke)远程方法调用
- Java IDL(Interface Description Language)/CORBA(Common Object Broker Architeture)接口定义语言/公用对象请求代理程序体系结构
- JSP(Java Server Pages)
- Servlet
- XML(Extensible Markup Language)可扩展白标记语言
- JMS(Java Message Service)java消息服务
- JTA(Java Transaction API)Java事物API
- JTS(Java Transaction Services)Jave 事物服务
- JavaMail
- JAF(JavaBean Activation Framework)
什么是Java消息服务
JMS(java Message Service)java消息服务是javaEE中的一个技术。
Java消息服务指的是两个应用程序之间进行异步通行的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持java应用程序开发。在javaEE中,当两个应用程序使用JMS进行通信时,他们之间并不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步消峰的效果。
JMS的组成结构及特点
1. JMS provider : 实现JMS接口和规范的消息中间件,也就是我们的MQ服务器
2. JMS producer : 消息生产者,创建和发送JMS消息的客户端应用
3. JMS consumer: 消息消费者,接受和处理JMS消息的客户端应用
4. JMS message: 有三个即: 消息头: 消息属性: 消息体:
消息头
- JMSDestination: 消息发送的目的地,只要是指Queue和Topic
- JMSDeliveryMode:持久模式和非持久模式
一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后在传递。
一条非持久消息:最多会传送一次,这意味着这服务器出现故障,该消息将永远丢失。 - JMSExpiration:可以设置消息在一定时间后过期,默认是永不过期
消息的过期时间,等于Destination的 send方法中的 timeToLive值加上发送时刻的GMT时间值
如果timeToLive值等于零,则JMSExpiration被设置为零,表示消息永不过期。
如果发送后,在消息过期时间之后消息还没有被发送到目的地,则该消息被清除。 - JMSPriority:消息的优先级,从0-9十个级别,0到4是普通消息,5到9是加急消息
JMS不要求MQ严格按照这十个优先级发送消息,但必须保证加急消息要先于普通消息到达,默认是4。 - JMSMessageID : 唯一识别每个消息的标识由MQ产生。
消息体
- 定义:封装具体消息的数据
- 5种消息体格式:
1 TextMessage : 普通字符串消息,包含一个String
2 MapMessage: 一个Map类型的消息,key为String类型,而值为java的基本类型
3 BytesMessage: 二进制数组包含一个byte[]
4 StreamMessage : java数据流消息,用标准流操作来顺序的填充和读取
5 ObjectMessage : 对象消息,包含一个可序列化的java对象
例:MapMessage
//6.通过使用messageProducer生产3条消息发送到MQ的队列里面
for (int i=1;i<=3 ; i++){
//7.创建消息
TextMessage textMessage = session.createTextMessage("msg------"+i);//理解为一个字符串
//8.通过messageProducer发送给mq
messageProducer.send(textMessage);
//通过MapMessage的方式发送消息
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("k1","mapMessage-----v1");
messageProducer.send(mapMessage);
}
- 发送和接受消息体必须一致
消息属性
如果需要除消息头字段以外的值,那么可以使用消息属性是识别/去重/重点标识等操作非常有用的方法
定义:他们是以属性名和属性值对的形式制定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息,比如可以在属性里面指定消息选择器
消息的属性就像可以分配给一条消息的附加消息头一样。他们允许开发者添加有关消息的不透明附加消息
它们还用于暴露消息选择器在消息过滤时使用的数据
TextMessage message = session.createMessage();
Message.setText(text);
Message.setStringPropety(“username”,”Z1”); //自定义属性
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
//消费的消息要和接受的消息的格式一致
if(null != message && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("***消费者接受到消息:" + textMessage.getText());
System.out.println("***消费者接受到消息:" + textMessage.getStringProperty("c01"));
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != message && message instanceof MapMessage){
MapMessage mapMessage = (MapMessage) message;
try {
System.out.println("***消费者接受到消息:" + mapMessage.getString("k1"));
System.out.println("***消费者接受到消息:" + mapMessage.getStringProperty("k1"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
消息的可靠性
包括:持久性、事物、签收等
PERSISTENT:持久性
参数说明
1.非持久
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
非持久化:当服务器宕机时,消息不存在
2.持久
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
持久化:当服务器宕机时,消息依然存在
持久的Queue
持久化消息:这是队列的默认传送模式,此模式保证这些消息只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的元素。
可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。
持久的Topic
1.代码步骤
先启动订阅在启动生产
先运行一次消费者,等于想MQ注册,类似于我订阅了这个主题
然后在运行生产者发送消息,此时
无论消费者是否在线,都会接受到,不在线的话,下次连接的时候,会把没有收到的消息都接受下来
先启动消费者,Z3处于订阅的持久的激活状态
在启动生产者 5秒之后Z3处于离线状态
2.控制台
3.生产者代码
public class JmsProduce_Topic_Persist {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String TOPIC_NAME="Topic-Persist";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer messageProducer = session.createProducer(topic);
//设置队列持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
//设置启动模式
connection.start();
for (int i = 1; i <= 3 ; i++) {
TextMessage textMessage = session.createTextMessage("msg-persist-" + i);
messageProducer.send(textMessage);
}
//关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("********消息发送到topic-Persist成功");
}
}
4.消费者代码
public class JmsConsumer_Topic_Persist {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String TOPIC_NAME="Topic-Persist";
public static void main(String[] args) throws JMSException {
System.out.println("********Z3");
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
//获取订阅用户的ID
connection.setClientID("z3");
Session session = connection.createSession(Boolean.FALSE , Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
//设置订阅的持久化操作(发布订阅)
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark••••");
//启动消费者
connection.start();
Message message = topicSubscriber.receive();
while(null != message){
TextMessage textMessage = (TextMessage) message;
System.out.println("*******收到持久化的Topic"+textMessage.getText());
//设置5秒钟 从激活状态变成离线状态
message = topicSubscriber.receive(5000L);
}
session.close();
connection.close();
}
}
5. 类似微信公众号订阅发送
TRANSACTION 事物
Producer 提交时的事物有两个 false 和true
false
只要执行send,就进入到队列中
关闭事务,那第2个签收参数的设置需要有效
true
先执行send在执行commit,消息才被真正的提交到队列中
消息需要批量发送,需要缓冲区处理
例
//事物的生产者
public class JmsProduce_TX {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String QUEUE_NAME="tx-01";
public static void main(String[] args) throws JMSException {
//1.创建连接工场
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.创建连接,启动服务
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话的session;有两个参数 第一个是事物,第二个是签收。
// 当事物设置为false时默认对提交,当事物设置为true 在session关闭之前需要commit提交
Session session = connection.createSession(Boolean.TRUE , Session.AUTO_ACKNOWLEDGE);
//4.创建目的地
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.使用MessageProducer创建3条消息放到队列中
for (int i = 1; i <=3 ; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("tx-msg*******"+i);
//8.通过messageProducer发送给MQ
messageProducer.send(textMessage);
}
//9.关闭资源,提交数据
messageProducer.close();
//10.事物提交
session.commit();
session.close();
connection.close();
System.out.println("消息发送成功*******TX");
}
}
//事物介绍之消费者
public class JmsConsumer_Tx {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String QUEUE_NAME="tx-01";
public static void main(String[] args) throws JMSException {
//1.创建连接工场
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工场获得连接connection,并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3. 创建会话Session
//两个参数第一个参数表示事物,第二个参数表示签收AUTO_ACKNOWLEDGE表示默认
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(分为两种一个是队列Queue,一个是主题Topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while(true){
//接受消息与之前的发送消息的格式对应
TextMessage textMessage = (TextMessage) messageConsumer.receive(4000L);
//第二种方法带时间等待表示4秒钟之后就会停止消息的消费:TextMessage textMessage = (TextMessage) messageConsumer.receive(4000l);
if (null != textMessage){
System.out.println("****消费者接受消息"+ textMessage.getText());
}else {
break;
}
}
messageConsumer.close();
session.commit();
session.close();
connection.close();
System.out.println("tx");
}
事务偏生产者/签收偏消费者
Acknowledge 签收
非事物签收
自动签收(默认) : Session.QUTO_ACKNOWLEDGE
手动签收 : Session.CLIENT_ACKNOWLEDGE (客户端调用acknowledge手动签收-----message.acknowledge() ;)
//生产者原始代码不变
public class JmsConsumer_Tx {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String QUEUE_NAME="tx-01";
public static void main(String[] args) throws JMSException {
//1.创建连接工场
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工场获得连接connection,并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3. 创建会话Session
//两个参数第一个参数表示事物,第二个参数表示签收AUTO_ACKNOWLEDGE表示默认
Session session = connection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);
//4.创建目的地(分为两种一个是队列Queue,一个是主题Topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while(true){
//接受消息与之前的发送消息的格式对应
TextMessage textMessage = (TextMessage) messageConsumer.receive(4000L);
//第二种方法带时间等待表示4秒钟之后就会停止消息的消费:
if (null != textMessage){
System.out.println("****消费者接受消息"+ textMessage.getText());
//当手动签收之后避免重复消费的补救措施
textMessage.acknowledge();
}else {
break;
}
}
messageConsumer.close();
session.close();
connection.close();
System.out.println("tx");
}
}
允许重复消息 : Session.DUPS_OK_ACKNOWLEDGE
事物签收
生产者不牵涉到签收,所以当事物定义为true时,针对的后面的第二个参数即签收的意义就不是很大了。
生产事物开启,只有commit后才能将全部的消息变为已消费
消费事物开启,不管是手动签收还是自动签收,只要有commit都不影响其结果
//事物生产
public class JmsProduce_TX {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String QUEUE_NAME="tx-01";
public static void main(String[] args) throws JMSException {
//1.创建连接工场
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.创建连接,启动服务
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话的session;有两个参数 第一个是事物,第二个是签收。
// 当事物设置为false时默认对提交,当事物设置为true 在session关闭之前需要commit提交
Session session = connection.createSession(Boolean.TRUE , Session.AUTO_ACKNOWLEDGE);
//4.创建目的地
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.使用MessageProducer创建3条消息放到队列中
for (int i = 1; i <=3 ; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("tx-msg*******"+i);
//8.通过messageProducer发送给MQ
messageProducer.send(textMessage);
}
//9.关闭资源,提交数据
messageProducer.close();
//10.事物提交
session.commit();
session.close();
connection.close();
System.out.println("消息发送成功*******TX");
}
}
// 事物消费
public class JmsConsumer_Tx {
public static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
public static final String QUEUE_NAME="tx-01";
public static void main(String[] args) throws JMSException {
//1.创建连接工场
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.通过连接工场获得连接connection,并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3. 创建会话Session
//两个参数第一个参数表示事物,第二个参数表示签收AUTO_ACKNOWLEDGE表示默认;CLIENT_ACKNOWLEDGE是手动签收
Session session = connection.createSession(Boolean.TRUE, Session.CLIENT_ACKNOWLEDGE);
//4.创建目的地(分为两种一个是队列Queue,一个是主题Topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while(true){
//接受消息与之前的发送消息的格式对应
TextMessage textMessage = (TextMessage) messageConsumer.receive(4000L);
//第二种方法带时间等待表示4秒钟之后就会停止消息的消费
if (null != textMessage){
System.out.println("****消费者接受消息"+ textMessage.getText());
//当手动签收之后避免重复消费的补救措施,当为事物时这句话可有可无
//textMessage.acknowledge();
}else {
break;
}
}
messageConsumer.close();
session.commit();
session.close();
connection.close();
System.out.println("tx");
}
}
事物和签收的关系
在事物会话中,当一个事物被成功提交则消息被自动签收。如果事物回滚,则消息会被在次传送
非事物会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)
JMS点对点总结
点对点模式是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能和我们平时给朋友发送短息类似。
- 如果在Session关闭时有部分消息已被收到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收。
- 队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势。
JMS发布订阅总结
- JMS Pub/Sub 模式定义了如何向一个内容节点发布和订阅消息,这些节点被称作topic
- 主题可以被认为是消费的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息
- 主题使得消息订阅者和消息发布者保持相互独立,不需要接触即可保证消息的传送
非持久订阅
非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收到发送到某个主题的消息
如果消费者处于离线状态,生产者发送的主题消息将会被丢失作废,消息永远不会收到。
先订阅注册才能接受到发布,只给订阅者发布消息
持久订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID报错所有发送到主题的消息,当客户端再次连接到MQ时会根据消费者的ID得到所有自己处于离线时发送到主题的消息。
非持久订阅状态下,不能恢复或重新派送一个未签收的消息。
持久订阅才能恢复或重新派送一个未签收的消息。