如何保证 RabbitMQ 的消息可靠性?
项目开发中经常会使用消息队列来完成异步处理、应用解耦、流量控制等功能。虽然消息队列的出现解决了一些场景下的问题,但是同时也引出了一些问题,其中使用消息队列时如何保证消息的可靠性就是一个常见的问题。如果在项目中遇到需要保证消息一定被消费的场景时,如何保证消息不丢失,如何保证消息的可靠性?
先放一张 RabbitMQ 是如何消息传递的图:
生产者Producer 将消息发送到指定的 交换机Exchange,交换机根据路由规则路由到绑定的 队列Queue 中,最后和消费者建立连接后,将消息推送给 消费者Consumer。
那么消息会在哪些环节丢失呢,列出可能出现消息丢失的场景有:
生产者将消息发送到 RabbitMQ Server 异常: 可能因为网络问题造成 RabbitMQ 服务端无法收到消息,造成生产者发送消息丢失场景。
RabbitMQ Server 中消息在交换机中无法路由到指定队列: 可能由于代码层面或配置层面错误导致消息路由到指定队列失败,造成生产者发送消息丢失场景。
RabbitMQ Server 中存储的消息丢失:可能因为 RabbitMQ Server 宕机导致消息未完全持久化或队列丢失导致消息丢失等持久化问题,造成 RabbitMQ Server 存储的消息丢失场景。
消费者消费消息异常: 可能在消费者接收到消息后,还没来得及消费消息,消费者宕机或故障等问题,造成消费者无法消费消息导致消息丢失的场景。
以上就是 RabbitMQ 可能出现消息丢失的场景,接下来将依次讲解如何避免这些消息丢失的场景问题。
1. 保证生产者发送消息到 RabbitMQ Server
为了避免因为网络故障或闪断问题导致消息无法正常发送到 RabbitMQ Server 的情况,RabbitMQ 提供了两种方案让生产者可以感知到消息是否正确无误的发送到 RabbitMQ Server中,这两种方案分别是 事务机制 和 发送方确认机制。下面分别介绍一下这两种机制如何实现。
事务机制
先说配置和使用:
1.配置类中配置事务管理器
/*** 消息队列配置类** @author 单程车票*/
@Configuration
public class RabbitMQConfig {/*** 配置事务管理器*/@Beanpublic RabbitTransactionManager transactionManager(ConnectionFactory connectionFactory) {return new RabbitTransactionManager(connectionFactory);}
}
2.通过添加事务注解 + 开启事务实现事务机制
/*** 消息业务实现类** @author 单程车票*/
@Service
public class RabbitMQServiceImpl {@Autowiredprivate RabbitTemplate rabbitTemplate;@Transactional // 事务注解public void sendMessage() {// 开启事务rabbitTemplate.setChannelTransacted(true);// 发送消息rabbitTemplate.convertAndSend(RabbitMQConfig.Direct_Exchange, routingKey, message);}
}
通过上面的配置即可实现事务机制,执行流程为:在生产者发送消息之前,开启事务,而后发送消息,如果消息发送至 RabbitMQ Server 失败后,进行事务回滚,重新发送。如果 RabbitMQ Server 接收到消息,则提交事务。
可以发现事务机制其实是同步操作,存在阻塞生产者的情况直到 RabbitMQ Server 应答,这样其实会很大程度上降低发送消息的性能,所以一般不会使用事务机制来保证生产者的消息可靠性,而是使用发送方确认机制。
发送方确认机制
先说配置和使用:
配置文件
spring:rabbitmq:publisher-confirm-type: correlated # 开启发送方确认机制
配置属性有三种分别为:
这里一般使用 correlated 开启发送方确认机制即可,至于 simple 的 waitForConfirms() 方法调用是指串行确认方法,即生产者发送消息后,调用该方法等待 RabbitMQ Server 确认,如果返回 false 或超时未返回则进行消息重传。由于串行性能较差,这里一般都是用异步
confirm 模式。
none:表示禁用发送方确认机制
correlated:表示开启发送方确认机制
simple:表示开启发送方确认机制,并支持 waitForConfirms() 和 waitForConfirmsOrDie() 的调用。
通过调用 setConfirmCallback() 实现异步 confirm 模式感知消息发送结果
/*** 消息业务实现类** @author 单程车票*/
@Service
public class RabbitMQServiceImpl {@Autowiredprivate RabbitTemplate rabbitTemplate;@Overridepublic void sendMessage() {// 发送消息rabbitTemplate.convertAndSend(RabbitMQConfig.Direct_Exchange, routingKey, message);// 设置消息确认回调方法rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/*** MQ确认回调方法* @param correlationData 消息的唯一标识* @param ack 消息是否成功收到* @param cause 失败原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {// 记录日志log.info("ConfirmCallback...correlationData["+correlationData+"]==>ack:["+ack+"]==>cause:["+cause+"]");if (!ack) {// 出错处理...}}});}
}
生产者发送消息后通过调用 setConfirmCallback() 可以将信道设置为 confirm 模式,所有消息会被指派一个消息唯一标识,当消息被发送到 RabbitMQ Server 后,Server 确认消息后生产者会回调设置的方法,从而实现生产者可以感知到消息是否正确无误的投递,从而实现发送方确认机制。并且该模式是异步的,发送消息的吞吐量会得到很大提升。
上面就是发送放确认机制的配置和使用,使用这种机制可以保证生产者的消息可靠性投递,并且性能较好。
2. 保证消息能从交换机路由到指定队列
在确保生产者能将消息投递到交换机的前提下,RabbitMQ 同样提供了消息投递失败的策略配置来确保消息的可靠性,接下来通过配置来介绍一下消息投递失败的策略。
先说配置:
spring:rabbitmq:publisher-confirm-type: correlated # 开启发送方确认机制publisher-returns: true # 开启消息返回template:mandatory: true # 消息投递失败返回客户端
mandatory 分为 true 失败后返回客户端 和 false 失败后自动删除两种策略。显然设置为 false 无法保证消息的可靠性。
到这里的配置是可以保证生产者发送消息的可靠性投递。
通过调用 setReturnCallback() 方法设置路由失败后的回调方法:
/*** 消息业务实现类** @author 单程车票*/
@Service
public class RabbitMQServiceImpl {@Autowiredprivate RabbitTemplate rabbitTemplate;@Overridepublic void sendMessage() {// 发送消息rabbitTemplate.convertAndSend(RabbitMQConfig.Direct_Exchange, routingKey, message);// 设置消息确认回调方法rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/*** MQ确认回调方法* @param correlationData 消息的唯一标识* @param ack 消息是否成功收到* @param cause 失败原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {// 记录日志log.info("ConfirmCallback...correlationData["+correlationData+"]==>ack:["+ack+"]==>cause:["+cause+"]");if (!ack) {// 出错处理...}}});// 设置路由失败回调方法rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {/*** MQ没有将消息投递给指定的队列回调方法* @param message 投递失败的消息详细信息* @param replyCode 回复的状态码* @param replyText 回复的文本内容* @param exchange 消息发给哪个交换机* @param routingKey 消息用哪个路邮键*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {// 记录日志log.info("Fail Message["+message+"]==>replyCode["+replyCode+"]" +"==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");// 出错处理...}});}
}
通过调用 setReturnCallback() 方法即可实现当交换机路由到指定队列失败后回调方法,拿到被退回的消息信息,进行相应的处理如记录日志或重传等等。
3. 保证消息在 RabbitMQ Server 中的持久化
对于消息的持久化,只需要在发送消息时将消息持久化,并且在创建交换机和队列时也保证持久化即可。
配置如下:
/*** 消息队列*/
@Bean
public Queue queue() {// 四个参数:name(队列名)、durable(持久化)、 exclusive(独占)、autoDelete(自动删除)return new Queue(MESSAGE_QUEUE, true);
}/*** 直接交换机*/
@Bean
public DirectExchange exchange() {// 四个参数:name(交换机名)、durable(持久化)、autoDelete(自动删除)、arguments(额外参数)return new DirectExchange(Direct_Exchange, true, false);
}
在创建交换机和队列时通过构造方法将持久化的参数都设置为 true 即可实现交换机和队列的持久化。
@Override
public void sendMessage() {// 构造消息(将消息持久化)Message message = MessageBuilder.withBody("单程车票".getBytes(StandardCharsets.UTF_8)).setDeliveryMode(MessageDeliveryMode.PERSISTENT).build();// 向MQ发送消息(消息内容都为消息表记录的id)rabbitTemplate.convertAndSend(RabbitMQConfig.Direct_Exchange, routingKey, message);
}
在发送消息前通过调用 MessageBuilder 的 setDeliveryMode(MessageDeliveryMode.PERSISTENT) 在构造消息时设置消息持久化(MessageDeliveryMode.PERSISTENT)即可实现对消息的持久化。
通过确保消息、交换机、队列的持久化操作可以保证消息的在 RabbitMQ Server 中不丢失,从而保证可靠性,其实除了持久化之外还需要保证 RabbitMQ 的高可用性,否则 MQ 都宕机或磁盘受损都无法确保消息的可靠性,关于高可用性这里就不作过多说明,有兴趣的可以去了解一下。
4. 保证消费者消费的消息不丢失
在保证发送方和 RabbitMQ Server 的消息可靠性的前提下,只需要保证消费者在消费消息时异常消息不丢失即可保证消息的可靠性。
RabbitMQ 提供了 消费者应答机制 来使 RabbitMQ 能够感知到消费者是否消费成功消息,默认情况下,消费者应答机制是自动应答的,也就是RabbitMQ 将消息推送给消费者,便会从队列删除该消息,如果消费者在消费过程失败时,消息就存在丢失的情况。所以需要将消费者应答机制设置为手动应答,只有消费者确认消费成功后才会删除消息,从而避免消息的丢失。
下面来看看如何配置消费者手动应答:
spring:rabbitmq:publisher-confirm-type: correlated # 开启发送方确认机制publisher-returns: true # 开启消息返回template:mandatory: true # 消息投递失败返回客户端listener:simple:acknowledge-mode: manual # 开启手动确认消费机制
通过 listener.simple.acknowledge-mode = manual 即可将消费者应答机制设置为手动应答。
之后只需要在消费消息时,通过调用 channel.basicAck() 与 channel.basicNack() 来根据业务的执行成功选择是手动确认消费还是手动丢弃消息。
/*** 监听消费队列的消息*/
@RabbitListener(queues = RabbitMQConfig.MESSAGE_QUEUE)
public void onMessage(Message message, Channel channel) {// 获取消息索引long index = message.getMessageProperties().getDeliveryTag();// 解析消息byte[] body = message.getBody();...try {// 业务处理...// 业务执行成功则手动确认channel.basicAck(index, false);}catch (Exception e) {// 记录日志log.info("出现异常:{}", e.getMessage());try {// 手动丢弃信息channel.basicNack(index, false, false);} catch (IOException ex) {log.info("丢弃消息异常");}}
}
这里说明一下 basicAck() 与 basicNack() 的参数说明:
void basicAck(long deliveryTag, boolean multiple) 方法(会抛异常):deliveryTag:该消息的indexmultiple:是否批量处理(true 表示将一次性ack所有小于deliveryTag的消息)void basicNack(long deliveryTag, boolean multiple, boolean requeue) 方法(会抛异常):deliveryTag:该消息的indexmultiple:是否批量处理(true 表示将一次性ack所有小于deliveryTag的消息)requeue:被拒绝的是否重新入队列(true 表示添加在队列的末端;false 表示丢弃)
通过设置手动确认消费者应答机制即可保证消费者在消费信息时的消息可靠性。
Spring Boot 提供的消息重试机制
除了消费者应答机制外,Spring Boot也提供了一种重试机制,只需要通过配置即可实现消息重试从而确保消息的可靠性,这里简单介绍一下:
spring:rabbitmq:listener:simple:acknowledge-mode: auto # 开启自动确认消费机制retry:enabled: true # 开启消费者失败重试initial-interval: 5000ms # 初始失败等待时长为5秒multiplier: 1 # 失败的等待时长倍数(下次等待时长 = multiplier * 上次等待时间)max-attempts: 3 # 最大重试次数stateless: true # true无状态;false有状态(如果业务中包含事务,这里改为false)
通过配置在消费者的方法上如果执行失败或执行异常只需要抛出异常(一定要出现异常才会触发重试,注意:不要捕获异常) 即可实现消息重试,这样也可以保证消息的可靠性。
上面就是我在项目中关于如何保证 RabbitMQ 的消息可靠性的配置和实现方案了。下面想聊聊我在实际使用消息队列实现消息可靠性时遇到的问题。
消费者消费消息需要保证幂等性
由于实现了消息可靠性导致消息重发或消息重试造成消费者可能会存在消息被重复消费的情况,这种情况就需要保证消息不被重复消费,也就是消息保证幂等性。
实现幂等性的方法有很多:借助数据库的乐观锁或悲观锁、借助 redis 的分布式锁、借助 redis 实现 token 机制等等都可以很好的保证消息的幂等性。
使用消息队列很难做到 100% 的消息可靠性
我在项目实际开发中使用 RabbitMQ 实现消息可靠性,实践后的感受是消息队列很难能做到 100% 的消息可靠性,上面的实现方案中 RabbitMQ 提供的机制做到的是尽可能地减小消息丢失的几率。
大多数情况下消息丢失都是因为代码出现错误,那么这样无论进行多少次重发都是无法解决问题的,这样只会增加 CPU 的开销,所以我认为更好的解决办法是通过记录日志的方式等待后续回溯时更好的发现问题并解决问题。对于一些不是很需要保证百分百可靠性的场景,都可以通过记录日志的方式来保证消息可靠性即可。
我在项目中采用的是消息落库的方式,先将消息落库,而后生产者将消息发送给 MQ,使用数据库记录消息的消费情况,对于重试多次仍然无法消费成功的消息,后续通过定时任务调度的方式对这些无法消费成功的消息进行补偿。我认为这样可以尽可能地保证消息的可靠性。但是同样这样也带来了问题就是消息落库需要数据库磁盘IO的开销,增大数据库压力同时降低了性能。
总之,在实现消息的可靠性时,应该根据项目的需求来考虑如何处理。对于消息要求可靠性低的只需要在出错时记录日志方便后续回溯解决出错问题即可,对于消息可靠性要求高的则可以采用消息落库 + 定时任务的方式尽可能保证百分百的可靠性。
相关文章:

如何保证 RabbitMQ 的消息可靠性?
项目开发中经常会使用消息队列来完成异步处理、应用解耦、流量控制等功能。虽然消息队列的出现解决了一些场景下的问题,但是同时也引出了一些问题,其中使用消息队列时如何保证消息的可靠性就是一个常见的问题。如果在项目中遇到需要保证消息一定被消费的…...
图像分类数据集划分(创建ImageNet格式的数据集)
原始数据文件夹如下: ├──data├── 0 类别1├── 1 类别2制作数据集格式如下所示: ├──datasets├── meta│ ├── test.txt # 测试数据集的标注文件│ ├── train.txt # 训练数据集的标注文件│ └── val.txt # 验证…...

ArcGIS Engine:报错无法嵌入互操作类型“ESRI.ArcGIS.Geometry.EnvelopeClass”。请改用适用的接口。
此错误是由于尝试直接实例化ArcGIS COM组件的某些互操作类引起的。这在.NET Framework 4及更高版本中是不被推荐的。 为了解决此问题,你需要确保在工程的引用中将ArcGIS的互操作类型设置为“不嵌入”。 按照以下步骤操作: 在解决方案资源管理器中找到…...

核货宝:服装店收银系统如何选择?收银系统选购指南!
对于各行各业而言,收银系统都是必备的工具。特别是对于像服装店这样的零售门店来说,选择一套适合的收银系统尤为重要。在选择收银系统时,有一些关键的技巧需要注意,以达到软硬件合理搭配、节省开支的目的。下面将分享四个选购服装…...
GB/T 7134-2008 浇筑型工业有机玻璃板材检测
非改性浇筑PMMA板材是指甲基丙烯酸甲酯均聚物板材,或者甲基丙烯酸甲酯与丙烯酸酯类或甲基丙烯酸酯类单体的共聚物板材,通过适当的引发剂本体聚合生产。 GB/T 7134-2008浇筑型工业有机玻璃板材测试项目: 测试项目 测试方法 拉伸强度 GB/T …...
数据采集平台(二)
5. 安装Kafka 5.1 基础架构 为方便扩展,并提高吞吐量,一个topic分为多个partition配合分区的设计,提出消费者组的概念,组内每个消费者并行消费为提高可用性,为每个partition增加若干副本,类似NameNode HA…...

Nginx + PHP 异常排查,open_basedir 异常处理
新上一个网站,通过域名访问失败,排查方法如下: 开启异常日志 开启域名下,nginx的异常日志,并查看日志 tail -f /var/log/nginx/nginx.localhost.error.log开启php的异常日志,该配置位于php.ini文件下 …...

Linux免密登录
目标: 192.168.233.31 ton-pc-003 192.168.233.32 ton-pc-004 在ton-pc-004(以下简称004)免密登录ton-pc-003(以下简称003) 具体流程和说明: 1、在004(客户机)中生成秘钥对 ssh…...
迷宫 蓝桥杯
问题描述 这天, 小明在玩迷宫游戏。 迷宫为一个 nn 的网格图, 小明可以在格子中移动, 左上角为 (1,1), 右下角 (n, n)为终点。迷宫中除了可以向上下左右四个方向移动一格以外, 还有 m 个双向传送门可以使用, 传送门可以连接两个任意格子。 假如小明处在格子(x1,y1), 同时有…...

25 mysql like 是否使用索引
前言 这里主要是 探究一下 explain $sql 中各个 type 诸如 const, ref, range, index, all 的查询的影响, 以及一个初步的效率的判断 这里会调试源码来看一下 各个类型的查询 需要 lookUp 的记录 以及 相关的差异 此系列文章建议从 mysql const 查询 开始看 测试表结构…...

Android---Class 对象在执行引擎中的初始化过程
一个 class 文件被加载到内存中的步骤如下图所示: 装载 装载是指 Java 虚拟机查找 .class 文件并生成字节流,然后根据字节流创建 java.lang.Class 对象的过程。 1. ClassLoader 通过一个类的全限定名(包名类名)来查找 .class 文件…...

Altium Designer实用系列(二)----PCB绘图小技巧
一、技巧总结 1.1 丝印大小 在导入PCB之后,元器件的丝印一般都是strock font,个人感觉比较大,也不美观,但是一个个修改成true type又比较麻烦。简便方法是使用相似查找全部修改: 此时会选中所有stroke 类型的丝印ÿ…...

threejs-开发入门与调试设置
近年来web得到了快速的发展。随着HTML5的普及,网页的表现能力越来越强大。网页上已经可以做出很多复杂的动画,精美的效果。还能通过WebGL在网页中绘制高性能的3D图形。 学习资料来源:https://www.three3d.cn/threejs/01-%E5%BC%80%E5%8F%91%E…...

win11安装双系统Ubuntu的坎坷记录
之前一直装的都是在一个硬盘中,这是是两块盘。 我的电脑是惠普暗影精灵8Pro 一 安装前的准备工作 1.1 记得先关闭,Bitlocker 输入wins,搜索框输入:设备加密设置 1.2 BIOS设置 (惠普这电脑是开机时按 F10࿰…...
关于docker的xuexi
概念了解 1.镜像: 类似于类与实例关系中的类,也类似于系统镜像的概念,对于前端而言,镜像就是包含了代码运行所需要的一切产物、依赖、配置等。这样的话,可以保证每次程序运行的环境一致。构建镜像,一般都…...

Python接口自动化测试实战详解,你想要的全都有
前言 接口自动化测试是当前软件开发中最重要的环节之一,可以提高代码质量、加速开发周期、减少手工测试成本等优点。Python语言在接口自动化测试方面应用广泛,因为它具有简单易学、开发效率高、库丰富等特点。 一、接口自动化测试概述 接口自动化测试…...
SparkSQL 外部数据源
1.简介 1.1 多数据源支持 Spark 支持以下六个核心数据源,同时 Spark 社区还提供了多达上百种数据源的读取方式,能够满足绝大部分使用场景。 - CSV - JSON - Parquet - ORC - JDBC/ODBC connections - Plain-text files 1.2 读数据格式 所有读取 API 遵循以下调用格式: // …...
leetcode做题笔记167. 两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 < index1 < index2 < numbers…...

[ZJCTF 2019]NiZhuanSiWei - 伪协议+文件包含+反序列化
[ZJCTF 2019]NiZhuanSiWei 1 解题流程1.1 分析1.2 解题 题目源码: <?php $text $_GET["text"]; $file $_GET["file"]; $password $_GET["password"]; if(isset($text)&&(file_get_contents($text,r)"welcome t…...

如何提升和扩展 PostgreSQL — 从共享缓冲区到内存数据网格
利用共享缓存和操作系统缓存利用 RAM Postgres 是一个基于磁盘的数据库,即使您的整个架构是围绕磁盘访问设计的,利用 RAM 也很重要。如果按照人类规模的延迟来判断,这可以将延迟从几天缩短到几分钟(图 1)。只需看一下…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...