十七、消息中间件ActiveMQ

学习地址:https://www.bilibili.com/video/BV164411G7aB?vd_source=461545ff50a35eaeaa8218ecdc5f7152
时长:11小时25分钟

ActiveMQ官方文档:https://activemq.apache.org/

1.前言说明

MQ=消息中间件/消息队列

消息:微信、短信、语音…
中间件:类似nacos、Zookeeper…

主要作用:解耦、削锋、异步

2.两种讲授闲聊

3.MQ的产品学习说明

MQ的产品种类
ActiveMQ
Kafka:https://www.bilibili.com/video/BV1vr4y1677k?spm_id_from=333.999.0.0
RabbitMQ:https://www.bilibili.com/video/BV1vr4y1677k?spm_id_from=333.999.0.0
RocketMQ:https://www.bilibili.com/video/BV1cf4y157sz?vd_source=461545ff50a35eaeaa8218ecdc5f7152

ActiveMQ ( MQ 都需要满足的技术 )

  • API 接受发送
  • MQ 的高可用
  • MQ 的集群和容错配置
  • MQ 的持久化
  • 延时发送/定时投递
  • 签收机制
  • Spring/SpringBoot 整合

4.为什么要引入MQ-上

引入MQ前:学生问问题,非常多的学生,除了第一个再问,其余人都要一直等待
引入MQ后:老师给班长说,让学生把问题按照一定的格式写在纸上,按照先后顺序给班长,不用一直排队耗着

为什么要使用 MQ ?
解决了耦合调用、异步模型、抵御洪峰流量,保护了主业务,消峰。

5.为什么要引入MQ-下

系统之间直接调用实际工程落地和存在的问题
系统之间耦合比较严重
面对大流量并发时,容易被冲垮
等待同步存在性能问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.MQ的作用定义

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
发送者和接收者不必了解对方只需要确认消息
发送者和接收者不必同时在线

7.ActiveMQ官网介绍和下载

ActiveMQ 的官网 : http://activemq.apache.org
在这里插入图片描述只能下载Linux,不能再Windows上集成服务器
在这里插入图片描述

怎么玩
最主要的功能:实现高可用、高性能、可伸缩、易用和安全的企业级面向消息服务的系统
异步消息的消费和处理
控制消息的消费顺序
可以和Spring/SpringBoot整合简化编码
配置集群容错的MQ集群

8.ActiveMQ在Linux下安装

  1. 官网下载
  2. /opt目录下
  3. 解压缩命令:tar -zxvf 文件名
  4. 在根目录下:mkdir /myactiveMQ
  5. cp -r 文件名 /myactiveMQ
  6. cd /myactiveMQ/
  7. 直接进入myactiveMQ的 文件下的activemq 下的 bin 目录,使用 ./activemq start 命令启动
  8. activemq的默认进程端口是61616
  9. 第一种方法:检查进程ps -ef|grep activemq|grep -v grep (grep -v grep 可以不让显示grep 本来的信息)
  10. 第二种方法:查看端口是否被占用netstat -anp|grep 61616
  11. 第三种方法:lsof -i:61616
  12. 重启:bin目录下./activemq restart
  13. 关闭:bin目录下./activemq stop
  14. 带日志的启动方式./activemq start > /myactiveMQ/myrunmq.log

9.ActiveMQ安装后的控制台访问

http://IP地址:8161
默认的用户名和密码是admin/admin

先用ping目录检查一下

  1. 先关闭Linux防火墙或者配置白名单
  2. Windows防火墙也关闭
  3. Liunx:ping IP地址
  4. Windows:ping IP地址
    在这里插入图片描述

通过61616端口提供JMS服务
采用8161端口提供管理控制台服务

访问不到的坑:
1 可能是你的linux 和 windows 没有在一个网关下
2 可能你windows 的防火墙或者 linux 的防火墙没有关掉(是的,先得关掉防火墙)
3 你忘记启动activemq 的服务了
4 你启动失败了,可能是你得java 环境没配好,必须是jdk 8 或者以上

10.Java编码MQ标准API讲解

  1. IDEA建Maven工程
  2. POM.xml
  3. JMS编码总体架构
    JMS : Java 消息中间件的服务接口规范,activemq 之上是 mq , 而 mq 之上是JMS 定义的消息规范 。 activemq 是mq 技术的一种理论实现(与之相类似的实现还有 Kafka RabbitMQ RockitMQ ),而 JMS 是更上一级的规范。
    在这里插入图片描述
  4. 粗说目的地Destination队列和主题
  5. 在点对点的消息传递域中,目的地被称为队列(queue)
  6. 在发布订阅消息的传递域中,目的地被称为主题(topic)
    在这里插入图片描述

11.消息生产者编码

public class JmsProduce {
    //linux 上部署的activemq 的 IP 地址 + activemq 的端口号
    public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
    //队列的名字
    public static final String QUEUE_NAME = "queue01";
    
    public static void main(String[] args) throws  Exception{
        
        //1.创建连接工厂,按照给定的url创建连接工程,这个构造器采用默认的用户名密码admin/admin
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂获得连接connection并启动
        javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
        //启动
        connection.start();
        //3.创建回话session
        //两个参数,第一个事务,第二个签收(自动)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(两种:队列/主题,这里用队列)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);
        //6.通过messageProducer生产3条消息发送到消息队列中
        for (int i = 1; i < 4 ; i++) {
            //7.创建消息
            //理解为一个字符串,好比学生按照固定的格式要求写好的问题
            TextMessage textMessage = session.createTextMessage("msg--" + i);
            //8.通过messageProducer发布消息
            messageProducer.send(textMessage);
        }
        //9.关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("  **** 消息发送到MQ完成 ****");
    }
}

成功情况
在这里插入图片描述

12.消息消费者编码

public class JmsConsumer {
    public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
    public static final String QUEUE_NAME = "queue01";   // 1对1 的队列

    public static void main(String[] args) throws Exception{
        //1.按照给定的url创建连接工程,这个构造器采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂连接connection和启动
        javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
        //启动
        connection.start();
        //3.创建回话session
        //两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地 (两种 : 队列/主题   这里用队列)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        while(true){
            //这里是 TextMessage是因为消息发送者是 TextMessage,接受处理的也应该是这个类型的消息
            TextMessage message = (TextMessage)messageConsumer.receive();
            if (null != message){
                System.out.println("****消费者的消息:"+message.getText());
            }else {
                break;
            }
        }
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

在这里插入图片描述
在这里插入图片描述
待处理的消息:0
消费者数量:1个
消息的入队:3
消息的出队:3

13.消息消费者receive方法说明

//这里是 TextMessage是因为消息发送者是 TextMessage,接受处理的也应该是这个类型的消息
TextMessage message = (TextMessage)messageConsumer.receive();
//receive()不带时间,死等,灯不灭
//receive(4000L)带时间,过时不候,消费者数量变为0

14.消息消费者MessageListener方法说明

 	// 通过监听的方式来消费消息
  // 通过异步非阻塞的方式消费消息
  // 通过messageConsumer 的setMessageListener 注册一个监听器,
  // 当有消息发送来时,系统自动调用MessageListener 的 onMessage 方法处理消息
  messageConsumer.setMessageListener(new MessageListener() {   // 可以用监听器替换之前的同步receive 方法
      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();
                  }
          }
      }
  });

15.消费者三大消费情况

  1. 先生产,只启动1号消费者,问题:1号消费者能消费消息吗?
    能消费
  2. 先生产,先启动1号消费者,再启动二号消费者,问题:2号消费者还能消费消息吗?
    1号能消费,2号不能消费
  3. 先启动2个消费者,再生产6条消息,消费情况如何?
    平均轮询分配,一人一半。这里的一点经验: activemq 好像自带负载均衡,当先启动两个队列(Queue)的消费者时,在启动生产者发出消息,此时的消息平均的被两个消费者消费。 并且消费者不会消费已经被消费的消息(即为已经出队的消息)

16.队列案例小总结

17.Java编码Topic讲解

  1. 生产者将消息发布到topic中,每个消息可以有多个消费者,属于1:N的关系
  2. 生产者和消费者之间有时间上的相关性。订阅某一个主题的消费者只能消费自它订阅之后发布的消息
  3. 生产者生产时,topic不保存消息它是无状态的不落地,加入无人订阅就去生产,那就是一条废消息,所以,一般先启动消费者再启动生产者。
  4. 但是当有多个主题(Topic)订阅者时,发布者发布的消息,每个订阅者都会接收所有的消息。topic 更像是被广播的消息,但是缺点是不能接受已经发送过的消息。先要有订阅者,生产者才有意义。

Topic生产者

public class JmsProduce {
    //linux 上部署的activemq 的 IP 地址 + activemq 的端口号
    public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
    //队列的名字
    public static final String TOPIC_NAME = "topic";
    
    public static void main(String[] args) throws  Exception{
        
        //1.创建连接工厂,按照给定的url创建连接工程,这个构造器采用默认的用户名密码admin/admin
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂获得连接connection并启动
        javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
        //启动
        connection.start();
        //3.创建回话session
        //两个参数,第一个事务,第二个签收(自动)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(两种:队列/主题,这里用队列)
        Topic topic = session.createTopic(TOPIC_NAME);
        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(topic);
        //6.通过messageProducer生产3条消息发送到消息队列中
        for (int i = 1; i < 4 ; i++) {
            //7.创建消息
            //理解为一个字符串,好比学生按照固定的格式要求写好的问题
            TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
            //8.通过messageProducer发布消息
            messageProducer.send(textMessage);
        }
        //9.关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("  **** TOPIC_NAME消息发送到MQ完成 ****");
    }
}

18.topic和queue的对比总结

比较项目Topic模式队列Queue模式队列
工作模式“订阅-发布”模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息“负载均衡” 模式,如果当前没有消费者,消息也不会丢弃;如果有多个消费者,那么一条消息也只会发送给其中的一个消费者,并且要求消费者ack信息
有无状态无状态Queue数据默认会在mq服务器上以文件的形式保存,比如ActiveMQ一般保存在$AMQ_HOME\data\kr-store\data下面,也可以配置成DB存储
传递完整性如果没有订阅者,消息会被丢弃消息不会被丢弃
处理效率由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异由于一条消息只能发送给一个消费者,所以就算消费者再多,性能也不会有明显降低,当然不同消息协议的具体性能也是有差异的

19.JMS是什么

JAVAEE 是一套使用Java 进行企业级开发的13 个核心规范工业标准
JDBC 数据库连接
JNDI Java的命名和目录接口
EJB Enterprise java bean
RMI 远程方法调用 一般使用TCP/IP 协议
Java IDL 接口定义语言
JSP
Servlet
XML
JMS Java 消息服务
JTA
JTS
JavaMail
JAF

JMS的可靠性:持久性、事务、签收

20.MQ中间件的落地产品

在这里插入图片描述

21.JMS组成的四大元素

JMS部件JMS providerJMS producerJMS consumerJMS message
含义实现JMS 的消息中间件,也就是MQ服务器消息生产者,创建和发送消息的客户端消息消费者,接收和处理消息的客户端JMS 消息,分为消息头、消息属性、消息体

JMS message组成:消息头、消息属性、消息体

22.Message之消息头

消息头JMSDestinationJMSDeliveryModeJMSExpirationJMSPriorityJMSMessageId
含义头在哪儿是持久还是非持久过期时间,默认永不过期优先级,默认是4有0~9 ,5-9 是紧急的,0-4 是普通的唯一的消息ID
 		//5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(topic);
        //6.通过messageProducer生产3条消息发送到消息队列中
        for (int i = 1; i < 4 ; i++) {
            //7.创建消息
            //理解为一个字符串,好比学生按照固定的格式要求写好的问题
            TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
            //textMessage.set各种消息头
            //8.通过messageProducer发布消息
            messageProducer.send(textMessage);
            //send也可以设置各种消息头
        }

23.Message之消息体

发送和接收的消息类型必须一致

5种消息体TextMessageMapmessageBytesMessageStreamMessageObjectMessage
含义普通字符串消息,包含一个StringMap 类型的消息,k-> String v -> Java 基本类型二进制数组消息,包含一个byte[]Java 数据流消息,用标准流操作来顺序的填充读取对象消息,包含一个可序列化的Java 对象

24.Message之消息属性

消息属性:识别、去重、重点标注

他们是以属性和属性值对的形式制定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息,比如可以再属性里指定消息选择器。

如果需要除消息头字段以外的值,那么可以使用消息属性
识别/去重/重点标注等操作非常有用的方法

25.消息可靠性之非持久化

消息的可靠性:持久性、事务、签收

// 在队列为目的地的时候持久化消息
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

// 队列为目的地的非持久化消息
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

26.消息可靠性之持久化和默认策略

持久化的消息,服务器宕机后消息依旧存在,只是没有入队,当服务器再次启动,消息任就会被消费。
非持久化的消息,服务器宕机后消息永远丢失。
而当你没有注明是否是持久化还是非持久化时,默认是持久化的消息。

27.持久化topic

对于目的地为主题(topic)来说,默认就是非持久化的,让主题的订阅支持化的意义在于:对于订阅了公众号的人来说,当用户手机关机,在开机后任就可以接受到关注公众号之前发送的消息。

持久化topic 的消费者

		// 前面代码相同,不复制了      
        Topic topic = session.createTopic(TOPIC_NAME);
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark...");

         // 5 发布订阅
        connection.start();

        Message message = topicSubscriber.receive();// 一直等
         while (null != message){
             TextMessage textMessage = (TextMessage)message;
             System.out.println(" 收到的持久化 topic :"+textMessage.getText());
             message = topicSubscriber.receive(3000L);    // 等1秒后meesage 为空,跳出循环,控制台关闭
         }

持久化生产者

        MessageProducer messageProducer = session.createProducer(topic);
        // 6 通过messageProducer 生产 3 条 消息发送到消息队列中

        // 设置持久化topic 在启动
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); 
        connection.start();
        for (int i = 1; i < 4 ; i++) {
            // 7  创建字消息
            TextMessage textMessage = session.createTextMessage("topic_name--" + i);
            // 8  通过messageProducer发布消息
            messageProducer.send(textMessage);

            MapMessage mapMessage = session.createMapMessage();
            //    mapMessage.setString("k1","v1");
            //     messageProducer.send(mapMessage);
        }
        // 9 关闭资源

在这里插入图片描述
当生产者启动后:
在这里插入图片描述
消息被消费,并且: (因为我在receive方法中设置了如果接收到消息后3秒还没有消息就离线,也也可以设置永久存活)
在这里插入图片描述

28.消息的生产者事务介绍

createSession的第一个参数为true 为开启事务,开启事务之后必须在将消息提交,才可以在队列中看到消息

Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

提交:

session.commit(); 

事务开启的意义在于,如果对于多条必须同批次传输的消息,可以使用事务,如果一条传输失败,可以将事务回滚,再次传输,保证数据的完整性。
对于消息消费者来说,开启事务的话,可以避免消息被多次消费,以及后台和服务器数据的不一致性。

举个栗子:
如果消息消费的 createSession 设置为 ture ,但是没有 commit ,此时就会造成非常严重的后果,那就是在后台看来消息已经被消费,但是对于服务器来说并没有接收到消息被消费,此时就有可能被多次消费。

29.消息的消费者事务介绍

30.消息非事务模式下消费者签收介绍

Acknowledge 签收 (俗称ack)
非事务 :

Session.AUTO_ACKNOWLEDGE      //自动签收,默认

Session.CLIENT_ACKNOWLEDGE     //手动签收
//手动签收需要acknowledge   
textMessage.acknowledge();

31.消息有事务模式下消费者签收介绍

对于开启事务时,设置手动签收和自动签收没有多大的意义,都默认自动签收,也就是说事务的优先级更高一些,事务>签收。

Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
//Session session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE);   //  也是自动签收   

        ……

session.commit();  

但是开启事务没有commit就会重复消费

32.点对点和发布订阅小总结

点对点是模型是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使消息的异步传输成为可能。和我们平时给朋友发送短信类似。
1.如果在session关闭时有部分罅隙已被接收到但还没有被签收,那么当消费者下次连接到相同的队列时,这些消息还会被再次接收
2.队列可以长久的保存消息直到消费者接收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势

发布订阅
1.主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息。
2.主题使得消息订阅者和消息发布者保持相互独立,不需要接触即可保证消息的传送。

非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态下才能收到发送到某个主题的消息
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
一句话:先要订阅注册才能收到发布,只给订阅者发布消息

33.按照不同的conf配置文件来启动activemq

broker 就是实现了用代码形式启动 ActiveMQ 将 MQ 内嵌到 Java 代码中,可以随时启动,节省资源,提高了可靠性。
就是将 MQ 服务器作为了 Java 对象

使用多个配置文件启动 activemq

cp activemq.xml  activemq02.xml 

// 以active02 启动mq 服务器
./activemq start xbean:file:/myactivemq/apache-activemq-5.15.9/conf/activemq02.xml 

34.ActiveMQ的Broker

把小型 activemq 服务器嵌入到 java 代码: 不在使用linux 的服务器

导入依赖

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.5</version>
</dependency>

代码实现

public class Embebroker {
    public static void main(String[] args) throws Exception {
        // broker 服务
        BrokerService brokerService = new BrokerService();
        // 把小型 activemq 服务器嵌入到 java 代码
        brokerService.setUseJmx(true);
        // 原本的是 192.……  是linux 上的服务器,而这里是本地windows 的小型mq服务器
        brokerService.addConnector("tcp://localhost:61616");
        brokerService.start();
    }
}

35.Spring整合ActiveMQ之队列生产者

所需jar 包

<!--  activeMQ  jms 的支持  -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jms</artifactId>
    <version>4.3.23.RELEASE</version>
</dependency>
<dependency>    <!--  pool 池化包相关的支持  -->
  <groupId>org.apache.activemq</groupId>
  <artifactId>activemq-pool</artifactId>
  <version>5.15.9</version>
</dependency>

<!--  aop 相关的支持  -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>4.3.23.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.23.RELEASE</version>
</dependency>

写xml 配置文件(applicationContext.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://camel.apache.org/schema/spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd
     http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
	<!-- 开启包的自动扫描 -->
    <context:commponent-scan base-package="com.at.activemq"/>
    
    <!-- 配置生产者 -->
    <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory"  destroy-method="stop">
        <property name="connectionFactory">
        	<!-- 真正可以产生Connection的ConnectionFactory,由对应的JMS服务厂商提供 -->
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
            	<!--value换成自己的IP-->
                <property name="brokerURL" value="tcp://192.168.17.3:61616"></property>
            </bean>
        </property>
        <property name="maxConnections" value="100"></property>
    </bean>

    <!-- 队列目的地,点对点 -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queue"></constructor-arg>
    </bean>


    <!--  jms 的工具类,进行消息的发送、接收等 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"/>
        <property name="defaultDestination" ref="destinationQueue"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
        </property>
    </bean>
</beans>

编写代码:

//生产者
@Service
public class SpringMQ_producer {
	//导入模板
    @Autowired
    private JmsTemplate jmsTemplate;
    public static void main(String[] args) {
    	//读配置文件
        ApplicationContext  ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

		//类似SpringMQ_producer produce = new SpringMQ_producer();
        SpringMQ_producer producer = (SpringMQ_producer) ctx.getBean("springMQ_Producer");
        
        producer.jmsTemplate.send((session) -> {
            TextMessage textMessage = session.createTextMessage("spring 和 activemq 的整合");
            return textMessage;
        });
        System.out.println(" *** send task over ***");
    }
}

36.Spring整合ActiveMQ之队列消费者

//消费者
@Service
public class Spring_MQConsummer {
    @Autowired
    private JmsTemplate jmsTemplate;
    public static void main(String[] args) {
        ApplicationContext  ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Spring_MQConsummer  sm = (Spring_MQConsummer)ac.getBean("spring_MQConsummer");

        String s = (String) sm.jmsTemplate.receiveAndConvert();
        System.out.println(" *** 消费者消息"+s);
    }
}

37.Spring整合ActiveMQ之主题生产消费者

只需要修改配置文件(applicationContext.xml)

修改部分

	<!-- 主题目的地-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-active-topic"></constructor-arg>
    </bean>

    <!--  jms 的工具类,进行消息的发送、接收等 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"/>
        <property name="defaultDestination" ref="destinationTopic"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
        </property>
    </bean>

修改完毕

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://camel.apache.org/schema/spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd
     http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
	<!-- 开启包的自动扫描 -->
    <context:commponent-scan base-package="com.at.activemq"/>
    
    <!-- 配置生产者 -->
    <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory"  destroy-method="stop">
        <property name="connectionFactory">
        	<!-- 真正可以产生Connection的ConnectionFactory,由对应的JMS服务厂商提供 -->
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
            	<!--value换成自己的IP-->
                <property name="brokerURL" value="tcp://192.168.17.3:61616"></property>
            </bean>
        </property>
        <property name="maxConnections" value="100"></property>
    </bean>

    <!-- 队列目的地,点对点 -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queue"></constructor-arg>
    </bean>

	<!-- 主题目的地-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-active-topic"></constructor-arg>
    </bean>

    <!--  jms 的工具类,进行消息的发送、接收等 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"/>
        <property name="defaultDestination" ref="destinationTopic"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
        </property>
    </bean>
</beans>

38.Spring整合ActiveMQ之监听器配置

1.添加配置文件(applicationContext.xml)配置监听程序
在这里插入图片描述
2.添加MyMessageListener类实现消息监听
在这里插入图片描述
3.只需要启动生产者,消费者不用启动,自动会监听记录

39.SpringBoot整合ActiveMQ之队列生产者

  1. 新建maven工程并设置包名类名
  2. Pom.xml
  3. application.yml
  4. 配置Bean:类似Spring框架的applicationContext.xml文件
  5. Queue_Produce
  6. 主启动类MainApp_Produce
  7. 测试单元

40.SpringBoot整合ActiveMQ之队列生产者间隔定投

1.建立boot 项目,配置 pom.xml 配置 application.yml 配置 bean
2.编写生产者 编写启动类 测试类

按键触发消息和定时发送消息的业务代码:

// 调用一次一个信息发出
public void produceMessage(){
    jmsMessagingTemplate.convertAndSend(queue,"****"+ UUID.randomUUID().toString().substring(0,6));
}

// 带定时投递的业务方法
@Scheduled(fixedDelay = 3000)    // 每3秒自动调用
public void produceMessageScheduled(){
    jmsMessagingTemplate.convertAndSend(queue,"** scheduled **"+ UUID.randomUUID().toString().substring(0,6));
    System.out.println("  produceMessage  send   ok   ");
}

41.SpringBoot整合ActiveMQ之队列消费者

对于消息消费者,在以前使用单独的监听器类,编写监听器代码,但是在spring boot 中,使用注解 JmsListener 即可:

@Component
public class Queue_consummer {

    @JmsListener(destination = "${myqueue}")     // 注解监听  
    public void receive(TextMessage textMessage) throws  Exception{
        System.out.println(" ***  消费者收到消息  ***"+textMessage.getText());
    }
}

这些是之前(队列)消息发送者发送的消息
在这里插入图片描述

编写消费者项目
编写主题的消息生产者和消费者项目,运行demo

42.SpringBoot整合ActiveMQ之主题生产者

在这里插入图片描述

43.SpringBoot整合ActiveMQ之主题消费者

在这里插入图片描述

44.ActiveMQ的传输协议简介

ActiveMQ 支持的协议有 TCP 、 UDP、NIO、SSL、HTTP(S) 、VM
这是activemq 的activemq.xml 中配置文件设置协议的地方

<transportConnectors>
		 <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumCon    nections=1000&amp;wireFormat.maxFrameSize=104857600"/>
		 <!-- 唯独在进行openwire协议描述时,URL头却采用tcp://,这是因为ActiveMQ中默认的消息协议就是openwire -->
         <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnect    ions=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConn    ections=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnect    ions=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnection    s=1000&amp;wireFormat.maxFrameSize=104857600"/>
</transportConnectors>

45.ActiveMQ的传输协议种类

默认是使用 openwire 也就是 tcp 连接
默认的Broker 配置,TCP 的Client 监听端口 61616 ,在网络上传输数据,必须序列化数据,消息是通过一个 write protocol 来序列化为字节流。默认情况 ActiveMQ 会把 wire protocol 叫做 Open Wire ,它的目的是促使网络上的效率和数据快速交互。

使用Tcp 的一些优化方案:tcp://hostname:port?key=value
它的参数详情参考:http://activemq.apache.org/tcp-transport-reference
在这里插入图片描述
NIO协议和TCP协议类似但NIO更侧重于底层的访问操作。它允许开发人员对同一资源有更多的client调用和服务端有更多的负载,NIO 协议为ActiveMQ 提供更好的性能
适合NIO 使用的场景:
1.当有大量的Client 连接到Broker 上 , 使用NIO 比使用 tcp 需要更少的线程数量,所以使用 NIO
2.可能对于 Broker 有一个很迟钝的网络传输, NIO 的性能高于 TCP

NIO连接形式:nio://hostname:port?key=value
各种协议对比 : http://activemq.apache.org/configuring-version-5-transports.html
在这里插入图片描述

46.ActiveMQ的传输协议之NIO

修改配置文件activemq.xml

如果不特别指定ActiveMQ的网络监听端口,那么这些端口都将使用BIO网络IO模型
所以为了首先提高单节点的网络吞吐性能,我们需要明确指定Active的网络IO模型

<transportConnectors>
		 <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumCon    nections=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnect    ions=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConn    ections=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnect    ions=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnection    s=1000&amp;wireFormat.maxFrameSize=104857600"/>
         <!-- 这是添加的 -->
         <!-- 注意端口号61618 -->
         <!-- URL格式以“nio”开头,表示这个端口使用以TCP协议为基础的NIO网络IO模型 -->
 		 <transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>
</transportConnectors>

访问ActiveMQ客户端
在这里插入图片描述
修改消息生产者和消费者的 URL即可

//public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61618";

47.ActiveMQ的传输协议之NIO加强

问题:URI 格式以 nio 开头,表示这个端口使用 tcp 协议为基础的NIO 网络 IO 模型,但这样设置方式,只能使这个端口支持Openwire协议。那么我们怎么既让这个端口支持NIO网络IO模型,又让他支持多个协议呢?
在这里插入图片描述
Starting with version 5.13.0, ActiveMQ supports wire format protocol detection. OpenWire, STOMP, AMQP, and MQTT can be automatically detected. This allows one transport to be shared for all 4 types of clients.
使用 : auto+nio+ssl
官网介绍 :http://activemq.apache.org/auto
使用 auto 的方式就相当于四合一协议 : STOMP AMQP MQTT TCP NIO

<transportConnector name="auto+nio" uri="auto+nio://localhost:5671"/>

auto 就像是一个网络协议的适配器,可以自动检测协议的类型,并作出匹配

<transportConnector name="auto" uri="auto://localhost:5671?auto.protocols=default,stomp"/>

配置文件修改:

        …… 

<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61608?maximumConnections=1000
&amp;wireFormat.maxFrameSize=104857600&amp;org.apache.activemq.transport.nio.SelectorManager.corelPoolSize=20
&amp;org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50"/>

连接:
在这里插入图片描述
消息发送成功

同样代码只需修改 URI

public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61608";

对于 NIO 和 tcp 的代码相同,但不代表使用其他协议代码相同,因为底层配置不同,其他协议如果使用需要去修改代码

48.ActiveMQ消息持久化理论简介

官网:http://activemq.apache.org/persistence

将MQ收到的消息存储到文件、硬盘、数据库等、 则叫MQ的持久化,这样即使服务器宕机,消息在本地还是有,仍旧可以访问到。

  • 为了避免以外宏机以后丢失消息,需要做到重启后可以恢复消息队列,消息系统一般都会采用持久化机制
  • ActiveMQ 支持的消息持久化机制: 带赋值功能的 LeavelDB、 KahaDB、AMQ、JDBC
  • 就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或者远程数据库再试图将消息发送给接收者,成功则将消息从存储中删除,失败则继续尝试发送。

49.ActiveMQ消息持久化机制之AMQ和KahaDB

AMQ 是文件存储形式,写入快、易恢复 默认 32M 在 ActiveMQ 5.3 之后不再适用

KahaDB 的属性配置 :http://activemq.apache.org/kahadb
KahaDB : 5.4 之后基于日志文件的持久化插件,默认持久化插件,提高了性能和恢复能力,类似Redis的AOF。它使用一个事务日志索引文件来存储所有的地址。

50.ActiveMQ消息持久化机制之KahaDB的存储机制

在这里插入图片描述
四类文件+一把锁 ==> KahaDB
1.db-<数字>.log:kahaDB存储消息到预定义大小的数据记录文件中,一个存满(如每32M一个文件)会再次创建db-2.log、db-3.log …… 。当不会有引用到数据文件的内容时,文件会被删除或归档。
2.db.data:该文件包含了持久化的BTree 索引,索引了消息数据记录的消息,是消息索引文件,本质上是B-Tree(B树),它作为索引指向了 db-.log 里的消息。
一点题外话:就像mysql 数据库,新建一张表,就有这个表对应的 .MYD 文件,作为它的数据文件,就有一个 .MYI 作为索引文件。
3.db.free :文件的具体内容是所有空闲页的ID,存储空闲页 ID有时会被清除
4.db.redo:当 KahaDB 消息存储在强制退出后启动,用于恢复 BTree 索引
5.lock 顾名思义就是锁

51.ActiveMQ消息持久化机制之LeveIDB简介

LeavelDB :5.8 以后引进,希望作为以后的存储引擎,也是基于文件的本地数据存储形式,但是比 KahaDB更快。
它比KahaDB 更快的原因是她不使用B-Tree 索引,而是使用本身自带的 LeavelDB索引

题外话:为什么LeavelDB 更快,并且5.8 以后就支持,为什么还是默认 KahaDB 引擎?
因为 activemq 官网本身没有定论,LeavelDB之后又出了可复制的LeavelDB比LeavelDB 更性能更优越,但需要基于 Zookeeper 所以这些官方还没有定论,仍旧使用 KahaDB。

52.ActiveMQ消息持久化机制之JDBC配置mysql-上

JDBC : 有一部分数据会真实的存储到数据库中
使用JDBC 的持久化,
①修改配置文件,默认 kahaDB
修改之前:

<persistenceAdapter>
       <kahaDB directory="${activemq.data}/kahadb"/>  
</persistenceAdapter>

修改之后:

<persistenceAdapter>
      <jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>

②在activemq 的lib 目录下添加 jdbc 的jar 包 (connector.jar 我使用5.1.41 版本)
③修改配置文件 : activemq.xml 使其连接自己windows 上的数据库,并在本地创建名为activemq 的数据库
在这里插入图片描述

53.ActiveMQ消息持久化机制之JDBC配置mysql-中

④让linux 上activemq 可以访问到 mysql ,之后产生消息。
ActiveMQ 启动后会自动在 mysql 的activemq 数据库下创建三张表:activemq_msgs 、activemq_acks、activemq_lock
activemq_acks:用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存
activemq_lock:在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker
activemq_msgs:用于存储消息,Queue和Topic都存储在这个表中

54.ActiveMQ消息持久化机制之JDBC配置mysql-下

点对点会在数据库的数据表 ACTIVEMQ_MSGS 中加入消息的数据,且在点对点时,消息被消费就会从数据库中删除。
但是对于主题,订阅方式接受到的消息,会在 ACTIVEMQ_MSGS 存储消息,即使MQ 服务器下线,并在 ACTIVEMQ_ACKS 中存储消费者信息 。 并且存储以 activemq 为主,当activemq 中的消息被删除后,数据库中的也会自动被删除。

55.ActiveMQ消息持久化机制之JDBC配置mysql小总结

如果是queue
在没有消费者消费的情况下会将消息保存到activemq_msgs表中,只要有任一一个消费者已经消费过了,消费者之后这些消息将会立即被删除。

如果是topic
一般是先启动消费订阅然后再生产的情况下会将消息保存到activemq_acks

开发有坑:
在这里插入图片描述

56.ActiveMQ消息持久化机制之JDBC With Journal

是什么

  1. 这种方式克服了JDBC Store的不足,JDBC每次消息过来,都需要去写库和读库
  2. ActiveMQ Journal,使用告诉缓存写入技术,大大提高了性能。
  3. 当消费者的消费速度能够跟上生产者消息的生产速度时,journal文件能够大大减少需要写到DB中的消息。

JDBC 改进: 加入高速缓存机制 Journal
高速缓存在 activemq.xml 中的配置:
在这里插入图片描述

57.ActiveMQ消息持久化机制小总结

持久化消息是指:
MQ 所在的服务器down 了消息也不会丢失
持久化机制演化过程:
从最初的AMQ Message Store 方案到 ActiveMQ V4版本推出的High performance journal (高性能事务)附件,并且同步推出了关系型数据库的存储方案, ActiveMQ 5.3 版本有推出了KahaDB 的支持,(也是5.4之后的默认持久化方案),后来ActiveMQ 从5.8开始支持LevelDB ,现在5.9 提供了 Zookeeper + LevelDB 的集群化方案。
ActiveMQ 消息持久化机制有:

机制基于什么
AMQ基于日志文件
KahaDB基于日志文件,5.4 之后的默认持久化
JDBC基于第三方数据库
LevelDB基于文件的本地数据库存储,从5.8 之后推出了LevelDB 性能高于 KahaDB
ReplicatedLevelDB Store从5.8之后提供了基于LevelDB 和Zookeeper 的数据复制方式,用于Master-slave方式的首数据复制选方案

但是无论使用哪种持久化方式,消息的存储逻辑都一样

58.Zookeeper和Replicated LeveIDB集群原理

如何保证高可用:基于Zookeeper和LeveIDB搭建ActiveMQ集群。集群仅提供主备方式的高可用集群功能,避免单点故障。

官方文档:http://activemq.apache.org/replicated-leveldb-store

在这里插入图片描述
这幅图的意思就是当Master 宕机后,zookeper监测到没有心跳信号, 则认为master 宕机了,然后选举机制会从剩下的Slave中选出一个作为新的Master
在这里插入图片描述

59.Zookeeper和Replicated LeveIDB集群部署规划说明

大概流程:
在这里插入图片描述

  1. 环境和版本
    CentOS release 6.8(Final)
    JDK 1.8.0_201
    zookeeper-3.4.9
    apache-activemq-5.15.9
  2. 关闭防火墙并保证win可以ping通过ActiveMQ服务器
  3. 要求具备ZK集群并可以成功启动
    参考之前讲过的zookeeper
  4. 集群部署规划列表
  5. 创建3台集群目录
  6. 修改管理控制台端口
  7. hostname名字映射
  8. ActiveMQ集群配置
  9. 修改各节点的消息端口
  10. 按照顺序启动三个ActiveMQ节点
    前提是ZK集群已经成功启动运行
  11. ZK集群的节点状态说明

60.Zookeeper和Replicated LeveIDB集群部署配置-上

集群搭建: 新建 /mq_cluster 将原始的解压文件复制三个,修改端口 (jetty.xml)
在这里插入图片描述
增加IP 到域名的映射(/etc/hosts 文件)
在这里插入图片描述
修改 为相同的borkername
在这里插入图片描述
改为 replica levelDB (3个都配,这里列举一个)

<persistenceAdapter>
    <replicatedLevelDB
      directory="{activemq.data}/leveldb"
      replicas="3"
      bind="tcp://0.0.0.0:63631"
      zkAddress="localhost:2191,localhost:2192,localhost:2193"
      zkPassword="123456"
	  sync="local_disk"
      zkPath="/activemq/leveldb-stores"
      hostname="wh-mq-server"
      />
</persistenceAdapter>

61.Zookeeper和Replicated LeveIDB集群部署配置-下

改端口 02 节点 =》 61617 03 节点 =》 61618
在这里插入图片描述
想要启动replica leavel DB 必须先启动所有的zookeper 服务,zookeper 的单机伪节点安装这里不细说了,主要说zookeper 复制三份后改配置文件,并让之自动生成 myid 文件,并将zk的端口改为之前表格中对应的端口 。这是conf 下的配置文件
在这里插入图片描述
其具体配置为:

tickTime=2000
initLimit=10
syncLimit=5
clientPort=2191    // 自行设置
server.1=192.168.17.3:2888:3888
server.2=192.168.17.3:2887:3887
server.3=192.168.17.3:286:3886
dataDir=/zk_server/data/log1    // 自行设置

设置了三个,此时方便起见可以写批处理脚本

#!/bin/sh             // 注意这个必须写在第一行

cd /zk_server/zk_01/bin
./zkServer.sh  start

cd /zk_server/zk_02/bin
./zkServer.sh  start

cd /zk_server/zk_03/bin
./zkServer.sh  start   

编写这个 zk_batch.sh 之后, 使用

chmod  700    zk_batch.sh

命令即可让它变为可执行脚本, ./zk_batch.sh start 即可 (即启动了三个zk 的服务)
同理可以写一个批处理关闭zk 服务的脚本和 批处理开启mq 服务 关闭 mq 服务的脚本。

完成上述之后连接zk 的一个客户端

./zkCli.sh -server 127.0.0.1:2191

连接之后:
表示连接成功在这里插入图片描述
查看我的三个节点: 我的分别是 0…… 3 …… 4 …… 5
在这里插入图片描述
查看我的节点状态

get /activemq/leveldb-stores/00000000003

在这里插入图片描述
此次验证表明 00000003 的节点状态是master (即为63631 的那个mq 服务) 而其余的(00000004 00000005) activemq 的节点是 slave

如此集群顺利搭建成功 !

62.Replicated LeveIDB集群故障迁移和验证

ActiveMQ的客户端只能访问Master的Broker,其它处于Slave的Broker不能访问,所以客户端连接的Broker应该使用failover协议(失败转移)

当一个ActiveMQ节点挂掉或者一个Zookeeper节点挂掉,ActiveMQ服务仍然正常运转,如果仅剩一个ActiveMQ节点,由于不能选举Master,所以ActiveMQ不能正常运行

同样的,如果Zookeeper仅剩一个结点活动,不管ActiveMQ各节点存活,ActiveMQ也不能正常提供服务。(ActiveMQ集群的高可用,依赖Zookeeper集群的高可用)

63.高级特性之异步投递

1.引入消息队列之后该如何保证其高可用性
zookeeper+replicated-leceldb-store的主从集群

2.异步投递Async Sends

  1. 对于一个慢消费者,使用同步有可能造成堵塞,消息消费较慢时适合用异步发送消息
  2. activemq 支持同步异步 发送的消息,默认异步。当你设定同步发送的方式和 未使用事务的情况下发持久化消息,这时是同步的。
  3. 如果没有使用事务,且发送的是持久化消息,每次发送都会阻塞一个生产者直到 broker 发回一个确认,这样做保证了消息的安全送达,但是会阻塞客户端,造成很大延时 。
  4. 在高性能要求下,可以使用异步提高producer 的性能。但会消耗较多的client 端内存,也不能完全保证消息发送成功。在 useAsyncSend = true 情况下容忍消息丢失。

在这里插入图片描述

//  开启异步投递
activeMQConnectionFactory.setUseAsyncSend(true);  

在这里插入图片描述

64.高级特性之异步投递如何确认发送成功

如何在投递快还可以保证消息不丢失 ?
异步发送消息丢失的情况场景是: UseAsyncSend 为 true 使用 producer(send)持续发送消息,消息不会阻塞,生产者会认为所有的 send 消息均会被发送到 MQ ,如果MQ 突然宕机,此时生产者端尚未同步到 MQ 的消息均会丢失 。
故正确的异步发送方法需要接收回调

同步发送和异步发送的区别就在于 :
同步发送send 不阻塞就代表消息发送成功
异步发送需要接收回执并又客户端在判断一次是否发送

在代码中接收回调的函数 :

activeMQConnectionFactory.setUseAsyncSend(true);
    ……  
    
 for (int i = 1; i < 4 ; i++) {
         textMessage = session.createTextMessage("msg--" + i);
      textMessage.setJMSMessageID(UUID.randomUUID().toString()+"--  orderr");
     String msgid = textMessage.getJMSMessageID();
            messageProducer.send(textMessage, new AsyncCallback() {
                @Override
                public void onSuccess() {
                    // 发送成功怎么样
                    System.out.println(msgid+"has been successful send ");
                }

                @Override
                public void onException(JMSException e) {
                    // 发送失败怎么样
                    System.out.println(msgid+" has been failure send ");
                }
            });
}    

在这里插入图片描述

65.高级特性之延迟投递和定时投递

① 在配置文件中设置定时器开关 为 true
在这里插入图片描述
② 代码编写
Java 代码中封装的辅助消息类型 ScheduleMessage
可以设置的常用参数如下:
在这里插入图片描述

long delay = 3 * 1000 ;
long perid = 4 * 1000 ;
int repeat = 7 ;
for (int i = 1; i < 4 ; i++) {
    TextMessage textMessage = session.createTextMessage("delay msg--" + i);
    // 消息每过 3 秒投递,每 4 秒重复投递一次 ,一共重复投递 7 次
    textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
    textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,perid);
    textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);

    messageProducer.send(textMessage);
}

66.高级特性之消费重试机制

在这里插入图片描述

67.高级特性之死信队列

官网介绍:https://activemq.apache.org/redelivery-policy

最多六次还没发出就会加入DLQ (死信队列)
在这里插入图片描述

死信队列的一些设置

    修改,当嫌6 次太多,设置为 3次         // 三次的意思是不计算本来发送的第一次 ,之后再次发送的第三次就被废弃
RedeliveryPolicy  redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);

在spring 中使用 死信机制
在这里插入图片描述
在业务逻辑中,如果一个订单系统没有问题,则使用正常的业务队列,当出现问题,则加入死信队列 ,此时可以选择人工干预还是机器处理 。

死信队列默认是全部共享的,但是也可以设置独立的死信队列
独立的死信队列配置

在这里插入图片描述

68.高级特性之防止重复调用

如何保证消息不被重复消费,幂等性的问题
如果消息是做数据库的插入操作,给这个消息一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据 。
如果不是,可以用redis 等的第三方服务,给消息一个全局 id ,只要消费过的消息,将 id ,message 以 K-V 形式写入 redis ,那消费者开始消费前,先去 redis 中查询有没消费的记录即可。
在这里插入图片描述
观察者模式 、 发布订阅者设计模式 :
观察者模式 : 对象间的一对多的依赖关系
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。

发布订阅者 : 是观察者模式的一个概念的变种,

发布/订阅者模式与观察者模式主要有以下几个不同点:

  1. 在观察者模式中,主体维护观察者列表,因此主体知道当状态发生变化时如何通知观察者。然而,在发布者/订阅者中,发布者和订阅者不需要相互了解。它们只需在中间层消息代理(或消息队列)的帮助下进行通信。
  2. 在发布者/订阅者模式中,组件与观察者模式完全分离。在观察者模式中,主题和观察者松散耦合。
  3. 观察者模式主要是以同步方式实现的,即当发生某些事件时,主题调用其所有观察者的适当方法。发布服务器/订阅服务器模式主要以异步方式实现(使用消息队列)。
  4. 发布者/订阅者模式更像是一种跨应用程序模式。发布服务器和订阅服务器可以驻留在两个不同的应用程序中。它们中的每一个都通过消息代理或消息队列进行通信。
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hvitur

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值