JMS是什么
什么是MS消息服务?
MS消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。
MQ的产品种类和对比
MQ就是消息中间件。MQ是一种理念,ActiveMQ是MQ的落地产品。不管是哪款消息中间件,都有如下一些技术维度:
(1)kafka
编程语言:scala。
大数据领域的主流MQ。
(2)rabbitmq
编程语言:erlang
基于erlang语言,不好修改底层,不要查找问题的原因,不建议选用。
(3)rocketmq
编程语言:java
适用于大型项目。适用于集群。
(4)activemq
编程语言:java
适用于中小型项目。
Jsm message 消息头
JMS的消息头有哪些属性:
JMSDestination:消息目的地
JMSDeliveryMode:消息持久化模式
JMSExpiration:消息过期时间
JMSPriority:消息的优先级
JMSMessageID:消息的唯一标识符。后面我们会介绍如何解决幂等性。
说明: 消息的生产者可以set这些属性,消息的消费者可以get这些属性。
这些属性在send方法里面也可以设置。
// 这里可以指定每个消息的目的地
textMessage.setJMSDestination(topic);
/*
持久模式和非持久模式。
一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。
一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
*/
textMessage.setJMSDeliveryMode(0);
/*
可以设置消息在一定时间后过期,默认是永不过期。
消息过期时间,等于Destination的send方法中的timeToLive值加上发送时刻的GMT时间值。
如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期。
如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除。
*/
textMessage.setJMSExpiration(1000);
/* 消息优先级,从0-9十个级别,0-4是普通消息5-9是加急消息。
JMS不要求MQ严格按照这十个优先级发送消息但必须保证加急消息要先于普通消息到达。默认是4级。
*/
textMessage.setJMSPriority(10);
// 唯一标识每个消息的标识。MQ会给我们默认生成一个,我们也可以自己指定。
textMessage.setJMSMessageID("ABCD");
// 上面有些属性在send方法里也能设置
messageProducer.send(textMessage);
Jsm message 消息体
5种消息体格式:
发送消息
//通过messageProducer给mq发送消息
for(int i = 1;i<=3;i++){
//创建消息 里面放消息内容
TextMessage textMessage = session.createTextMessage("textMessage---" + i);
//通过messageProducer发送给mq
messageProducer.send(textMessage);
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("k1","mapMessage---v1");
messageProducer.send(mapMessage);
}
接收消息
package com.mq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息消费者,同步阻塞方式
*/
public class JmsConsumerDemo01Map {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String QUEUE_NAME = "query01";
public static void main(String[] args) throws JMSException {
System.out.println("2号消费者");
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Queue queue = session.createQueue(QUEUE_NAME);
//创建消费者
//从指定的队列中取消息
MessageConsumer messageConsumer = session.createConsumer(queue);
//通过监听获取消息,有消息则消费,没消息则不管
//异步非阻塞方式(监听器onMessage())
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());
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != message && message instanceof MapMessage){
MapMessage mapMessage = (MapMessage)message;
try {
System.out.println("*****消费者收到消息:"+mapMessage.getString("k1"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
messageConsumer.close();
session.close();
connection.close();
}
}
Jsm message 消息属性
如果需要除消息头字段之外的值,那么可以使用消息属性。他是识别/去重/重点标注等操作,非常有用的方法。
他们是以属性名和属性值对的形式制定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息,比如可以在属性里指定消息选择器。消息的属性就像可以分配给一条消息的附加消息头一样。它们允许开发者添加有关消息的不透明附加信息。它们还用于暴露消息选择器在消息过滤时使用的数据。
下图是设置消息属性的API:
发送消息
textMessage.setStringProperty(“c01”,“vip”);
package com.mq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息生产者
*/
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String QUEUE_NAME = "query01";
public static void main(String[] args) throws JMSException {
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Queue queue = session.createQueue(QUEUE_NAME);
//创建消失生产者
MessageProducer messageProducer = session.createProducer(queue);
//通过messageProducer给mq发送消息
for(int i = 1;i<=3;i++){
//创建消息 里面放消息内容
TextMessage textMessage = session.createTextMessage("textMessage---" + i);
//设置唯一标识
textMessage.setStringProperty("c01","vip");
//通过messageProducer发送给mq
messageProducer.send(textMessage);
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("k1","mapMessage---v1");
messageProducer.send(mapMessage);
}
//关闭支援
messageProducer.close();
session.close();
connection.close();
System.out.println("****消息发送给mq****");
}
}
接收消息
System.out.println("*****消费者收到消息属性:"+textMessage.getStringProperty(“c01”));
package com.mq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息消费者,同步阻塞方式
*/
public class JmsConsumerDemo01Map {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String QUEUE_NAME = "query01";
public static void main(String[] args) throws JMSException {
System.out.println("2号消费者");
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Queue queue = session.createQueue(QUEUE_NAME);
//创建消费者
//从指定的队列中取消息
MessageConsumer messageConsumer = session.createConsumer(queue);
//通过监听获取消息,有消息则消费,没消息则不管
//异步非阻塞方式(监听器onMessage())
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"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
messageConsumer.close();
session.close();
connection.close();
}
}
队列(Queue)消息非持久和持久
topic默认就是非持久化的,因为生产者生产消息时,消费者也要在线,这样消费者才能消费到消息。
topic消息持久化,只要消费者向MQ服务器注册过,所有生产者发布成功的消息,该消费者都能收到,不管是MQ服务器宕机还是消费者不在线。
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); //非持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); //持久话 (默认)
服务器宕机后重启mq
发送消息
package com.mq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息生产者
*/
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String QUEUE_NAME = "query01";
public static void main(String[] args) throws JMSException {
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Queue queue = session.createQueue(QUEUE_NAME);
//创建消失生产者
MessageProducer messageProducer = session.createProducer(queue);
// messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); //非持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); //持久话
//通过messageProducer给mq发送消息
for(int i = 1;i<=3;i++){
//创建消息 里面放消息内容
TextMessage textMessage = session.createTextMessage("textMessage---" + i);
//设置唯一标识
textMessage.setStringProperty("c01","vip");
//通过messageProducer发送给mq
messageProducer.send(textMessage);
}
//关闭支援
messageProducer.close();
session.close();
connection.close();
System.out.println("****消息发送给mq****");
}
}
接受消息
package com.mq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* 消息消费者,通过监听获取消息
*/
public class JmsConsumerDemo02 {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String QUEUE_NAME = "query01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("1号消费者");
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Queue queue = session.createQueue(QUEUE_NAME);
//创建消费者
//从指定的队列中取消息
MessageConsumer messageConsumer = session.createConsumer(queue);
//通过监听获取消息,有消息则消费,没消息则不管
//异步非阻塞方式(监听器onMessage())
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());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
结论服务器宕机后队列依旧保存消息,消费者调用后依旧有数据
主题(Topic)消息非持久和持久
如果是非持久话,要先启动消费者在启动生产者,不然生产者生产的消息消费者接收不到
改造消息生产者
package com.mq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce_Topic {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String TOPIC_NAME = "topic-linailong";
public static void main(String[] args) throws JMSException {
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Topic topic = (Topic) session.createTopic(TOPIC_NAME);
//创建消失生产者
MessageProducer messageProducer = session.createProducer(topic);
//持久化生产者
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();//生产持久化topic后在开启
//通过messageProducer给mq发送消息
for(int i = 1;i<=3;i++){
//创建消息 里面放消息内容
TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
//通过messageProducer发送给mq
messageProducer.send(textMessage);
}
//关闭支援
messageProducer.close();
session.close();
connection.close();
System.out.println("****TOPIC_NAME消息发送给mq****");
}
}
改造消费者
启动后第一次接到生产者消息后如果一秒以内没在接收到消息则自动关闭
message = topicSubscriber.receive(1000L);(设置自动关闭时间)
package com.mq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumerDemo03_topic {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String TOPIC_NAME = "topic-linailong";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("3号消费者");
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
//设置id(表示谁订阅了消息)
connection.setClientID("3号消费者");
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Topic 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());
//设置自动关闭时间
message = topicSubscriber.receive(1000L);
}
session.close();
connection.close();
}
}
启动消费者
3号消费者
再启动持久化生产者
消费者接收到3条数据
启动两个消费者
3号消费者
2号消费者(不设置制动关闭时间)
运行消息生产者
这个时候3号消费者已经是离线状态,如果不做topic持久化再次启动消息生产者消费者三是接收不到消息的,但是按照上面的代码改造在运行一次消息生产者的结果是
在次启动的时候已经停止的3号消费者也接收到了消息
再次实验topic持久化
如果不做topic持久化启动顺序是先启动消费者再启动生产者,不然生产者发出的数据消费者接收不到(数据丢失),但是做了持久化则可以先启动生产者再启动消费者也可以保证数据不丢失(前提是必须提前注册)
topic持久化总结
1,一定要先运行一次消费者,等向MQ注册,类似我订阅了这个主题
2,然后在运行生产者发送消息此时
3,无论消费者是否在线,都会接收到,不在线的话,下次连接的时候,会把没有接收到的消息都接收下来
消息生产者事务
事物偏向消息生产者
设置true或者false代表是已事物提交还是非实物提交
//创建会话 参数 1事务 2签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
如果设置成true,事务和非事务对数据可靠性有影响
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
启动生产者
先执行send在执行commit消息再被提交到队列中,消息需要批量发,需要缓冲区
消息生产者
package com.mq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce_Topic {
public static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616/";
public static final String TOPIC_NAME = "topic-linailong";
public static void main(String[] args) throws JMSException {
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
//创建会话 参数 1事务 2签收
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//创建目的地 是队列还是主题topic
Topic topic = (Topic) session.createTopic(TOPIC_NAME);
//创建消失生产者
MessageProducer messageProducer = session.createProducer(topic);
//持久化生产者
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();//生产持久化topic后在开启
//通过messageProducer给mq发送消息
for(int i = 1;i<=3;i++){
//创建消息 里面放消息内容
TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
//通过messageProducer发送给mq
messageProducer.send(textMessage);
}
//关闭支援
messageProducer.close();
session.commit();
session.close();
connection.close();
System.out.println("****TOPIC_NAME消息发送给mq****");
//如果提交3条消息有一条错了则回滚
try{
//ok
}catch (Exception e){
e.printStackTrace();
//error
session.rollback();
}finally {
if(null != session){
session.close();
}
}
}
}
true和false的区别
如果是开启事务例如一下发生10条信息有一条出差者其余的全部回滚
try{
//ok
}catch (Exception e){
e.printStackTrace();
//error
//回滚
session.rollback();
}finally {
if(null != session){
session.close();
}
}
消息消费者事务介绍
消费者也要改成true同时commind提交,不然会出现重复消费