本文已参与【新人创作礼】活动,一起开启掘金创作之路。
目录
一、什么是Java消息服务
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。
编辑
二、JMS的组成结构和特点
JMS Provider
实现JMS接口和规范的消息中间件,也就是我们说的MQ服务器
JMS Producer
消息生产者,创建和发送JMS消息的客户端应用
JMS Consumer
消息消费者,接收和处理JMS消息的客户端应用
JSM Message
1.消息头
1)JMSDestination:消息发送的目的地,主要是指Queue和Topic
2)JMSDeliveryMode:持久模式和非持久模式。
一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。
一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
3)JMSExpiration:可以设置消息在一定时间后过期,默认是永不过期
消息过期时间,等于Destination的send方法中的timeToLive值加上发送时刻的GMT时间值。
如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期。
如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除。
4)JMSPriority:消息优先级
从0-9十个级别,0-4是普通消息5-9是加急消息。
JMS不要求MQ严格按照这十个优先级发送消息但必须保证加急消息要先于普通消息到达。默认是4级。
5)JMSMessageID:唯一标识每个消息的标识由MQ产生。
2.消息体:封装具体的消息数据
5种消息格式:
1)TxtMessage:普通字符串消息,包含一个String
2)MapMessage:一个Map类型的消息,key为Strng类型,而值为Java基本类型
3)BytesMessage:二进制数组消息,包含一个byte[]
4)StreamMessage:Java数据流消息,用标准流操作来顺序填充和读取
5)ObjectMessage:对象消息,包含一个可序列化的Java对象
发送和接收的消息体类型必须一致对应
3.消息属性
如果需要除消息字段以外的值,那么可以使用消息属性。
识别/去重/重点标注等操作非常有用的方法
编辑
代码说明
生产者代码
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String QUEUE_NAME = "queue";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.获得连接connection并启动
Connection connection = factory.createConnection();
connection.start();
//3.创建会话session
//两个参数:①事务 ②签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.通过使用messageProducer生产三天消息发送到MQ的队列
for(int i=0;i<3;i++){
//7.创建消息
//text类型
TextMessage textMessage = session.createTextMessage("mag---" + i);
//设置消息属性
textMessage.setStringProperty("z3","vip");
//map类型
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("k1","mapMessage_v1");
//object类型
ObjectMessage objectMessage = session.createObjectMessage();
objectMessage.setObject(new Student(1,"z3","java"));
//byte类型
BytesMessage bytesMessage = session.createBytesMessage();
bytesMessage.writeBytes("bytesMessage消息".getBytes());
//stream类型
StreamMessage streamMessage = session.createStreamMessage();
streamMessage.writeString("stream类型的消息");
streamMessage.writeString("你好");
streamMessage.writeDouble(3);
//8.通过messageProducer发送给mq
messageProducer.send(textMessage);
messageProducer.send(mapMessage);
messageProducer.send(objectMessage);
messageProducer.send(bytesMessage);
messageProducer.send(streamMessage);
}
//9.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("---消息发布到mq---");
}
}
@AllArgsConstructor
@Data
@NoArgsConstructor
//对象要可序列化
public class Student implements Serializable{
private int id;
private String name;
private String hobby;
}
消费者代码
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String QUEUE_NAME = "queue";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//从ActiveMQ5.12.2 开始,为了增强这个框架的安全性,ActiveMQ将强制用户配置可序列化的包名
//Student在序列化后,消费者在读取ObjectMessage会报错
factory.setTrustAllPackages(true);
//2.获得连接connection并启动
Connection connection = factory.createConnection();
connection.start();
//3.创建会话session
//两个参数:①事务 ②签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
/*
异步非阻塞式方式监听器(onMessage)
订阅者或消费者通过创建的消费者对象,给消费者注册消息监听器setMessageListener,
当消息有消息的时候,系统会自动调用MessageListener类的onMessage方法
我们只需要在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("接收到text消息:"+(textMessage.getText()));
System.out.println(" text消息的属性:"+textMessage.getStringProperty("z3"));
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != message && message instanceof MapMessage){
MapMessage mapMessage = (MapMessage) message;
try {
System.out.println("接收到map消息:"+mapMessage.getString("k1"));
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != message && message instanceof ObjectMessage){
ObjectMessage objectMessage = (ObjectMessage) message;
try {
Student student =(Student) objectMessage.getObject();
System.out.println("接收到object消息:"+student.toString());
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != message && message instanceof BytesMessage){
BytesMessage bytesMessage = (BytesMessage) message;
try {
byte[] bytes = new byte[1024];
int len = -1;
System.out.print("接收到byte消息: ");
while ((len = bytesMessage.readBytes(bytes))!=-1){
System.out.print(new String(bytes,0,len));
}
System.out.println();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(null != message && message instanceof StreamMessage){
StreamMessage streamMessage = (StreamMessage) message;
try {
System.out.println("接收到stream消息-string:"+streamMessage.readString());
System.out.println("接收到stream消息-string:"+streamMessage.readString());
System.out.println("接收到stream消息-int:"+streamMessage.readDouble());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
//保证控制台不灭 不加的话消息没来得及处理程序就结束了
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
三、JMS的可靠性
1.PERSISTENT:持久性
参数设置说明
1)非持久化:当服务器宕机,消息不存在。
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
2)持久化:当服务器宕机,消息依然存在。
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT)
持久的Queue
这是队列的默认传递模式,此模式保证这些消息只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素。
可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。
MessageProducer messageProducer = session.createProducer(queue);
//设置通过session创建出来的生产者生产的Queue消息为持久性
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
持久的Topic
持久的发布主题生产者 代码
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String TOPIC_NAME = "topic-atguigu";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer messageProducer = session.createProducer(topic);
//持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
for(int i=0;i<3;i++){
TextMessage textMessage = session.createTextMessage("TOPIC_NAME_PERSIST---" + i);
messageProducer.send(textMessage);
}
messageProducer.close();
session.close();
connection.close();
System.out.println("---TOPIC_NAME消息发布到mq---");
}
}
持久的定阅主题消费者代码
1.一定要先运行一次消费者,等于向MQ注册,类似我订阅了这个主题
2.然后运行生产者发送消息,此时无论消费者是否在线,都会接收到,下次连接的时候,会把没有收过的消息都接收下来
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String TOPIC_NAME = "topic-atguigu";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.获得连接connection并启动
Connection connection = factory.createConnection();
connection.setClientID("z3");
//3.创建会话session
//两个参数:①事务 ②签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
//5.通过session创建持久化订阅
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark...");
//6.启动连接
connection.start();
//7.接受消息
Message message = topicSubscriber.receive();
while (null != message) {
TextMessage textMessage = (TextMessage) message;
System.out.println("收到的持久化的topic: " + textMessage.getText());
message = topicSubscriber.receive(5000L);
}
session.close();
connection.close();
}
}
2.Transaction:事务
producer是否开启事务的代码规范
1)false
只要执行send,就进入到队列中
关闭事务,那第2个签收参数的设置需要有效
2)true
先执行send再执行commit,消息才被真正提交到队列中
消息需要需要批量提交,需要缓冲处理
事务偏生产者/签收偏消费者
生产者代码
public class JmsProduce {
//写法参照源码
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.获得连接connection并启动
Connection connection = factory.createConnection();
connection.start();
//3.创建会话session
//两个参数:①事务 ②签收
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.通过使用messageProducer生产三天消息发送到MQ的队列
for(int i=0;i<3;i++){
//7.创建消息
//text类型
TextMessage textMessage = session.createTextMessage("mag---" + i);
messageProducer.send(textMessage);
}
//9.关闭资源
messageProducer.close();
//不加commit消息发送不出去
session.commit();
session.close();
connection.close();
System.out.println("---消息发布到mq---");
}
}
消费者代码
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.获得连接connection并启动
Connection connection = factory.createConnection();
connection.start();
//3.创建会话session
//两个参数:①事务 ②签收
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while (true){
TextMessage testMessage =(TextMessage) messageConsumer.receive(4000L);
if(testMessage!=null){
System.out.println("**消费者接受到信息:"+testMessage.getText());
}else {
break;
}
}
messageConsumer.close();
//不加commit消息会被重复消费
session.commit();
session.close();
connection.close();
}
}
3.Acknowledge:签收
非事务(允许重复消息)
1)自动签收(默认)
Session.AUTO_ACKNOWLEDGE
2)手动签收
Session.CLIENT_ACKNOWLEDGE
客户端调用acknowledge方法手动签收
生产者代码不变
消费者手动签收代码
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.16.106:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2.获得连接connection并启动
Connection connection = factory.createConnection();
connection.start();
//3.创建会话session
//两个参数:①事务 ②签收
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);//手动
//4.创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while (true){
TextMessage testMessage =(TextMessage) messageConsumer.receive(3000L);
//签收
testMessage.acknowledge();
if(testMessage!=null){
System.out.println("**消费者接受到信息:"+testMessage.getText());
}else {
break;
}
}
messageConsumer.close();
session.close();
connection.close();
}
}
事务
生产事务开启,只有commit后才能将全部消息变为已消费
签收和事务的关系(事务>签收)
1.在事务性会话中,当一个事务被成功提交则消息被自动签收,如果事务回滚,则消息会被再次传送。
2.非事务性会话中,消息何时被确认取决于创建会话时的应答模式。
四、JMS点对点总结
点对点模型是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。和我们平时给朋友发送短信类似。
1:如果在Session关闭时有部分消息被收到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收
2:队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的链接状态,充分体现了异步传输模式的优势
五、JMS发布订阅总结
非持久订阅
非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收发到某个主题的消息。
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
持久订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ的时候,会根据消费者的ID得到所有当自己处于离线时发送到主题的消息
非持久订阅状态下,不能恢复或重新派送一个未签收的消息。
持久订阅才能恢复或重新派送一个未签收的消息。
使用
当所有的消息必须被接收,则用持久订阅。当消息丢失能够被容忍,则用非持久订阅