RabbitMQ是如何保证消息可靠性的?——Java全栈知识(16)

RabbitMQ 的消息不可靠也就是 RabbitMQ 消息丢失只会发生在以下几个方面:

  1. 生产者发送消息到 MQ 或者 Exchange 过程中丢失。
  2. Exchange 中的消息发送到 MQ 中丢失。
  3. 消息在 MQ 或者 Exchange 中服务器宕机导致消息丢失。
  4. 消息被消费者消费的过程中丢失。
    image.png
    大致就分为生产者 -> MQ -> 消费者这三步的时候消息丢失。

1、生产者到 MQ

1.1. 生产者重试机制

首先第一种情况,就是生产者发送消息时,出现了网络故障,导致与 MQ 的连接中断。
为了解决这个问题,SpringAMQP 提供的消息发送时的重试机制。即:当 RabbitTemplate 与 MQ 连接超时后,多次重试。
修改 publisher 模块的 application.yaml 文件,添加下面的内容:

spring:rabbitmq:connection-timeout: 1s # 设置MQ的连接超时时间template:retry:enabled: true # 开启超时重试机制initial-interval: 1000ms # 失败后的初始等待时间multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multipliermax-attempts: 3 # 最大重试次数

1.2、生产者确认机制

1、Publisher Confirm 机制

也就是生产者确认机制,用于确保消息已经被 Exchange 成功接收和处理。一旦消息成功到达 Exchange 并被处理,RabbitMQ 会向消息生产者发送确认信号 (ACK)。如果由于某种原因(例如,Exchange 不存在或路由键不匹配)消息无法被处理,RabbitMQ 会向消息生产者发送否认信号 (NACK)

	//启用Publisher Confirms  channel.confirmSelect();  //设置Publisher Confirms回调  channel.addConfirmListener(new ConfirmListener() {  //在这里处理消息确认  @Override  public void handleAck(long deliveryTag, boolean multiple) throws IOException {  System.out.println("Message confirmed with deliveryTag:"deliveryTag);  }  //在这里处理消息未确认  @Override  public void handleNack(long deliveryTag, boolean multiple) throws IOException {  System.out.println("Message not confirmed with deliveryTag:"deliveryTag);  }  
});
2、Publisher Return 机制

Publisher Confirm 机制用于消息无法正常到达交换机中的情况,Publisher Return 机制用于消息无法正常路由到队列的情况。
image.png|800
当消息正常路由到队列中的时候,MQ 不会返回任何消息,当无法正常路由的时候会返回错误信息。

1.3、开启生产者确认

在 publisher 模块的 application.yaml 中添加配置:

spring:  rabbitmq:  publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型  publisher-returns: true # 开启publisher return机制

这里 publisher-confirm-type 有三种模式可选:

  • none:关闭 confirm 机制
  • simple:同步阻塞等待 MQ 的回执
  • correlated:MQ 异步回调返回回执
    一般我们推荐使用 correlated,回调机制。
定义 ReturnCallback

每个 RabbitTemplate 只能配置一个 ReturnCallback,因此我们可以在配置类中统一设置。我们在 publisher 模块定义一个配置类:
image.png
内容如下:

package com.itheima.publisher.config;import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;@Slf4j
@AllArgsConstructor
@Configuration
public class MqConfig {private final RabbitTemplate rabbitTemplate;@PostConstructpublic void init(){rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {@Overridepublic void returnedMessage(ReturnedMessage returned) {log.error("触发return callback,");log.debug("exchange: {}", returned.getExchange());log.debug("routingKey: {}", returned.getRoutingKey());log.debug("message: {}", returned.getMessage());log.debug("replyCode: {}", returned.getReplyCode());log.debug("replyText: {}", returned.getReplyText());}});}
}
定义 ConfirmCallback

由于每个消息发送时的处理逻辑不一定相同,因此 ConfirmCallback 需要在每次发消息时定义。具体来说,是在调用 RabbitTemplate 中的 convertAndSend 方法时,多传递一个参数:
image.png
这里的 CorrelationData 中包含两个核心的东西:

  • id:消息的唯一标示,MQ 对不同的消息的回执以此做判断,避免混淆
  • SettableListenableFuture:回执结果的 Future 对象
    将来 MQ 的回执就会通过这个 Future 来返回,我们可以提前给 CorrelationData 中的 Future 添加回调函数来处理消息回执:
    image.png

我们新建一个测试,向系统自带的交换机发送消息,并且添加 ConfirmCallback

@Test
void testPublisherConfirm() {// 1.创建CorrelationDataCorrelationData cd = new CorrelationData();// 2.给Future添加ConfirmCallbackcd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {@Overridepublic void onFailure(Throwable ex) {// 2.1.Future发生异常时的处理逻辑,基本不会触发log.error("send message fail", ex);}@Overridepublic void onSuccess(CorrelationData.Confirm result) {// 2.2.Future接收到回执的处理逻辑,参数中的result就是回执内容if(result.isAck()){ // result.isAck(),boolean类型,true代表ack回执,false 代表 nack回执log.debug("发送消息成功,收到 ack!");}else{ // result.getReason(),String类型,返回nack时的异常描述log.error("发送消息失败,收到 nack, reason : {}", result.getReason());}}});// 3.发送消息rabbitTemplate.convertAndSend("hmall.direct", "q", "hello", cd);
}

执行结果如下:
image.png
可以看到,由于传递的 RoutingKey 是错误的,路由失败后,触发了 return callback,同时也收到了 ack。
当我们修改为正确的 RoutingKey 以后,就不会触发 return callback 了,只收到 ack。
而如果连交换机都是错误的,则只会收到 nack。
注意
开启生产者确认比较消耗 MQ 性能,一般不建议开启。而且大家思考一下触发确认的几种情况:

  • 路由失败:一般是因为 RoutingKey 错误导致,往往是编程导致
  • 交换机名称错误:同样是编程错误导致
  • MQ 内部故障:这种需要处理,但概率往往较低。因此只有对消息可靠性要求非常高的业务才需要开启,而且仅仅需要开启 ConfirmCallback 处理 nack 就可以了。

2、队列

2.1. 数据持久化

为了提升性能,默认情况下 MQ 的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置数据持久化,包括:

  • 交换机持久化
  • 队列持久化
  • 消息持久化
    我们以控制台界面为例来说明。
2.1.1. 交换机持久化

在控制台的 Exchanges 页面,添加交换机时可以配置交换机的 Durability 参数:
image.png
设置为 Durable 就是持久化模式,Transient 就是临时模式。

2.1.2. 队列持久化

在控制台的 Queues 页面,添加队列时,同样可以配置队列的 Durability 参数:
image.png

2.1.3. 消息持久化

在控制台发送消息的时候,可以添加很多参数,而消息的持久化是要配置一个 properties
image.png

说明:在开启持久化机制以后,如果同时还开启了生产者确认,那么 MQ 会在消息持久化以后才发送 ACK 回执,进一步确保消息的可靠性。
不过出于性能考虑,为了减少 IO 次数,发送到 MQ 的消息并不是逐条持久化到数据库的,而是每隔一段时间批量持久化。一般间隔在 100 毫秒左右,这就会导致 ACK 有一定的延迟,因此建议生产者确认全部采用异步方式。

2.2. LazyQueue

在默认情况下,RabbitMQ 会将接收到的信息保存在内存中以降低消息收发的延迟。但在某些特殊情况下,这会导致消息积压,比如:

  • 消费者宕机或出现网络故障
  • 消息发送量激增,超过了消费者处理速度
  • 消费者处理业务发生阻塞
    一旦出现消息堆积问题,RabbitMQ 的内存占用就会越来越高,直到触发内存预警上限。此时 RabbitMQ 会将内存消息刷到磁盘上,这个行为成为 PageOut. PageOut 会耗费一段时间,并且会阻塞队列进程。因此在这个过程中 RabbitMQ 不会再处理新的消息,生产者的所有请求都会被阻塞。

为了解决这个问题,从 RabbitMQ 的 3.6.0 版本开始,就增加了 Lazy Queues 的模式,也就是惰性队列。惰性队列的特征如下:

  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存(也就是懒加载)
  • 支持数百万条的消息存储
    而在 3.12 版本之后,LazyQueue 已经成为所有队列的默认格式。因此官方推荐升级 MQ 为 3.12 版本或者所有队列都设置为 LazyQueue 模式。
2.2.1. 控制台配置 Lazy 模式

在添加队列的时候,添加 x-queue-mod=lazy 参数即可设置队列为 Lazy 模式:
image.png

2.2.2. 代码配置 Lazy 模式

在利用 SpringAMQP 声明队列的时候,添加 x-queue-mod=lazy 参数也可设置队列为 Lazy 模式:

@Bean
public Queue lazyQueue(){return QueueBuilder.durable("lazy.queue").lazy() // 开启Lazy模式.build();
}

这里是通过 QueueBuilderlazy() 函数配置 Lazy 模式,底层源码如下:
image.png

当然,我们也可以基于注解来声明队列并设置为 Lazy 模式:

@RabbitListener(queuesToDeclare = @Queue(name = "lazy.queue",durable = "true",arguments = @Argument(name = "x-queue-mode", value = "lazy")
))
public void listenLazyQueue(String msg){log.info("接收到 lazy.queue的消息:{}", msg);
}
2.2.3. 更新已有队列为 lazy 模式

对于已经存在的队列,也可以配置为 lazy 模式,但是要通过设置 policy 实现。
可以基于命令行设置 policy:

rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues  

命令解读:

  • rabbitmqctl :RabbitMQ 的命令行工具
  • set_policy :添加一个策略
  • Lazy :策略名称,可以自定义
  • "^lazy-queue$" :用正则表达式匹配队列的名字
  • '{"queue-mode":"lazy"}' :设置队列模式为 lazy 模式
  • --apply-to queues:策略的作用对象,是所有的队列

当然,也可以在控制台配置 policy,进入在控制台的 Admin 页面,点击 Policies,即可添加配置:
image.png

3、消费者

有了持久化机制后,那么怎么保证消息在持久化下来之后一定能被消费者消费呢?这里就涉及到消息的消费确认机制。
在 RabbitMQ 中,消费者处理消息成功后可以向 MQ 发送 ack 回执,MQ 收到 ack 回执后才会删除该消息,这样才能确保消息不会丢失。如果消费者在处理消息中出现了异常,那么就会返回 ack 回执,MQ 收到回执之后就会重新投递一次消息,如果消费者一直都没有返回 ACK/NACK 的话,那么他也会在尝试重新投递。

3.1、消费者确认机制

为了确认消费者是否成功处理消息,RabbitMQ 提供了消费者确认机制(Consumer Acknowledgement)。即:当消费者处理消息结束后,应该向 RabbitMQ 发送一个回执,告知 RabbitMQ 自己消息处理状态。回执有三种可选值:

  • ack:成功处理消息,RabbitMQ 从队列中删除该消息
  • nack:消息处理失败,RabbitMQ 需要再次投递消息
  • reject:消息处理失败并拒绝该消息,RabbitMQ 从队列中删除该消息

一般 reject 方式用的较少,除非是消息格式有问题,那就是开发问题了。因此大多数情况下我们需要将消息处理的代码通过 try catch 机制捕获,消息处理成功时返回 ack,处理失败时返回 nack.
由于消息回执的处理代码比较统一,因此 SpringAMQP 帮我们实现了消息确认。并允许我们通过配置文件设置 ACK 处理方式,有三种模式:

  • none:不处理。即消息投递给消费者后立刻 ack,消息会立刻从 MQ 删除。非常不安全,不建议使用
  • manual:手动模式。需要自己在业务代码中调用 api,发送 ackreject,存在业务入侵,但更灵活
  • auto:自动模式。SpringAMQP 利用 AOP 对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回 ack. 当业务出现异常时,根据异常判断返回不同结果:
    • 如果是业务异常,会自动返回 nack
    • 如果是消息处理或校验异常,自动返回 reject;
      通过下面的配置可以修改 SpringAMQP 的 ACK 处理方式:
spring:  rabbitmq:  listener:  simple:  acknowledge-mode: none # 不做处理

修改 consumer 服务的 SpringRabbitListener 类中的方法,模拟一个消息处理的异常:

@RabbitListener(queues = "simple.queue")  
public void listenSimpleQueueMessage(String msg) throws InterruptedException {  log.info("spring 消费者接收到消息:【" + msg + "】");  if (true) {  throw new MessageConversionException("故意的");  }  log.info("消息处理完成");  
}

测试可以发现:当消息处理发生异常时,消息依然被 RabbitMQ 删除了。
我们再次把确认机制修改为 auto:

spring:  rabbitmq:  listener:  simple:  acknowledge-mode: auto # 自动ack

在异常位置打断点,再次发送消息,程序卡在断点时,可以发现此时消息状态为 unacked(未确定状态): image.png 放行以后,由于抛出的是消息转换异常,因此 Spring 会自动返回 reject,所以消息依然会被删除: image.png
我们将异常改为 RuntimeException 类型:

@RabbitListener(queues = "simple.queue")  
public void listenSimpleQueueMessage(String msg) throws InterruptedException {  log.info("spring 消费者接收到消息:【" + msg + "】");  if (true) {  throw new RuntimeException("故意的");  }  log.info("消息处理完成");  
}

在异常位置打断点,然后再次发送消息测试,程序卡在断点时,可以发现此时消息状态为 unacked(未确定状态): image.png 放行以后,由于抛出的是业务异常,所以 Spring 返回 ack,最终消息恢复至 Ready 状态,并且没有被 RabbitMQ 删除: image.png 当我们把配置改为 auto 时,消息处理失败后,会回到 RabbitMQ,并重新投递到消费者。

3.2、失败重试机制

当消费者出现异常后,消息会不断 requeue(重入队)到队列,再重新发送给消费者。如果消费者再次执行依然出错,消息会再次 requeue 到队列,再次投递,直到消息处理成功为止。极端情况就是消费者一直无法执行成功,那么消息 requeue 就会无限循环,导致 mq 的消息处理飙升,带来不必要的压力: image.png

当然,上述极端情况发生的概率还是非常低的,不过不怕一万就怕万一。为了应对上述情况 Spring 又提供了消费者失败重试机制:在消费者出现异常时利用本地重试,而不是无限制的 requeue 到 mq 队列。

修改 consumer 服务的 application. yml 文件,添加内容:

spring:  rabbitmq:  listener:  simple:  retry:  enabled: true # 开启消费者失败重试  initial-interval: 1000ms # 初识的失败等待时长为1秒  multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval  max-attempts: 3 # 最大重试次数  stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false

重启 consumer 服务,重复之前的测试。可以发现:

  • 消费者在失败后消息没有重新回到 MQ 无限重新投递,而是在本地重试了3次
  • 本地重试 3 次以后,抛出了 AmqpRejectAndDontRequeueException 异常。查看 RabbitMQ 控制台,发现消息被删除了,说明最后 SpringAMQP 返回的是 reject

结论:

  • 开启本地重试时,消息处理过程中抛出异常,不会 requeue 到队列,而是在消费者本地重试
  • 重试达到最大次数后,Spring 会返回 reject,消息会被丢弃

3.3、失败处理策略

在之前的测试中,本地测试达到最大重试次数后,消息会被丢弃。这在某些对于消息可靠性要求较高的业务场景下,显然不太合适了。因此 Spring 允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由 MessageRecovery 接口来定义的,它有 3 个不同实现:

  • RejectAndDontRequeueRecoverer:重试耗尽后,直接 reject,丢弃消息。默认就是这种方式
  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回 nack,消息重新入队
  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

比较优雅的一种处理方案是 RepublishMessageRecoverer,失败后将消息投递到一个指定的,专门存放异常消息的队列,后续由人工集中处理。

1)在 consumer 服务中定义处理失败消息的交换机和队列


@Bean  
public DirectExchange errorMessageExchange(){  return new DirectExchange("error.direct");  
}  
@Bean  
public Queue errorQueue(){  return new Queue("error.queue", true);  
}  
@Bean  
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){  return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");  
}

2)定义一个 RepublishMessageRecoverer,关联队列和交换机

@Bean  
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){  return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");  
}

完整代码如下:


package com.itheima.consumer.config;  
​  
import org.springframework.amqp.core.Binding;  
import org.springframework.amqp.core.BindingBuilder;  
import org.springframework.amqp.core.DirectExchange;  
import org.springframework.amqp.core.Queue;  
import org.springframework.amqp.rabbit.core.RabbitTemplate;  
import org.springframework.amqp.rabbit.retry.MessageRecoverer;  
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;  
import org.springframework.context.annotation.Bean;  
​  
@Configuration  
@ConditionalOnProperty(name = "spring.rabbitmq.listener.simple.retry.enabled", havingValue = "true")  
public class ErrorMessageConfig {  @Bean  public DirectExchange errorMessageExchange(){  return new DirectExchange("error.direct");  }  @Bean  public Queue errorQueue(){  return new Queue("error.queue", true);  }  @Bean  public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){  return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");  }  
​  @Bean  public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){  return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");  }  
}

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

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

相关文章

Star-CCM+分配零部件至区域1-将所有零部件分配至区域

前言 Star-CCM中&#xff0c;在划分网格之前需要将零部件分配至区域&#xff0c;然后才可以划分网格。如下图1所示&#xff0c;分配零部件至区域需要选择创建区域的方式、创建边界的方式以及交界面的类型。 图1 将零部件分配至区域 1 创建区域的方式选择 如下图2所示&#x…

基于Spring Boot的家具网站设计与实现

基于Spring Boot的家具网站设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统前台主界面图&#xff0c;用户可进入家具网站可查看…

探索大模型能力--prompt工程

1 prompt工程是什么 1.1 什么是Prompt&#xff1f; LLM大语言模型终究也只是一个工具&#xff0c;我们不可能每个人都去训一个大模型&#xff0c;但是我们可以思考如何利用好大模型&#xff0c;让他提升我们的工作效率。就像计算器工具一样&#xff0c;要你算10的10倍&#x…

Java IO类之FilterOutputStream的研究与应用

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

RCE学习(一)

一.知识点 RCE&#xff1a;远程命令/代码执行漏洞&#xff0c;简称为RCE漏洞&#xff0c;可以直接向服务器后台远程注入操作系统的命令或者代码&#xff0c;从而拿到服务器后台的权限。RCE分为远程执行命令&#xff08;执行ping命令&#xff09;和远程代码执行eval 简单来说就…

VMware导入ova/ovf虚拟机文件

1.文件-打开-ova文件 2.为新虚拟机起名称 3.等待导入 4.导入完成&#xff0c;可以开始使用 参考链接&#xff1a;VMware导入ova/ovf虚拟机文件

浅谈Session和Cookie

各位大佬光临寒舍&#xff0c;希望各位能赏脸给个三连&#xff0c;谢谢各位大佬了&#xff01;&#xff01;&#xff01; 目录 1.Cookie 2.Sesssion&#xff08;会话&#xff09; 3.Session和Cookie的联系 4.总结 1.Cookie Cookie是客户端存储数据的机制&#xff0c;一般是…

英语学习笔记5——Nice to meet you.

Nice to meet you. 很高兴见到你。 词汇 Vocabulary Mr. 先生 用法&#xff1a;自己全名 / 姓 例如&#xff1a;Mr. Zhang Mingdong 或 Mr. Zhang&#xff0c;绝对不能是 Mr. Mingdong&#xff01; Miss 女士&#xff0c;小姐 未婚 用法&#xff1a;自己全名 / 姓 例如&#…

【ROMA核心特性数据、服务、消息、设备集成及统一运维】

1、数据集成 FDI旨在解决多种数据源的快速灵活集成能力&#xff0c;您可以在任意时间、任意地点、任意系统之间实现实时数据订阅和定时增量数据迁移。 &#xff08;1&#xff09;数据集成任务的生命周期管理 &#xff08;2&#xff09;FDI支持修改数据集成任务的信息、查看数…

初识Node.js-认识node(安装Node.js环境配置)

目录 一、node介绍 1.概念和特点 2.核心功能 3.应用场景 二、Node初使用 1.安装node配置 windows上安Node.js 1.windows安装包&#xff08;.msi&#xff09; 2、Windows 二进制文件 (.exe)安装 Linux 上安装 Node.js 直接使用已编译好的包 Ubuntu 源码安装 Node.js …

【JVM】垃圾回收机制(Garbage Collection)

目录 一、什么是垃圾回收&#xff1f; 二、为什么要有垃圾回收机制&#xff08;GC&#xff09;&#xff1f; 三、垃圾回收主要回收的内存区域 四、死亡对象的判断算法 a&#xff09;引用计数算法 b&#xff09;可达性分析算法 五、垃圾回收算法 a&#xff09;标记-清除…

GDPU 天码行空11

&#xff08;一&#xff09;实验目的 1、掌握JAVA中IO中各种类及其构造方法&#xff1b; 2、重点掌握IO中类所具有的IO操作方法&#xff1b; 3、熟悉软件中登录模块的开发方法&#xff1b; 4、掌握IO中读写常用方法。 5、进一步熟悉正则规则的使用方法。 &#xff08;二&…

ue引擎游戏开发笔记(32)——为游戏添加新武器装备

1.需求分析&#xff1a; 游戏中角色不会只有一种武器&#xff0c;不同武器需要不同模型&#xff0c;甚至可能需要角色持握武器的不同位置&#xff0c;因此需要添加专门的武器类&#xff0c;方便武器后续更新&#xff0c;建立一个武器类。 2.操作实现&#xff1a; 1.在ue5中新建…

如何用DeepDiff测接口数据源变更?

开发同学最近变更了部分业务查询接口底层的数据源&#xff0c;希望测试同学能够针对这些接口进行一些回归验证&#xff0c;校验底层数据源更新前后业务查询接口返回的一致性&#xff0c;保证更新后对正常业务没有影响。 这个回归测试和一般接口测试有所区别&#xff0c;不仅仅…

使用Simulink Test进行单元测试

本文摘要&#xff1a;主要介绍如何利用Simulink Test工具箱&#xff0c;对模型进行单元测试。内容包括&#xff0c;如何创建Test Harness模型&#xff0c;如何自动生成excel格式的测试用例模板来创建测试用例&#xff0c;如何手动填写excel格式的测试用例模板来手动创建测试用例…

Redis---------实现查询缓存业务

目录 数据库与缓存之间的工作业务逻辑&#xff1a; 接下来看查询缓存代码实现&#xff0c;主要是捋清楚业务逻辑&#xff0c;代码实现是死的&#xff1a; Controller: Service: P37作业实现&#xff1a;总体逻辑跟上面的业务逻辑差不多 Controller&#xff1a; Service&#…

Azure AKS日志查询KQL表达式

背景需求 Azure&#xff08;Global&#xff09; AKS集群中&#xff0c;需要查询部署服务的历史日志&#xff0c;例如&#xff1a;我部署了服务A&#xff0c;但服务A的上一个版本Pod已经被杀掉由于版本的更新迭代&#xff0c;而我在命令行中只能看到当前版本的pod日志&#xff…

SparkSql介绍

概述 SparkSQL&#xff0c;顾名思义&#xff0c;就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL&#xff0c;而叫Shark&#xff0c;最开始的时候底层代码优化&#xff0c;sql的解析、执行引擎等等完全基于Hive&#xff0c…

科创板门槛升级!解析中国量子企业的上市之路与国际比拼

4月30日晚&#xff0c;中国证监会于发布了修订后的《科创属性评价指引&#xff08;试行&#xff09;》&#xff08;以下简称“新指引”&#xff09;&#xff0c;该指引自发布日起正式生效。本次修订对原有指引中的部分标准进行了调整&#xff0c;具体如下&#xff1a; 1&#x…

如何用Kimi,5秒1步生成流程图

引言 在当前快节奏的工作环境中&#xff0c;拥有快速、专业且高效的工具不可或缺。 Kimi不仅能在5秒内生成专业的流程图&#xff08;kimi&#xff09;&#xff0c;还允许实时编辑和预览&#xff0c;大幅简化了传统流程图的制作过程。 这种迅速的生成能力和高度的可定制性使得…