谷粒商城篇章9 ---- P248-P261/P292-P294 ---- 消息队列【分布式高级篇六】

目录

1 消息队列(Message Queue)简介

1.1 概述

1.2 消息服务中两个重要概念

1.3 消息队列主要有两种形式的目的地

1.4 JMS和AMQP对比

1.5 应用场景

1.6  Spring支持

1.7 SpringBoot自动配置

1.7 市面上的MQ产品

2 RabbitMQ

2.1 RabbitMQ简介

2.1.1 RabbitMQ简介

2.1.2 核心概念

2.2 docker安装RabbitMQ

2.3 RabbitMQ管理操作页面介绍

2.3.1 Overview概述

2.3.2 Connections连接信息

2.3.3 Channels信道信息

2.3.4 Exchanges交换机信息

2.3.5 Queues队列信息

2.3.6 Admin用户信息

2.4 RabbitMQ运行机制

2.5 交换机(Exchange)类型

2.5.1 Direct Exchange(直连式)

2.5.2 Fanout Exchange(扇出/广播式)

2.5.3 Topic Exchange(主题/发布订阅式) 

2.6 收发消息测试

2.6.1 exchange.direct发送消息

2.6.1.1 exchange.direct绑定队列

2.6.1.2 exchange.direct发送消息

2.6.2 exchange.fanout发送消息

2.6.2.1 exchange.fanout绑定队列

2.6.2.2 exchange.fanout发送消息

2.6.3 exchange.topic发送消息

2.6.3.1 exchange.topic绑定队列

2.6.3.2 exchange.topic发送消息

3 SpringBoot整合RabbitMQ

3.1 引入依赖

3.2 yml配置

3.3 开启RabbitMQ

3.4 整合测试

3.4.1 AmqpAdmin使用

3.4.1.1 创建交换机

3.4.1.2 创建队列

3.4.1.3 创建绑定关系

3.4.2 RabbitTemplate使用

3.4.2.1 发送消息

3.4.2.1.1 使用json序列化对象

3.4.3 RabbitListener & RabbitHandler接收消息

3.4.3.1 RabbitListener监听接收消息

3.4.3.1.1 简单接收消息

3.4.3.1.2 接收消息内容并反序列化

3.4.3.1.3 完整参数写法

3.4.3.1.4 验证多个消费者监听场景

3.4.3.2 RabbitListener和RabbitHandler监听接收消息

3.4.3.2.1 @RabbitListener和@RabbitHandler的区别与作用

3.4.3.2.2 测试重载处理不同类型的消息

3.5 消息可靠抵达

3.5.1 发送端确认

3.5.1.1 ConfirmCallback(确认模式)

3.5.1.2 ReturnCallback(回退模式)

3.5.1.3 消息唯一id

3.5.2 消费端确认(Ack确认机制)

3.5.2.1 消费端自动确认

3.5.2.2 消费端手动确认

3.5.2.3 消费端退货

4 RabbitMQ延时队列(实现定时任务)

4.1 为什么不使用定时任务?

4.2 使用场景

4.3 消息的存活时间TTL(Time To Live)

4.4 DXL和死信队列

4.5 延时队列实现

4.5.1 设置队列过期时间实现延时队列

4.5.2 设置消息过期时间实现延时队列

4.6 延时队列定时关单模拟

4.6.1 实现方式

4.6.1.1 基础版

4.6.1.2 升级版

4.6.2 实现

4.6.2.1 创建Queue、Exchange、Binding方式

4.6.2.2 发送消息相关代码

 4.6.2.3 监听接收消息相关代码

4.6.3 测试


1 消息队列(Message Queue)简介

1.1 概述

        大多应用,可以通过消息服务中间件来提升系统异步通信和扩展解耦能力。

1.2 消息服务中两个重要概念

        消息代理(message broker)目的地(destination)

1.3 消息队列主要有两种形式的目的地

  • 队列(queue):点对点消息通信(point-to-point);
  • 主题(topic):发布(publish)/订阅(subscribe)消息通信。

点对点模式和发布订阅模式区别:

(1)点对点式:

  • 消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移除队列。
  • 消息只有唯一的发送者和接受者(只能有一个接收者读取信息) ,但不是说只有一个接收者。

(2)发布订阅式:

  • 发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息。

1.4 JMS和AMQP对比

(1)JMS

  • 是Java消息服务的规范
  • 基于jvm消息代理的规范
  • 用@JmsListener监听消息
  • JMS是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的 jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。
  • ActiveMQ、HornetMQ是JMS实现的。

(2)AMQP(Advacnce Message Queuing Protocol)

  • 高级消息队列协议,是一个消息代理的规范,兼容JMS
  • RabbitMQ 是AMQP的实现
  • 用@RabbitListener(AMQP)监听消息
  • 它和AMQP有什么 不同,jms是java语言专属的消息服务标准,它是在api层定义标准,并且只能用于java应用;而AMQP是在协议层定义的标准,是跨语言的 。
  • RabbitMQ是AMQP的实现。

1.5 应用场景

        在实际应用中常用的场景:异步处理、应用解耦、流量削峰和消息通讯四个场景。

1.6  Spring支持

  • spring-jms提供了对JMS的支持
  • spring-rabbit提供了对AMQP的支持
  • 需要ConnectionFactory的实现来连接消息代理
  • 提供JmsTemplate、RabbitTemplate来发送消息
  • JmsListener(JMS).@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
  • EnableJms、@EnableRabbit开启支持

1.7 SpringBoot自动配置

  • JmsAutoConfiguration
  • RabbitAutoConfiguration

1.7 市面上的MQ产品

        ActiveMQ、RabbitMQ、RocketMQ、Kafka。

2 RabbitMQ

2.1 RabbitMQ简介

RabbitMQ官方文档:Networking and RabbitMQ — RabbitMQ

2.1.1 RabbitMQ简介

        RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue Protocol)。

2.1.2 核心概念

  • Message:消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头是由一些可选属性组成,这些属性包含routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
  • Publisher:生产者,也是一个向交换机发布消息的客户端应用程序。
  • Exchange:交换机,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。Exchange有4种类型:direct(默认)、 fanout、topic、headers,不同类型的Exchange转发消息的策略有所区别。
  • Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直 在队列里面,等待消费者连接到这个队列将其取走。
  • Binding:绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交 换器理解成一个由绑定构成的路由表。Exchange 和Queue的绑定可以是多对多的关系。
  • Connection:网络连接,比如一个TCP连接。
  • Channel:信道多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道 发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都 是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
  • Consumer:信息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
  • Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加 密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥 有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时 指定,RabbitMQ 默认的 vhost 是 / 。
  • Broker:表示消息队列服务器实体。

拓展: RabbitMQ为什么使用信道而不直接使用TCP连接通信?

原因:TCP连接的创建和销毁开销特别大。创建需要 3 次握手,销毁需要 4 次挥手。高峰时每秒成千上万条TCP连接的创建会照成资源巨大浪费。而且操作系统每秒处理TCP连接数也是有限制的,会造成性能瓶颈。而如果一条线程使用一条信道,一条TCP连接可以容纳无限的信道,即使每秒成千上万的请求也不会照成性能瓶颈。  

2.2 docker安装RabbitMQ

# 下载影像
docker pull rabbitmq:3.9.11-management# 安装
docker run -d --name=rabbitmq --restart=always -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 rabbitmq:3.9.11-management
  • 4369、25672:Erlang发现和集群端口

  • 5671、5672:AMQP端口

  • 15672:web管理后台端口

  • 61613、61614:STOMP协议端口

  • 1883、8883:MQTT协议接口

rabbitmq的虚拟主机以路径来区分,例如:/、/a、/b。虚拟主机之间是相互隔离的。

2.3 RabbitMQ管理操作页面介绍

2.3.1 Overview概述

2.3.2 Connections连接信息

2.3.3 Channels信道信息

2.3.4 Exchanges交换机信息

2.3.5 Queues队列信息

2.3.6 Admin用户信息

2.4 RabbitMQ运行机制

AMQP中的消息路由

  • AMQP中消息的路由过程和Java开发者熟悉的JMS存在一些差别,AMQP中增加了ExchangeBinding的角色。生产者把消息发布到Exchange上,消息最终到达队列并被消费者接收,Binding决定交换机将消息发送到哪个队列。

2.5 交换机(Exchange)类型

        Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:

2.5.1 Direct Exchange(直连式)

        消息中的路由键(routing key)如果和Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routingkey 标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式。

2.5.2 Fanout Exchange(扇出/广播式)

        每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。

2.5.3 Topic Exchange(主题/发布订阅式) 

        topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配一个单词。

2.6 收发消息测试

        按照如下图,新建交换机、队列进行测试。

(1)新建交换机,如下:

(2)新建队列,如下:

2.6.1 exchange.direct发送消息

2.6.1.1 exchange.direct绑定队列

2.6.1.2 exchange.direct发送消息

只有atguigu队列拿到消息,直连型路由键需要完全匹配。 

 Ask Mode:

1. Nack message requeue true:接收消息但不做确认,消息会重新加入队列;

2. Automatic ack:获取消息,应答确认,消息不重新入队,将会从队列中删除;

3. Reject requeue true:拒绝消息,消息会重新加入队列;

4. Reject requeue false:拒绝消息,消息会被从队列中删除。

2.6.2 exchange.fanout发送消息

2.6.2.1 exchange.fanout绑定队列

2.6.2.2 exchange.fanout发送消息

 所有与exchange.fanout交换机绑定的队列都会收到消息,这就是扇出型交换机。

2.6.3 exchange.topic发送消息

2.6.3.1 exchange.topic绑定队列

2.6.3.2 exchange.topic发送消息

(1)

(2) 

 atguigu、atguigu.emps、atguigu.news这三个队列收到了消息。“#”匹配0个或多个单词。

3 SpringBoot整合RabbitMQ

3.1 引入依赖

gulimall-order/pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

3.2 yml配置

gulimall-order/src/main/resources/application.yml

spring:rabbitmq:host: 172.1.11.10port: 5672virtual-host: /

3.3 开启RabbitMQ

相关注解 @EnableRabbit,监听消息必须使用,收发消息可以不使用该注解。

gulimall-order/src/main/java/com/wen/gulimall/order/GulimallOrderApplication.java

@EnableRabbit
@SpringBootApplication
public class GulimallOrderApplication {public static void main(String[] args) {SpringApplication.run(GulimallOrderApplication.class, args);}}

3.4 整合测试

3.4.1 AmqpAdmin使用

3.4.1.1 创建交换机

(1)交换机类型

(2)创建交换机

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {@Resourceprivate AmqpAdmin amqpAdmin;@Testvoid createExchange(){// 声明交换机/*** DirectExchange(String name,         【交换机名称】* boolean durable,                    【是否持久化】* boolean autoDelete,                 【是否自动删除】* Map<String, Object> arguments)      【自定义参数】*/DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);amqpAdmin.declareExchange(directExchange);log.info("Exchange[{}]创建成功","hello-java-exchange");}
}

3.4.1.2 创建队列
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {@Resourceprivate AmqpAdmin amqpAdmin;@Testvoid createQueue(){// 创建队列/*** Queue(String name,                           【队列名称】* boolean durable,                             【是否持久化】* boolean exclusive,                           【是否排他(只能被一个consumer连接占用)】* boolean autoDelete,                          【是否自动删除】* @Nullable Map<String, Object> arguments)     【自定义参数】*/Queue queue = new Queue("hello-java-queue",true,false,false);amqpAdmin.declareQueue(queue);log.info("Queue[{}]创建成功","hello-java-queue");}
}

3.4.1.3 创建绑定关系
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {@Resourceprivate AmqpAdmin amqpAdmin;@Testpublic void createBinding(){// 创建绑定/*** Binding(String destination,                  【目的地,队列name或交换机name】* Binding.DestinationType destinationType,     【目的地类型,queue还是exchange(路由)】* String exchange,                             【交换机】* String routingKey,                           【路由键】* @Nullable Map<String, Object> arguments)     【自定义参数】** 将exchange指定交换机和destination目的地进行绑定,使用routingKey作为路由键*/Binding binding = new Binding("hello-java-queue",Binding.DestinationType.QUEUE,"hello-java-exchange","hello-java",null);amqpAdmin.declareBinding(binding);log.info("Binding[{}]创建成功","hello-java");}
}

3.4.2 RabbitTemplate使用

3.4.2.1 发送消息

如果发送的消息是个对象,我们会使用序列化机制,将对象写出。对象必须实现Serializable接口,例如:

@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {@Resourceprivate RabbitTemplate rabbitTemplate;@Testvoid sendMessage(){/*** convertAndSend(String exchange, 【交换机】* String routingKey,              【路由键】* Object object)                  【发送的信息】*/String msg = "hjhajkxdhjashj哈哈哈哈哈";OrderReturnReasonEntity entity = new OrderReturnReasonEntity();entity.setCreateTime(new Date());entity.setId(2L);entity.setName("多多");//1. 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出。对象必须实现SerializablerabbitTemplate.convertAndSend("hello-java-exchange","hello-java",entity);log.info("发送消息【{}】成功",entity);}
}

 队列收到的消息,如下:

3.4.2.1.1 使用json序列化对象

通过源码分析,如果容器中没有MessageConverter默认使用使用SimpleMessageConverter

这里自定义RabbitMQ配置使用Jackson2JsonMessageConverter 消息转换器

gulimall-order/src/main/java/com/wen/gulimall/order/config/MyRabbitConfig.java

@Configuration
public class MyRabbitConfig {@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}
}

重新发送消息测试:

@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {@Resourceprivate RabbitTemplate rabbitTemplate;@Testvoid sendMessage(){/*** convertAndSend(String exchange, 【交换机】* String routingKey,              【路由键】* Object object)                  【发送的信息】*/String msg = "hjhajkxdhjashj哈哈哈哈哈";OrderReturnReasonEntity entity = new OrderReturnReasonEntity();entity.setCreateTime(new Date());entity.setId(2L);entity.setName("多多");//1. 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出。对象必须实现SerializablerabbitTemplate.convertAndSend("hello-java-exchange","hello-java",entity);log.info("发送消息【{}】成功",entity);}
}

测试结果如下:

3.4.3 RabbitListener & RabbitHandler接收消息

3.4.3.1 RabbitListener监听接收消息

监听消息使用注解@RabbitListener,该注解标注在方法上,使用该注解的前提需要主类上使用注解@EnableRabbit。

3.4.3.1.1 简单接收消息

使用注解@RabbitListener,并通过属性queues声明需要监听的所有队列。

gulimall-order/src/main/java/com/wen/gulimall/order/service/impl/OrderServiceImpl.java

@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage(Object msg){System.out.println("接收到消息...内容:"+msg+"==>类型:"+msg.getClass());
}

监听结果:

接收到消息...内容:(Body:'[B@17ee5a8a(byte[77])' MessageProperties [headers={__TypeId__=com.wen.gulimall.order.entity.OrderReturnReasonEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=hello-java-exchange, receivedRoutingKey=hello-java, deliveryTag=1, consumerTag=amq.ctag-K2kLpChLRK15TemEJ4XRBQ, consumerQueue=hello-java-queue])==>类型:class org.springframework.amqp.core.Message
3.4.3.1.2 接收消息内容并反序列化

由上一步接收到的消息内容可知消息的类型是org.springframework.amqp.core.Message,将需要反序列化的类放在第二个参数的位置可以将消息内容自动封装成对象。(消息接收对象类型要和消息发送对象类型保持一致

第一个参数:Message -- 原生消息详细信息,消息头+消息体。

第二个参数:T<发送消息的类型>

@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage2(Message msg, OrderReturnReasonEntity content){// 消息体byte[] body = msg.getBody();// 消息头信息MessageProperties messageProperties = msg.getMessageProperties();System.out.println("接收到消息...:"+msg+"==>内容:"+content);
}

监听结果:

接收到消息...:(Body:'[B@2ec3c5a5(byte[77])' MessageProperties [headers={__TypeId__=com.wen.gulimall.order.entity.OrderReturnReasonEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=hello-java-exchange, receivedRoutingKey=hello-java, deliveryTag=1, consumerTag=amq.ctag-vuaHxsngkQHP8vWIUy6oGA, consumerQueue=hello-java-queue])==>内容:OrderReturnReasonEntity(id=2, name=多多, sort=null, status=null, createTime=Thu Jan 25 14:32:30 CST 2024)

3.4.3.1.3 完整参数写法

参数类型:

1) Message message:原生消息详细信息,消息头+消息体。

2)T<发送消息的类型> :OrderReturnReasonEntity content。

3) Channel channel:当前传输数据的通道。(一个连接可以容纳多个通道)

@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage3(Message msg,OrderReturnReasonEntity content,Channel channel){log.info("信道Channel:{}",channel);// 消息体byte[] body = msg.getBody();// 消息头信息MessageProperties messageProperties = msg.getMessageProperties();System.out.println("接收到消息...:"+msg+"==>内容:"+content);
}

监听结果:

3.4.3.1.4 验证多个消费者监听场景

Queue可以由多个消费者监听,只能有一个消费者接收到消息。只要收到消息,队列删除该消息。

场景:

1)启动多个订单服务,同一个消息,只有一个客户端接收到;

2)只有一个消息处理完,方法运行结束,才可以接收到下一个消息。

模拟发送10条消息。 

gulimall-order/src/test/java/com/wen/gulimall/order/GulimallOrderApplicationTests.java

@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {@Resourceprivate AmqpAdmin amqpAdmin;@Resourceprivate RabbitTemplate rabbitTemplate;@Testvoid sendMessage(){for(int i=0;i<10;i++) {OrderReturnReasonEntity entity = new OrderReturnReasonEntity();entity.setCreateTime(new Date());entity.setId(2L);entity.setName("多多"+i);//1. 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出。对象必须实现SerializablerabbitTemplate.convertAndSend("hello-java-exchange", "hello-java", entity);log.info("发送消息【{}】成功", entity);}}
}

复制一个订单模块,并启用。

监听结果:

每个客户端都是接收同一个队列,一条消息只能由一个客户端接收,没有重复消息。

在此期间有两个客户端有两个连接,一个客户端只能有一个连接,一个连接可以有多个信道(channel)。

3.4.3.2 RabbitListener和RabbitHandler监听接收消息
3.4.3.2.1 @RabbitListener和@RabbitHandler的区别与作用
  • @RabbitListener:标注在类或方法上。【作用:监听从哪些队列接收消息】
  • @RabbitHandler:标注在方法上。【作用:重载区分不同类型的消息】
3.4.3.2.2 测试重载处理不同类型的消息

发送消息接口,发送两种不同类型的消息

gulimall-order/src/main/java/com/wen/gulimall/order/controller/RabbitController.java

@RestController
public class RabbitController {@Resourceprivate RabbitTemplate rabbitTemplate;@GetMapping("/sendMq")public String sendMq(Integer num){for(int i=0;i<num;i++){if(i%2==0){OrderReturnReasonEntity entity = new OrderReturnReasonEntity();entity.setCreateTime(new Date());entity.setId(2L);entity.setName("多多"+i);//1. 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出。对象必须实现SerializablerabbitTemplate.convertAndSend("hello-java-exchange", "hello-java", entity);}else {OrderEntity orderEntity = new OrderEntity();orderEntity.setOrderSn(UUID.randomUUID().toString());rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java", orderEntity);}}return "ok";}
}

重载方法监听接收不同类型的消息

gulimall-order/src/main/java/com/wen/gulimall/order/service/impl/OrderServiceImpl.java

@RabbitListener(queues = {"hello-java-queue"})
@Slf4j
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {//@RabbitListener(queues = {"hello-java-queue"})@RabbitHandlerpublic void receiveMessage3(Message msg,OrderReturnReasonEntity content,Channel channel) throws InterruptedException {//log.info("信道Channel:{}",channel);log.info("接收到消息..."+content);// 消息体//byte[] body = msg.getBody();消息头信息//MessageProperties messageProperties = msg.getMessageProperties();Thread.sleep(3000);//System.out.println("消息处理完成=>"+content.getName());}@RabbitHandlerpublic void receiveMessage3(Message msg,OrderEntity content,Channel channel) {log.info("接收到消息..."+content);}
}

 重启订单服务,请求http://localhost:9000/sendMq?num=10发送消息测试,通过@RabbitListener(queues={"hello-java-queue"})声明队列,@RabbitHandler重载用于接收同一个队列不同类型的数据。

注意:接收同一个队列不同类型的数据并自动封装成相应的对象,单使用@RabbitListener是无法实现的,需要@RabbitListener和@RabbitHandler搭配使用。如上。

测试结果: 

3.5 消息可靠抵达

官网参考文档:https://www.rabbitmq.com/=> Docs => Server Documentation => Reliable Delivery

1. 为什么不适用事务消息:保证消息不丢失,可靠抵达,因为使用事务消息会使性能下降250倍,为此引入确认机制。

2. 发送端确认

  • publisher:confirmCallback 确认模式;
  • publisher:returnCallback 未投递到 queue 退回模式。

3. 消费端确认

  • consumer:ack机制。

3.5.1 发送端确认

https://www.rabbitmq.com/ => Docs => Server Documentation => Reliable Delivery =>
Acknowledgements and Confirms => Publisher confirms
3.5.1.1 ConfirmCallback(确认模式)

1. 开启 p->e 的发送确认配置,我这里使用的是SpringBoot2.7.8之前的配置已经废弃,这里使用spring.rabbitmq.publisher-confirm-type=correlated替换spring.rabbitmq.publisher-confirms=true

2. 发送端确认类型spring.rabbitmq.publisher-confirm-type有3种,如下

  • none值:是禁用发布确认模式,是默认值。
  • correlated值:是发布消息成功到交换器后会触发回调方法。
  • simple值:经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker。

开启配置

gulimall-order/src/main/resources/application.yml

spring:rabbitmq:host: 172.xx.xx.xxport: 5672virtual-host: /publisher-confirm-type: correlated

自定义RabbitTemplate,设置确认回调

gulimall-order/src/main/java/com/wen/gulimall/order/config/MyRabbitConfig.java

@Configuration
public class MyRabbitConfig {@Resourceprivate RabbitTemplate rabbitTemplate;@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}/*** 定制RabbitTemplate* 1.服务收到消息就回调*      1)spring.rabbitmq.publisher-confirms=true(这里使用spring.rabbitmq.publisher-confirm-type=correlated)*      2)设置确认回调*  注意:springboot rabbitmq 中 spring.rabbitmq.publisher-confirms 已经失效。需要使用 spring.rabbitmq.publisher-confirm-type 替代*  publisher-confirm-type有3种:*  1)NONE值是禁用发布确认模式,是默认值*  2)CORRELATED值是发布消息成功到交换器后会触发回调方法*  3)SIMPLE值经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;*/@PostConstruct //MyRabbitConfig对象创建完成之后,执行这个方法public void initRabbitTemplate(){// 设置确认回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/***  只要消息抵达Broker就ack=true* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)* @param b (ack) 消息是否成功收到* @param s (cause) 失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("confirm...correlationData["+correlationData+"]==>ack["+b+"]==>cause["+s+"]");}});}
}

测试

请求http://localhost:9000/sendMq?num=10 ,看测试结果,只要消息抵达Broker就ack=true,如下:

3.5.1.2 ReturnCallback(回退模式)

开启 e->q 的确认配置,消息不能由交换机投递到目标队列将调用returnCallback。

gulimall-order/src/main/resources/application.yml

server:port: 9000
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.xx.xx.xx:9906/gulimall_omsusername: rootpassword: rootrabbitmq:host: 172.xx.xx.xxport: 5672virtual-host: /# 开启发送端确认publisher-confirm-type: correlated# 开启发送端消息抵达队列的确认,默认是falsepublisher-returns: true# 只要消息抵达队列,以异步发送优先回调我们的returnConfirmtemplate:mandatory: trueredis:host: 172.xx.xx.xxcloud:nacos:discovery:server-addr: 172.xx.xx.xx:8848main:allow-circular-references: true
mybatis-plus:mapper-locations: classpath:/mapper/**/*.xmlglobal-config: # 全局配置db-config:id-type: auto # 主键自增

 设置消息抵达队列的确认回调

@Configuration
public class MyRabbitConfig {@Resourceprivate RabbitTemplate rabbitTemplate;@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}/*** 定制RabbitTemplate* 1.服务收到消息就回调*      1)spring.rabbitmq.publisher-confirms=true(这里使用spring.rabbitmq.publisher-confirm-type=correlated)*      2)设置确认回调 ConfirmCallback* 2. 消息抵达队列就回调*      1)spring.rabbitmq.publisher-returns=true*          spring.rabbitmq.template.mandatory=true*      2)设置消息抵达队列的确认回调 ReturnCallback*  注意:springboot rabbitmq 中 spring.rabbitmq.publisher-confirms 已经失效。需要使用 spring.rabbitmq.publisher-confirm-type 替代*  publisher-confirm-type有3种:*  1)NONE值是禁用发布确认模式,是默认值*  2)CORRELATED值是发布消息成功到交换器后会触发回调方法*  3)SIMPLE值经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;*/@PostConstruct //MyRabbitConfig对象创建完成之后,执行这个方法public void initRabbitTemplate(){// 设置确认回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/***  只要消息抵达Broker就ack=true* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)* @param b (ack) 消息是否成功收到* @param s (cause) 失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("confirm...correlationData["+correlationData+"]==>ack["+b+"]==>cause["+s+"]");}});// 设置消息抵达队列的确认回调rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {/*** 只要消息没有投递给指定的队列,就触发这个失败回调* @param message the returned message. 投递失败的消息详细信息* @param replyCode the reply code.     回复的状态码* @param replyText the reply text.     回复的文本内容* @param exchange the exchange.        当时这个消息接收的交换机* @param routingKey the routing key.   当时这个消息用的哪个路由键*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {System.out.println("Fail Message["+message+"]==>replyCode["+replyCode+"]==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");}});}
}

测试:

该模式需要消息投递到指定队列失败才会触发,这里将路由键故意改错便于测试,测试结束后将错误改回来。

错误消息:

Fail Message[(Body:'[B@f04a8cf(byte[855])' MessageProperties [headers={__TypeId__=com.wen.gulimall.order.entity.OrderEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])]==>replyCode[312]==>replyText[NO_ROUTE]==>exchange[hello-java-exchange]==>routingKey[hello-java333]

3.5.1.3 消息唯一id

在发送消息时参数CorrelationData就是消息的唯一id。

 这个id在发送端确认时是可以拿到的,可以用于排查哪些消息为成功抵达(与消费端接收到存放在数据库中的消息唯一id进行对比)。

3.5.2 消费端确认(Ack确认机制)

3.5.2.1 消费端自动确认

1)消费端确认,默认是自动确认的,只要消息收到,客户端会自动确认,服务端就会移除这个消息。

2)消费端自动确认存在问题:

        在接收消息这里打上断点,在处理完第一个消息后,关掉服务器,模拟突发状况,服务器宕机,如下:

关掉服务器后,剩余未处理的4个消息也消失不见,未经过消费端处理,相当于消息丢失。 

3.5.2.2 消费端手动确认

        为了解决消息自动确认,遇到突发状况造成消息丢失问题,可以使用手动确认。 只有手动确认的消息才能被队列移除。

 开启消费端手动确认配置

gulimall-order/src/main/resources/application.yml

spring:rabbitmq:host: 172.1.11.10port: 5672virtual-host: /# 开启发送端确认publisher-confirm-type: correlated# 开启发送端消息抵达队列的确认,默认是falsepublisher-returns: true# 只要消息抵达队列,以异步发送优先回调我们的returnConfirmtemplate:mandatory: true# 开启消费端手动确认listener:simple:acknowledge-mode: manual

1. 消息抵达客户端,不手动ack测试 

发送五个消息进行测试,客户端就算拿到消息不做消息抵达确认,队列不能移除未确认的消息,只是消息的状态有ready变为unacked,关闭客户端服务器后消息的状态又由unacked变为ready,下次重启客户端服务器又可以接收消息。

关闭客户端服务器,观察消息的状态:

(由unacked变为ready)

2. 消息抵达客户端,模拟部分消息手动ack测试

客户端确认,debug模式下无法模拟真实情况下宕机,关闭了也会继续执行,这里断点放开,根据投递标签deliveryTag(channel内按顺序自增)取余模拟客户端突然宕机未接收到消息,如下: 

@RabbitListener(queues = {"hello-java-queue"})
@Slf4j
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {...//@RabbitListener(queues = {"hello-java-queue"})@RabbitHandlerpublic void receiveMessage3(Message msg,OrderReturnReasonEntity content,Channel channel) throws InterruptedException {//log.info("信道Channel:{}",channel);System.out.println("接收到消息..."+content);// 消息体byte[] body = msg.getBody();// 消息头信息MessageProperties messageProperties = msg.getMessageProperties();Thread.sleep(3000);System.out.println("消息处理完成=>"+content.getName());// channel内按顺序自增的long deliveryTag = msg.getMessageProperties().getDeliveryTag();System.out.println("deliveryTag=>"+deliveryTag);// basicAck(long deliveryTag, // channel内按顺序自增// boolean multiple)          // 是否批量确认// debug下无法模拟真实情况下的宕机,关闭了也会继续执行,// 这里根据投递标签deliveryTag模拟客户端突然宕机未接收到消息try {if(deliveryTag%2==0) {channel.basicAck(deliveryTag,false);// 手动ack确认接收到消息System.out.println("签收了货物..."+deliveryTag);}} catch (IOException e) {throw new RuntimeException(e);}}}

 此时只确认签收了货物-2和货物-4,还有3个未确认,如下:

关闭客户端服务器,消息状态由unacked变成ready,如下: 

重启客户端服务器,剩余的消息可以继续签收,因为deliveryTag是根据信道channel里的消息顺序自增,客户端重启后消息又重新排序。之前发送的5个消息(1,2,3,4,5)只确认了偶数2和4,客户端重启后channel内的消息顺序变为(1,2,3),消费端可以再次确认接收一个消息。

3.5.2.3 消费端退货
@RabbitListener(queues = {"hello-java-queue"})
@Slf4j
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {...//@RabbitListener(queues = {"hello-java-queue"})@RabbitHandlerpublic void receiveMessage3(Message msg,OrderReturnReasonEntity content,Channel channel) throws InterruptedException {//log.info("信道Channel:{}",channel);System.out.println("接收到消息..."+content);// 消息体byte[] body = msg.getBody();// 消息头信息MessageProperties messageProperties = msg.getMessageProperties();Thread.sleep(3000);System.out.println("消息处理完成=>"+content.getName());// channel内按顺序自增的long deliveryTag = msg.getMessageProperties().getDeliveryTag();System.out.println("deliveryTag=>"+deliveryTag);// basicAck(long deliveryTag, // channel内按顺序自增// boolean multiple)          // 是否批量确认// debug下无法模拟真实情况下的宕机,关闭了也会继续执行,// 这里根据投递标签deliveryTag模拟客户端突然宕机未接收到消息try {// 签收货物非批量模式if(deliveryTag%2==0) {// 收货channel.basicAck(deliveryTag,false);// 手动ack确认接收到消息System.out.println("签收了货物..."+deliveryTag);}else {// 退货// requeue=false 丢弃 ;requeue=true 重回队列// basicNack(long deliveryTag, boolean multiple, boolean requeue)channel.basicNack(deliveryTag,false,false);System.out.println("没有签收货物..."+deliveryTag);}} catch (IOException e) {throw new RuntimeException(e);}}
}

channel.basicNack(deliveryTag,false,false);中requeue=false丢弃掉未被签收的货物,清空队列,如下:

 当requeue=true时,deliveryTag为奇数消息被拒收重新入队deliveryTag变成偶数,被签收。

4 RabbitMQ延时队列(实现定时任务)

4.1 为什么不使用定时任务?

定时任务的时效性问题

场景:订单30min中未支付,关闭订单

出现问题:定时任务在0min中扫描的时候没有订单未支付,1min中后下订单,定时任务第二次执行的时候(30min)这时下订单29min中未支付订单不会被关闭,到定时任务第三次执行的时候(60min)才能关闭未支付的订单,订单从创建到关闭花费了30+29=59min,不满足订单30min中未支付关闭订单。

4.2 使用场景

4.3 消息的存活时间TTL(Time To Live)

  • 消息的TTL就是消息的存活时间
  • RabbitMQ可以对队列消息分别设置TTL。
    • 对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信
    • 如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延时任务的关键。可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。

4.4 DXL和死信队列

  • DXL(Dead Letter Exchanges)即死信交换机,它其实就是一个正常的交换机,能够与任何队列绑定。
  • 死信队列是指队列(正常)上的消息(过期)变成死信后,能够发送到另外一个交换机(DLX),然后被路由到一个队列上,这个队列就是死信队列。
  • 成为死信一般有以下几种情况:

        1)消息被拒收(basic.reject/basic.nack)requeue=false;

        2)消息的TTL到了,消息过期;

        3)队列长度限制被超越(队列满了)。

4.5 延时队列实现

1. 延时队列实现有两种方式设置队列过期时间设置消息过期时间实现延时队列。

2. 建议使用设置队列过期时间实现延时队列,因为设置消息过期时间,MQ是惰性检查。比如:第一个进来的消息过期时间是5min,第二个进来的消息过期时间是2min,MQ会先检查第一个消息,等第一个消息进入死信队列,再去检查第二个消息,这时第二个消息已经死3min了。

4.5.1 设置队列过期时间实现延时队列

4.5.2 设置消息过期时间实现延时队列

4.6 延时队列定时关单模拟

以下单为例,设置队列过期时间实现延时队列

4.6.1 实现方式

4.6.1.1 基础版

交换机与队列一一对应,一台路由器路由一个队列。

给队列user.order.delay.queue(死信队列)设置了三个参数:

  • x-dead-letter-exchange:user.order.exchange (死信交换机)
  • x-dead-letter-rounting-key:order(死信路由键)
  • x-message-ttl:60000(队列里面所有消息存活时间60000ms)
4.6.1.2 升级版

一个微服务模块配置一个交换机,一个交换机绑定多个队列。

4.6.2 实现

4.6.2.1 创建Queue、Exchange、Binding方式

容器中的 Binding、Queue、Exchange都会自动创建(RabbitMQ中没有的情况)

  • 第一次发送消息【使用队列】的时候创建交换机、队列、绑定关系
  • RabbitMQ中没有交换机、队列才会创建;RabbitMQ中只要有,属性发生变化也不会覆盖。

gulimall-order/src/main/java/com/wen/gulimall/order/config/MyMQConfig.java

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;/*** @author W* @createDate 2024/02/21 14:39* @description: 创建 Exchange 、 Queue、 Binding*/
@Configuration
public class MyMQConfig {//@RabbitListener(queues = "order.release.order.queue")//public void listen(Message message, Channel channel, OrderEntity orderEntity) throws IOException {//    System.out.println("收到过期订单消息,准备关闭订单:------->"+orderEntity);//    // 确认收到消息//    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//}/*** 容器中的 Binding、Queue、Exchange都会自动创建(RabbitMQ没有的情况)*  延时队列*  RabbitMQ中只要有。属性发生变化也不会覆盖。* @return*/@Beanpublic Queue orderDelayQueue(){Map<String, Object> arguments = new HashMap<>();//x-dead-letter-exchange: order-event-exchange//x-dead-letter-routing-key: order.release.order//x-message-ttl: 60000arguments.put("x-dead-letter-exchange","order-event-exchange");arguments.put("x-dead-letter-routing-key","order.release.order");arguments.put("x-message-ttl",60000);// String name,       【队列名称】// boolean durable,   【是否持久化】// boolean exclusive, 【是否排它】// boolean autoDelete,【是否自动删除】// @Nullable Map<String, Object> arguments 【自定义参数,死信路由、死信路由键、消息存活时间】Queue queue = new Queue("order.delay.queue",true,false,false,arguments);return queue;}/*** 死信队列* @return*/@Beanpublic Queue orderReleaseOrderQueue(){Queue queue = new Queue("order.release.order.queue",true,false,false);return queue;}/*** 普通路由/死信路由* @return*/@Beanpublic Exchange orderEventExchange(){// String name, boolean durable, boolean autoDelete, Map<String, Object> argumentsTopicExchange topicExchange = new TopicExchange("order-event-exchange",true,false);return topicExchange;}/*** 交换机与延时队列的绑定* @return*/@Beanpublic Binding orderCreateOrder(){// String destination, 【目的地】// DestinationType destinationType, 【目的地类型,queue、exchange】// String exchange,// String routingKey,// @Nullable Map<String, Object> argumentsreturn new Binding("order.delay.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.create.order",null);}/*** 死信交换机与死信队列的绑定* @return*/@Beanpublic Binding orderReleaseOrder(){return new Binding("order.release.order.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.release.order",null);}
}

启动订单服务后,给延时队列发送消息后(相关代码如下),创建队列和交换机以及绑定关系,如下:

4.6.2.2 发送消息相关代码

gulimall-order/src/main/java/com/wen/gulimall/order/web/HelloController.java

@Controller
public class HelloController {@Resourceprivate RabbitTemplate rabbitTemplate;@ResponseBody@GetMapping("/test/createOrder")public String createOrderTest(){OrderEntity orderEntity = new OrderEntity();orderEntity.setOrderSn(UUID.randomUUID().toString());orderEntity.setCreateTime(new Date());rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",orderEntity);return "ok";}
}
 4.6.2.3 监听接收消息相关代码

gulimall-order/src/main/java/com/wen/gulimall/order/config/MyMQConfig.java

@Configuration
public class MyMQConfig {@RabbitListener(queues = "order.release.order.queue")public void listen(Message message, Channel channel, OrderEntity orderEntity) throws IOException {System.out.println("当前时间"+new Date() +"收到过期订单消息,准备关闭订单:------->"+orderEntity);// 确认收到消息channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}...// 创建交换机 队列 绑定
}

4.6.3 测试

  • 启动订单服务,请求 http://order.gulimall.com/test/createOrder 给延时队列(order.delay.queue) 发送消息。

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

  • 一分钟后,死信队列(order.release.order.queue) 接收到延时队列(order.delay.queue)中过期的消息(即死信),消费者监听队列(order.release.order.queue)消费信息,控制台打印如下: 

当前时间Thu Feb 22 15:16:20 CST 2024收到过期订单消息,准备关闭订单:------->OrderEntity(id=null, memberId=null, orderSn=0d784fd2-6c82-48cc-aa2c-e573bf8de1d1, couponId=null, createTime=Thu Feb 22 15:15:20 CST 2024, memberUsername=null, totalAmount=null, payAmount=null, freightAmount=null, promotionAmount=null, integrationAmount=null, couponAmount=null, discountAmount=null, payType=null, sourceType=null, status=null, deliveryCompany=null, deliverySn=null, autoConfirmDay=null, integration=null, growth=null, billType=null, billHeader=null, billContent=null, billReceiverPhone=null, billReceiverEmail=null, receiverName=null, receiverPhone=null, receiverPostCode=null, receiverProvince=null, receiverCity=null, receiverRegion=null, receiverDetailAddress=null, note=null, confirmStatus=null, deleteStatus=null, useIntegration=null, paymentTime=null, deliveryTime=null, receiveTime=null, commentTime=null, modifyTime=null)

  • 一分钟后,死信队列(order.release.order.queue) 接收到延时队列(order.delay.queue)中过期的消息(即死信),如果没有消费者监听接收消息,消息在死信队列(order.release.order.queue)中,如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/696943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是Elasticsearch SQL

什么是Elasticsearch SQL 一. 介绍二. SQL 入门 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一. 介绍 Elasticsearch SQL 是一个 X-Pack 组件&#xff0c;允许针对 Elasticsea…

通俗易懂理解G-GhostNet轻量级神经网络模型

一、参考资料 原始论文&#xff1a;[1] IJCV22 | 已开源 | 华为GhostNet再升级&#xff0c;全系列硬件上最优极简AI网络 二、G-GhostNet相关介绍 G-GhostNet 又称为 GhostNetV1 的升级版&#xff0c;是针对GPU优化的轻量级神经网络。 1. 摘要 GhostNetV1 作为近年来最流行…

Leetcode 611.有效三角形的个数

题目 给定一个包含非负整数的数组 nums &#xff0c;返回其中可以组成三角形三条边的三元组个数。 示例 1: 输入: nums [2,2,3,4] 输出: 3 解释:有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 2) 2,2,3示例 2: 输入: nums [4,2,3,4] 输出: 4提示: 1 < nums…

WPF 开发调试比较:Visual Studio 原生和Snoop调试控制台

文章目录 前言运行环境简单的WPF代码实现一个简单的ListBoxVisual Studio自带代码调试热重置功能测试实时可视化树查找窗口元素显示属性 Snoop调试使用Snoop简单使用调试控制台元素追踪结构树Visual/可视化结构树Logical/本地代码可视化树AutoMation/自动识别结构树 WPF元素控制…

基于springboot+vue的房屋租赁管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

手拉手Vite+Vue3+TinyVue+Echarts+TailwindCSS

技术栈springboot3hutool-alloshi-coreVue3viteTinyVueEchartsTailwindCSS软件版本IDEAIntelliJ IDEA 2022.2.1JDK17Spring Boot3.1hutool-all5.8.18oshi-core6.4.1Vue35.0.10vite5.0.10axios1.6.7echarts5.4.3 ECharts是一个使用 JavaScript 实现的开源可视化库&#xff0c;可…

go-zero微服务入门教程

go-zero微服务入门教程 本教程主要模拟实现用户注册和用户信息查询两个接口。 准备工作 安装基础环境 安装etcd&#xff0c; mysql&#xff0c;redis&#xff0c;建议采用docker安装。 MySQL安装好之后&#xff0c;新建数据库dsms_admin&#xff0c;并新建表sys_user&#…

详细分析Python中的unittest测试框架

目录 1. 基本知识2. API2.1 断言2.2 setUp() 和 tearDown() 3. Demo 1. 基本知识 unittest 是 Python 标准库中的一个单元测试框架&#xff0c;用于编写和执行测试用例以验证代码的正确性 提供了一种结构化的方法来编写测试&#xff0c;使得测试代码更加模块化和易于维护 以…

【ACW 服务端】页面操作Java增删改查代码生成

版本: 1.2.2-JDK17-SNAPSHOT 项目地址&#xff1a;wu-smart-acw 演示地址&#xff1a;演示地址 admin/admin Java增删改查代码生成 找到对应菜单 选择你需要的数据实例 选择数据库 选择数据库表 选择客户端&#xff08;如果是本地ACW服务代码启动默认注册上的客户端ID是…

Maven【1】(命令行操作)

文章目录 一丶创建maven工程二、理解pom.xml三、maven的构建命令1.编译操作2.清理操作3.测试操作4.打包操作5.安装操作 一丶创建maven工程 首先创建这样一个目录&#xff0c;然后从命令行里进入这个目录&#xff1a; 然后接下来就在这个命令行里进行操作了。 这个命令是&…

深度学习发展里程碑事件2006-2024

2006-2024年&#xff0c;深度学习发展经历众多的里程碑事件&#xff0c;一次次地刺激着人们的神经&#xff0c;带来巨大的兴奋。电影还在继续&#xff0c;好戏在后面&#xff0c;期待…… 2006年 深度信念网络&#xff08;DBNs&#xff09;&#xff1a;Geoffrey Hinton与他的学…

计算机组成原理(9)----硬布线控制器

控制单元CU若想发出对应的控制信号&#xff0c;则需要以下信息&#xff1a;指令操作码&#xff0c;目前的机器周期&#xff0c;节拍信号&#xff0c;机器状态条件&#xff0c;根据这些信息&#xff0c;CU就能确定在这个节拍下应该发出哪些"微命令"&#xff0c;也就是…

SQL注入:使用预编译防御SQL注入时产生的问题

目录 前言 模拟预编译 真正的预编译 预编译中存在的SQL注入 宽字节 没有进行参数绑定 无法预编译的位置 前言 相信学习过SQL注入的小伙伴都知道防御SQL注入最好的方法&#xff0c;就是使用预编译也就是PDO是可以非常好的防御SQL注入的&#xff0c;但是如果错误的设置了…

计算机设计大赛 深度学习动物识别 - 卷积神经网络 机器视觉 图像识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

Python自动化UI测试之Selenium基础实操

1. Selenium简介 Selenium 是一个用于 Web 应用程序测试的工具。最初是为网站自动化测试而开发的&#xff0c;可以直接运行在浏览器上&#xff0c;支持的浏览器包括 IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Googl…

SVN忽略已提交的文件(ignore,移出版本控制)

本文适用于已安装TortoiseSVN客户端的同学。 1、右键点击要忽略的文件夹或文件&#xff0c;鼠标移到“TortoiseSVN”&#xff0c;找到“Unversion and add to ignore list”&#xff0c;选择文件夹&#xff0c;弹出提示框确认忽略。 2、设置完忽略文件后&#xff0c;还需要做…

多维时序 | Matlab实现GRU-MATT门控循环单元融合多头注意力多变量时间序列预测模型

多维时序 | Matlab实现GRU-MATT门控循环单元融合多头注意力多变量时间序列预测模型 目录 多维时序 | Matlab实现GRU-MATT门控循环单元融合多头注意力多变量时间序列预测模型预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.多维时序 | Matlab实现GRU-MATT门控循环单元融…

【Maven】介绍、下载及安装、集成IDEA

目录 一、什么是Maven Maven的作用 Maven模型 Maven仓库 二、下载及安装 三、IDEA集成Maven 1、POM配置详解 2、配置Maven环境 局部配置 全局设置 四、创建Maven项目 五、Maven坐标详解 六、导入Maven项目 方式1&#xff1a;使用Maven面板&#xff0c;快速导入项目 …

React Native框架开发介绍,以及其优点

大家好&#xff0c;我是咕噜铁蛋&#xff0c;在今天的文章中&#xff0c;我通过科技手段和大家一起探讨一下React Native框架的开发介绍以及其优点。我深知选择合适的开发工具对于项目的成功至关重要。而React Native作为一款流行的跨平台移动应用开发框架&#xff0c;其独特之…

【服务器数据恢复】FreeNAS+ESXi虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器通过FreeNAS&#xff08;本案例使用的是UFS2文件系统&#xff09;实现iSCSI存储&#xff0c;整个UFS2文件系统作为一个文件挂载到ESXi虚拟化系统&#xff08;安装在另外2台服务器上&#xff09;上。该虚拟化系统一共有5台虚拟机&…