分布式消息通信ActiveMQ

JMS 规范中定义了两种消息传递域

点对点(point-to-point )消息传递域 和发布 / 订 阅 消息传递域(publish/subscribe)简单理解就是:有点类似于我们通过 qq 聊天的时候,在群里面发消息和给其中一个同学私聊消息。在群里发消息,所有群成员都能收到消息。私聊消息只能被私聊的学员能收到消息。

点对点消息传递域

  1. 每个消息只能有一个消费者
  2. 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,都可以提取消息。

发布订阅消息传递域

  1. 每个消息可以有多个消费者
  2. 生产者和消费者之间有时间上的相关性。订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS 规范允许客户创建持久订阅,这在一定程度上降低了时间上的相关性要求。持久订阅允许消费者消费它在未处于激活状态时发送的消息。

消息结构组成

JMS 消息由及部分组成:消息头、属性、消息体

  1. 消息头

    消息头(Header) - 消息头包含消息的识别信息和路由信息,消息头包含一些标准的属性如:JMSDestination 消息发送的目的地,queue 或者 topic)JMSDeliveryMode 传送模式。持久模式和非持久模式JMSPriority 消息优先级(优先级分为 10 个级别,从 0(最低)到 9(最高). 如果不设定优先级,默认级别是 4。需要注意的是,JMS provider 并不一定保证按照优先级的顺序提交消息)JMSMessageID 唯一识别每个消息的标识。

  2. 属性

按类型可以分为应用设置的属性,标准属性和消息中间件定义的属性

  1. 应用程序设置和添加的属性,比如
Message.setStringProperty(“key”,”value”);

通过下面的代码可以获得自定义属性的,在接收端的代码中编写在发送端,定义消息属性。

message.setStringProperty("Mic","Hello World");

在接收端接收数据

Enumeration enumeration=message.getPropertyNames();
while(enumeration.hasMoreElements()){
	 String name=enumeration.nextElement().toString();
	 System.out.println("name:"+name+":"+message.getStringProperty(name));
	 System.out.println();
}
  1. JMS 定义的属性
    使用“JMSX”作为属性名的前缀,通过下面这段代码可以返回所有连接支持的 JMSX 属性的名字。
       Enumeration names = connection.getMetaData().getJMSXPropertyNames();
       while (names.hasMoreElements()){
           String name = (String)names.nextElement();
           System.out.println(name);
       }
  1. JMS provider 特定的属性
    消息体就是我们需要传递的消息内容,JMS API 定义了 5 种消息体格式,可以使用不同形式发送接收数据,并可以兼容现有的消息格式,其中包括
类型格式
TextMessagejava.lang.String 对象,如 xml 文件内容
MapMessage名/值对的集合,名是 String 对象,可以是 Java 任何基本类型
BytesMessage字节流
StreamMessageJava 中的输入输出流
ObjectMessageJava 中的可序列化对象
Message没有消息体,只有消息头和属性。

绝大部分的时候,我们只需要基于消息体进行构造

持久订阅

持久订阅的概念,也很容易理解,比如还是以 QQ 为例,我们把 QQ 退出了,但是下次登录的时候,仍然能收到离线的消息。持久订阅就是这样一个道理,持久订阅有两个特点:

  1. 持久订阅者和非持久订阅者针对的 Domain 是 Pub/Sub,而不是 PTP
  2. 当 Broker 发送消息给订阅者时,如果订阅者处于未激活状态状态:持久订阅者可以收到消息,而非持久订阅者则收不到消息。当然这种方式也有一定的影响:当持久订阅者处于未激活状态时,Broker 需要为持久订阅者保存消息;如果持久订阅者订阅的消息太多则会溢出。

服务端

package com.adviser.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.util.Enumeration;

public class JMSTopicProducer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory  = new ActiveMQConnectionFactory
                ("tcp://192.168.0.128:61616");
        Connection connection = null;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            Session session = connection.createSession
                    (Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);
            //创建队列
            Destination destination = session.createTopic("myTopic");
            //创建发送者
            MessageProducer producer = session.createProducer(destination);
            //持久化
            producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
            //创建需要发送的消息
            TextMessage message = session.createTextMessage("周四晚上上线");
            producer.send(message);
            session.commit();
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if (connection!=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

消费端

package com.adviser.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class JMSPersistentTopicConsumer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory  = new ActiveMQConnectionFactory
                ("tcp://192.168.0.128:61616");
        Connection connection = null;
        try {
            connection = connectionFactory.createConnection();
            connection.setClientID("lizhe-001");
            connection.start();
            Session session = connection.createSession
                    (Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);
            //创建队列
            Topic destination = session.createTopic("myTopic");
            //创建发送者
            MessageConsumer consumer = session.createDurableSubscriber(destination,"lizhe-001");
            //接收消息
            TextMessage textMessage = (TextMessage) consumer.receive();
            System.out.println(textMessage.getText());
            session.commit();
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if (connection!=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

先启动消费端去注册一个持久订阅。持久订阅时,客户端向 JMS 服务器注册一个自己身份的 ID,当这个客户端处于离线时,JMS Provider 会为这个 ID 保存所有发送到主题的消息,当客户再次连接到 JMS Provider 时,会根据自己的 ID 得到所有当自己处于离线时发送到主题的消息。这个身份ID,在代码中的体现就是 connection的 ClientID,这个其实很好理解,你要想收到朋友发送的 qq 消息,前提就是你得先注册个 QQ 号,而且还要有台能上网的设备,电脑或手机。设备就相当于是 clientId 是唯一的;qq 号相当于是订阅者的名称,在同一台设备上,不能用同一个 qq号挂 2 个客户端。连接的 clientId 必须是唯一的,订阅者的名称在同一个连接内必须唯一。这样才能唯一的确定连接和订阅者。

activeMQ 控制台的截图

设置持久订阅以后,在控制台能看到下图的变化
在这里插入图片描述

JMS 消息的可靠性机制

  • 理论上来说,我们需要保证消息中间件上的消息,只有被消费者确认过以后才会被签收,相当于我们寄一个快递出去,收件人没有收到快递,就认为这个包裹还是属于待签收状态,这样才能保证包裹能够安全达到收件人手里。消息中间件也是一样。
  • 消息的消费通常包含 3 个阶段:客户接收消息、客户处理消息、消息被确认首先,来简单了解 JMS 的事务性会话和非事务性会话的概念JMS
    Session 接口提供了 commit 和 rollback 方法。
  • 事务提交意味着生产的所有消息被发送,消费的所有消息被确认;事务回滚意味着生产的所有消息被销毁,消费的所有消息被恢复并重新提交,除非它们已经过期。事务性的会话总是牵涉到事务处理中,commit 或 rollback方法一旦被调用,一个事务就结束了,而另一个事务被开始。关闭事务性会话将回滚其中的事务。

在事务型会话中
必须保证发送端和接收端都是事务性会话

  • 在事务状态下进行发送操作,消息并未真正投递到中间件,而只有进行 session.commit操作之后,消息才会发送到中间件,再转发到适当的消费者进行处理。
  • 如果是调用rollback 操作,则表明,当前事务期间内所发送的消息都取消掉。通过在创建 session 的时候使用 true or false 来决定当前的会话是事务性还是非事务性。
Session session = connection.createSession(Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);

在事务性会话中,消息的确认是自动进行,通过session.commit()以后,消息会自动确认。

在非事务型会话中
消息何时被确认取决于创建会话时的应答模式(acknowledgement mode). 有三个可选项。
在这里插入图片描述

1.Session.AUTO_ACKNOWLEDGE

当客户成功从 receive 方法返回或者从MessageListenner.onMessage 方法成功返回的时候,会话自动确认客户收到消息。

2.Session.CLIENT_ACKNOWLEDGE

客户通过调用消息的 acknowledge 方法确认消息。

textMessage.acknowledge();

在这种模式中,确认是在会话层上进行,确认一个被消费的消息将自动确认所有已被会话消费的消息。列如,如果一个消息消费者消费了 10 个消息,然后确认了第 5 个消息,那么 0~5 的消息都会被确认。
演示如下:发送端发送 10 个消息,接收端接收 10 个消息,但是在 i==5 的时候,调用 message.acknowledge()进行确认,会发现 0~4 的消息都会被确认
发送端

package com.adviser.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQMessageProducer;

import javax.jms.*;

public class JMSQueueProducer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory  = new ActiveMQConnectionFactory
                ("tcp://192.168.0.128:61616");
        Connection connection = null;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
//            Session session = connection.createSession
//                    (Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);
            Session session = connection.createSession
                    (Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
            //创建队列
            Destination destination = session.createQueue("myQueue");
            //创建发送者
            MessageProducer producer = session.createProducer(destination);
            //创建需要发送的消息
            for (int i = 0; i < 10;i++) {
                TextMessage message = session.createTextMessage("Hello 女友"+i);
                producer.send(message);
            }
            //如果Boolean为true时需要commit
            //session.commit();
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if (connection!=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

接收端

package com.adviser.activemq;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class JMSQueueConsumer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory  = new ActiveMQConnectionFactory
                ("tcp://192.168.0.128:61616");
        Connection connection = null;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            //需要commit提交
//            Session session = connection.createSession
//                    (Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);
            Session session = connection.createSession
                    (Boolean.FALSE,Session.CLIENT_ACKNOWLEDGE);
            //创建队列
            Destination destination = session.createQueue("myQueue");
            //创建发送者
            MessageConsumer consumer = session.createConsumer(destination);
            //接收消息
            for (int i = 0; i < 10; i++) {
                TextMessage textMessage = (TextMessage) consumer.receive();
                System.out.println(textMessage.getText());
                if (i==5){
                    //消息签收
                    textMessage.acknowledge();
                }
            }
            //session.commit();表示消息被自动确认 Boolean.FALSE
            session.close();
        } catch (JMSException e) {
            e.printStackTrace();
        }finally {
            if (connection!=null){
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

3.Session.DUPS_ACKNOWLEDGE

消息延迟确认。指定消息提供者在消息接收者没有确认发送时重新发送消息,这种模式不在乎接受者收到重复的消息。

消息的持久化存储

消息的持久化存储也是保证可靠性最重要的机制之一,也就是消息发送到 Broker 上以后,如果 broker 出现故障宕机了,那么存储在 broker 上的消息不应该丢失。可以通过下面的代码来设置消息发送端的持久化和非持久化特性

//创建发送者
MessageProducer producer = session.createProducer(destination);
//消息的持久化存储
producer.setDeliveryMode(DeliveryMode.PERSISTENT);

➢ 对于非持久的消息,JMS provider 不会将它存到文件/数据库等稳定的存储介质中。也就是说非持久消息驻留在内存中,如果 jms provider 宕机,那么内存中的非持久消息会丢失
➢ 对于持久消息,消息提供者会使用存储-转发机制,先将消息存储到稳定介质中,等消息发送成功后再删除。如果 jms provider 挂掉了,那么这些未送达的消息不会丢失;jms provider 恢复正常后,会重新读取这些消息,并传送给对应的消费者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值