【MQ篇】RabbitMQ之消费失败重试!
目录
- 引言:消息不丢是底线,失败了优雅重试是修养!
- 消费失败了,为啥不能老是原地复活?🤔
- 智能重试策略一:本地重试(Spring Retry 的魔法)🏠✨
- 智能重试策略二:失败策略 - 重试耗尽后“送去劳改”还是“发配边疆”?📚👨🔧
- 策略三:经纪人端延迟重试(DLX + TTL 深度剖析)📦⏳📬
- 总结:如何构建可靠的消息消费策略?✨🛡️
🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解 MQ 请看 : 【MQ篇】初识MQ!
其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等
如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning
引言:消息不丢是底线,失败了优雅重试是修养!
各位技术界的同仁们,大家好!👋 咱们之前聊了 RabbitMQ 消息不丢的“三板斧”(生产者确认、持久化、消费者确认),构建了一个消息安全的“铜墙铁壁”。但是,消息安全送达了,不代表它就能被“快乐地”消费掉!有时候,消费者在处理消息时会遇到各种“糟心事儿”——数据库崩了、下游服务挂了、消息格式错了… 一言不合就抛异常!⛈️
了解RabbitMQ消息不丢的“三板斧”请看:
【MQ篇】RabbitMQ的生产者消息确认实战!
【MQ篇】RabbitMQ之消息持久化!
【MQ篇】RabbitMQ的消费者确认机制实战!
这时候问题就来了:如果消费者处理失败了,这条消息该咋办?直接丢了?那业务就断了!重新发给它?如果消费者一直“病着”,或者消息本身就有问题,那就会出现很多可怕场景:
瞧瞧这图,消息在队列里和消费者之间“魔鬼”般地反复横跳,处理次数飙升,MQ 压力山大,系统岌岌可危!😱 这就是简单地把失败消息 requeue=true
(重新入队)可能带来的灾难——无限循环重试,尤其对那种永远无法处理成功的 “毒药消息”(Poison Message) ☠️✉️ 来说,简直是噩梦!
所以,今天咱们就来聊聊,如何优雅地处理消费失败,搭建一套智能的消费失败重试机制,让消息处理变得更健壮!
消费失败了,为啥不能老是原地复活?🤔
简单地 requeue
会导致无限循环。这就像一个得了感冒的快递员(消费者)反复去送一个“烫手”的包裹(有问题的消息)。每次去都被烫一下,回来,再被派去,再被烫… 结果就是快递员累垮了(消费者资源耗尽或阻塞),包裹也没送出去,其他等着送的包裹也耽误了。
咱们需要根据失败的原因和预期,采取不同的重试策略:
- 临时性错误 (Transient Errors): 比如网络短暂抖动、数据库连接瞬断。这种错误等一会儿可能就恢复了,适合快速重试。
- 永久性错误 (Permanent Errors): 比如消息格式错误、业务数据非法。这种错误无论重试多少次都不会成功。
- 外部依赖错误: 比如调用第三方服务失败。这种错误可能需要等待较长时间让依赖恢复。
所以,咱们需要更精细的控制手段!
智能重试策略一:本地重试(Spring Retry 的魔法)🏠✨
“本地重试”顾名思义,就是消费者收到消息后,在把处理结果告诉 RabbitMQ 之前,先在自己家里(应用程序内部)多努力几次。💪 就像快递员拿到包裹后,发现地址有点模糊,他不会立刻把包裹退回邮局,而是在附近多转几圈,多问问人,尝试自己解决。
Spring Boot 集成了强大的 Spring Retry 框架,让实现本地重试变得异常简单!你只需要在配置文件里动动手指头:
在 application.yml
中为 Simple 监听容器开启 Retry:
# application.yml
spring:rabbitmq:listener:simple: # 或者 container type: direct 等,取决于你用的容器类型retry:enabled: true # ⭐ 开启消费者失败重试!魔法生效!🧙♂️initial-interval: 1000ms # ⭐ 初次失败后等待 1 秒重试multiplier: 2 # ⭐ 下次等待时长 = multiplier * 上次等待时长。这是指数退避!⏳max-attempts: 3 # ⭐ 最多重试 3 次 (包括第一次处理失败)stateless: true # ⭐ 无状态重试。通常用这个,除非涉及到跨方法调用的复杂事务
这配置简直是傻瓜式!😅
enabled: true
:一键开启重试功能!initial-interval
&multiplier
:配置重试的等待时间策略。上面的例子就是经典的指数退避:第一次失败等 1 秒,第二次等1 * 2 = 2
秒,第三次等2 * 2 = 4
秒…(直到max-attempts
或达到 Spring Retry 默认的最大等待时间)。这样可以避免失败后立即重试给依赖服务造成过大压力。你也可以设置multiplier: 1
实现固定间隔重试。max-attempts
:设置最大重试次数(注意,这个次数是总尝试次数,包括第一次失败)。比如设为 3,意味着“1次初次尝试 + 最多 2 次重试”。
本地重试的结果:
- 如果在
max-attempts
内,消费者成功处理了消息:Spring Retry 拦截器会认为处理成功,消息监听容器最终会向 RabbitMQ 发送 ACK,消息从队列中删除。🥳 - 如果在
max-attempts
后,消费者依然失败:Spring Retry 拦截器会放弃重试,并向上抛出一个AmqpRejectAndDontRequeueException
异常(或者其他表示重试耗尽的异常)。这时候,消息监听容器的错误处理器就会介入,根据默认策略或者你的自定义配置来处理这条消息。在默认情况下,消息会被丢弃。这是因为 Spring AMQP 默认的错误处理器RejectAndDontRequeueRecoverer
会对异常消息执行 Reject 并设置requeue=false
,通常这会导致消息被 RabbitMQ 丢弃(如果没有配置 DLX)。
本地重试的优缺点:
- 优点: 配置简单,对于瞬时错误处理效率高,不增加 RabbitMQ broker 的负担。
- 缺点: 重试期间阻塞消费者线程 ⏸️;如果消费者应用在重试过程中崩溃,正在重试的消息会丢失 👻;不适合长时间等待的场景。
智能重试策略二:失败策略 - 重试耗尽后“送去劳改”还是“发配边疆”?📚👨🔧
本地重试次数用完了,消息还是处理不了,怎么办?难道就这么丢弃了?对于重要消息来说,这绝对不能接受!😠 这时候就需要一个“善后”策略,或者叫“消息恢复”策略,由 Spring AMQP 的 MessageRecoverer
接口来定义。
Spring AMQP 提供的几种 MessageRecoverer
实现:
RejectAndDontRequeueRecoverer
:默认选项。 重试耗尽后,直接对消息执行 reject 并requeue=false
。如前所述,这通常意味着消息被 RabbitMQ 丢弃(如果没有 DLX)。简单粗暴,但可能丢消息。🚮ImmediateRequeueMessageRecoverer
:重试耗尽后,发送 NACK 并requeue=true
。请注意! 这又回到了最开始的问题,可能导致无限重试!😨 除了非常特殊的场景,慎用!🔄RepublishMessageRecoverer
:推荐的优雅方案。 重试耗尽后,将失败消息重新发布到指定的交换机!🚀 这就像把那些怎么都送不到收件人手里的疑难包裹,统一退回到一个“问题包裹处理中心”。
首先,定义一个专门处理错误消息的交换机和队列(通常用 Direct 交换机,绑定一个队列):
// RabbitConfig.java 或单独的 ErrorMessageConfig.java 文件
@Bean
public DirectExchange errorMessageExchange(){System.out.println("🛠️ 定义错误消息交换机: error.direct");return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){System.out.println("🛠️ 定义错误消息队列: error.queue");return new Queue("error.queue", true); // 队列通常要持久化 ✅
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){System.out.println("🛠️ 绑定错误队列到错误交换机,路由键为 'error'");return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error"); //
}
然后,定义一个 RepublishMessageRecoverer
Bean,告诉它把失败消息发到哪个交换机和使用哪个路由键:
// RabbitConfig.java 或单独的 ErrorMessageConfig.java 文件
import org.springframework.amqp.rabbit.retry.MessageRecoverer; //
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer; //
import org.springframework.amqp.rabbit.core.RabbitTemplate; //@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){System.out.println("🛠️ 配置 RepublishMessageRecoverer,将失败消息发送到 error.direct 交换机,路由键 'error'");// 参数1: rabbitTemplate 用于发送消息// 参数2: 目标交换机名称// 参数3: 目标路由键return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error"); //
}
最后,你需要将这个 republishMessageRecoverer
Bean 配置到你的消费者容器中,通常是通过设置 SimpleRabbitListenerContainerFactory
的 setMessageRecoverer
方法,或者像 Spring Boot 默认配置那样,只要容器中有 MessageRecoverer
类型的 Bean,它会自动关联上去。
// 在你的 SimpleRabbitListenerContainerFactory 配置中(如果有的话)
// factory.setMessageRecoverer(republishMessageRecoverer);// 或者如果你没有自定义 SimpleRabbitListenerContainerFactory,并且 republishMessageRecoverer Bean 在 Spring Context 中
// Spring Boot 的 auto-configuration 会尝试自动关联 MessageRecoverer Bean
通过这种方式,那些经过本地重试依然失败的消息,就不会被简单丢弃或无限循环,而是被整整齐齐地发送到 error.queue
里。你可以有专门的服务去监听这个队列,对这些异常消息进行统一分析、人工处理或报警,极大地提高了系统的可运维性!报警!🚨🚨🚨
策略三:经纪人端延迟重试(DLX + TTL 深度剖析)📦⏳📬
咱们之前说了,本地重试是在消费者内部挣扎,而经纪人端重试是把消息“踢”回给 RabbitMQ,让它帮忙等待一段时间再派送。DLX + TTL 就是实现这种“踢回去,等会儿再发”逻辑的“官方推荐”组合拳!👊💥
想象一下,你的消息处理失败了,就像一个包裹送到了,但收件人现在不在家。快递员(消费者)不能一直拿着包裹(阻塞),也不能直接丢弃(丢消息)。他最好的办法是把包裹带回快递站,告诉调度中心:“这个包裹现在送不了,麻烦你过 N 分钟再重新派送一次吧!” DLX + TTL 在 RabbitMQ 中扮演的就是“调度中心”和“临时存放区”的角色。
核心思想:利用“死信”和“过期”的特性,让消息“曲线救国”回到原队列!
要玩转 DLX + TTL,我们需要在 RabbitMQ 里配置几个关键的“地点”和“规则”:
-
业务队列 (Original Queue): 这是你的消费者正常监听的队列。当消费者处理失败并决定进行延迟重试时,它会把消息变成“死信”,然后这个业务队列要被配置成,把它的死信发送到一个指定的 DLX。
- 关键配置: 在队列的
arguments
里设置x-dead-letter-exchange
,指向你的死信交换机。
- 关键配置: 在队列的
-
死信交换机 (Dead Letter Exchange - DLX): 这是一个普通的交换机,但它很特殊,因为它专门接收来自那些配置了
x-dead-letter-exchange
的队列的“死信”。- 关键作用: 接收死信,并根据死信的路由键将它们路由出去。注意,死信的路由键默认是原消息的路由键,你也可以通过队列的
arguments
里的x-dead-letter-routing-key
来指定死信的路由键。
- 关键作用: 接收死信,并根据死信的路由键将它们路由出去。注意,死信的路由键默认是原消息的路由键,你也可以通过队列的
-
重试队列 (Retry Queue): 这是一个关键的“临时存放区”。它不直接给消费者监听,它的作用就是“关禁闭”——让消息在里面等待一段时间。
- 关键配置 1: 设置
x-message-ttl
,指定消息在这个队列里的存活时间(毫秒)。消息超过这个时间就会变成新的死信。🕰️ - 关键配置 2: 设置
x-dead-letter-exchange
,指向原来的业务交换机! 🤯 这是最骚的操作!这样重试队列里的消息过期后,会变成死信,然后被发送回原来的业务交换机。 - 关键绑定: 这个重试队列需要绑定到死信交换机 (DLX)。绑定时使用的路由键,要和从业务队列出来的死信的路由键匹配(默认就是原消息路由键)。
- 关键配置 1: 设置
-
业务交换机 (Original Exchange): 你的生产者发送消息的目标。重试队列里“刑满释放”的消息(过期死信),会回到这里,然后根据消息的原路由键再次被路由到业务队列。
整个“生死轮回”流程细讲:
- 生产者发送消息到 业务交换机,使用 业务路由键。
- 消息被路由到 业务队列。
- 消费者从 业务队列 取出消息,处理失败。
- 消费者发送 NACK 并设置
requeue=false
。 - 消息变成死信。因为 业务队列 配置了
x-dead-letter-exchange
指向 DLX,消息被发送到 DLX。💀➡️ - DLX 收到死信,使用死信的路由键(默认是原业务路由键)查找绑定。
- 找到了 重试队列 与 DLX 的绑定(绑定键是业务路由键)。消息被路由到 重试队列。📬
- 消息进入 重试队列,开始计时 TTL。消息在队列里“沉睡”。⏳
- TTL 时间到!消息在 重试队列 里过期,再次变成死信。
- 因为 重试队列 配置了
x-dead-letter-exchange
指向 业务交换机,消息被发送回 业务交换机。♻️ - 业务交换机 收到消息(此时消息会带有一些死信头信息),再次根据消息的原路由键路由。
- 消息再次被路由回 业务队列。🎉
- 消费者从 业务队列 再次收到消息,进行重试处理。这次收到的消息
Redelivered
标志通常为true
。
这个流程可以循环多次,通过设置多个重试队列,每个队列绑定到前一个队列的 DLX,并设置不同的 TTL 和指向下一个队列的 DLX,可以实现多级、阶梯式的延迟重试!比如:失败 -> 等 1 分钟 -> 失败 -> 等 5 分钟 -> 失败 -> 等 30 分钟 -> 最终失败进入人工处理队列。🛣️⏱️
代码怎么配置这些“地点”和“规则”?
这主要是在你的 Spring Boot 项目的 RabbitConfig.java
里定义 Bean 时,通过队列的 arguments
参数来设置。
package com.example.rabbitmqconfirmdemo.config; // 使用你自己的包名// ... 必要的导入,包括 Queue, DirectExchange, Binding, BindingBuilder ...
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.core.*; // 导入这些类
import java.util.HashMap; // 引入 HashMap
import java.util.Map; // 引入 Map@Configuration
public class RabbitConfig {// 业务交换机 (同上)@Beanpublic TopicExchange myDurableExchange() {System.out.println("🛠️ 正在创建业务交换机: my.durable.exchange");return new TopicExchange("my.durable.exchange", true, false);}// ⭐ 定义死信交换机 (DLX) ⭐@Beanpublic DirectExchange dlxExchange() {System.out.println("🛠️ 正在创建死信交换机 (DLX): my.dlx.exchange");// DLX 通常用 Direct 类型,因为路由键不变或指定死信路由键return new DirectExchange("my.dlx.exchange", true, false); // 持久化}// ⭐ 定义业务队列,并配置其死信发往 DLX ⭐@Beanpublic Queue myDurableQueue() {System.out.println("🛠️ 正在创建持久化业务队列: my.durable.queue");Map<String, Object> args = new HashMap<>();// ⭐ 核心配置 1: 指定当前队列的死信发往哪个交换机 ⭐// 当消息被 NACK(requeue=false) 或在当前队列过期,会被发到这个交换机args.put("x-dead-letter-exchange", "my.dlx.exchange"); // 指向上面定义的 DLX// ⭐ 可选配置:指定死信的路由键,不指定则使用原消息的路由键 ⭐// args.put("x-dead-letter-routing-key", "some-dlx-routing-key");// ⭐ 可选配置:给业务队列的消息设置 TTL (如果在队列里长时间没被消费也会死信) ⭐// args.put("x-message-ttl", 300000); // 比如 5 分钟,防止消息永远积压在业务队列// 队列本身也要持久化return new Queue("my.durable.queue", true, false, false, args);}// ⭐ 定义重试队列 (这是第一个延迟等待层级) ⭐@Beanpublic Queue retryQueue() {System.out.println("🛠️ 正在创建重试队列 (延迟 60秒): my.retry.queue");Map<String, Object> args = new HashMap<>();// ⭐ 核心配置 2: 消息在这个队列里等待多久变成死信(毫秒)⭐args.put("x-message-ttl", 60000); // 例子:等待 60 秒// ⭐ 核心配置 3: 消息过期变成死信后,发回哪个交换机?指向业务交换机! ⭐args.put("x-dead-letter-exchange", "my.durable.exchange"); // 死信发回业务交换机// ⭐ 核心配置 4: 消息过期变成死信后,使用哪个路由键发回?通常用原路由键 ⭐args.put("x-dead-letter-routing-key", "my.routing.key"); // 指定死信发回业务交换机时的路由键// 队列本身也要持久化return new Queue("my.retry.queue", true, false, false, args);}// ⭐ 定义重试队列绑定到 DLX ⭐@Beanpublic Binding retryBinding(Queue retryQueue, DirectExchange dlxExchange) {System.out.println("🛠️ 将重试队列绑定到 DLX");// 绑定键要和从业务队列出来到 DLX 的死信路由键匹配 (默认是原路由键 "my.routing.key")return BindingBuilder.bind(retryQueue).to(dlxExchange).with("my.routing.key"); //}// ⭐ 可选:定义最终失败队列 (Final DLQ) ⭐// 用于接收从重试队列出来,但没有被正确路由(比如业务交换机或队列被删了)// 或者你设计了多层重试,这是最后一层重试队列的死信目的地@Beanpublic Queue finalDlxQueue() {System.out.println("🛠️ 正在创建最终死信队列: my.final.dlq.queue");return new Queue("my.final.dlq.queue", true); // 持久化}// ⭐ 可选:定义一个交换机用于接收所有最终的死信 (如果需要统一处理) ⭐@Beanpublic DirectExchange finalDlxExchange() {System.out.println("🛠️ 正在创建最终死信交换机: my.final.dlx.exchange");return new DirectExchange("my.final.dlx.exchange", true, false);}// ⭐ 可选:将最终失败队列绑定到最终死信交换机 ⭐@Beanpublic Binding finalDlxBinding(Queue finalDlxQueue, DirectExchange finalDlxExchange) {System.out.println("🛠️ 将最终死信队列绑定到最终死信交换机");return BindingBuilder.bind(finalDlxQueue).to(finalDlxExchange).with("final.dlq.key"); // 使用一个特定的路由键}// ⭐ 如果需要多层延迟重试,就需要定义更多重试队列和绑定 ⭐// retryQueue2 (TTL 5分钟), retryQueue3 (TTL 30分钟) ...// retryQueue -> DLX (my.dlx.exchange) -> retryQueue2// retryQueue2 -> DLX (my.dlx.exchange) -> retryQueue3// retryQueue3 -> DLX (my.dlx.exchange) -> my.durable.exchange (或指向最终死信交换机)// 业务队列绑定到业务交换机 (同上)@Beanpublic Binding binding(Queue myDurableQueue, TopicExchange myDurableExchange) {return BindingBuilder.bind(myDurableQueue).to(myDurableExchange).with("my.routing.key");}// ... RabbitTemplate 和 ListenerContainer 配置 (同上,确保 ListenerContainer 模式是 MANUAL) ...
}
消费者代码中的关键:
在你的 ChannelAwareMessageListener
实现中,当处理失败并决定进行延迟重试时,一定要发送 NACK 并设置 requeue=false
!
// ManualAckMessageListener.java (onMessage 方法中的失败处理部分)// ... catch (Exception e) { ...
try {// ⭐ 核心:手动发送 NACK,并将 requeue 设为 FALSE! ⭐// 这样消息就会变成死信发往业务队列配置的 DLX!channel.basicNack(deliveryTag, false, false); // 拒绝当前消息,不批量,不重新入队 (进入DLX)System.err.println("💔 消息处理失败,手动发送 NACK 并发送到 DLX!Delivery Tag: " + deliveryTag);
} catch (Exception nackException) {System.err.println("🔥 发送 NACK 时也发生异常! Delivery Tag: " + deliveryTag + ", 错误: " + nackException.getMessage());// 极少发生,需重点关注
}
// ...
这样配置并配合消费者端的 basicNack(false, false)
操作,你就搭建起了一个完整的 DLX + TTL 延迟重试机制!🥳
优点总结:
- 实现了消息处理失败后的延迟重试,不会立即给消费者和依赖服务造成压力。
- 重试等待期间不阻塞消费者线程。
- 即使消费者应用崩溃,消息也安全地待在 RabbitMQ 的重试队列里等待。
- 通过配置多层重试队列,可以实现灵活的阶梯式延迟重试策略。🛣️⏱️
- 最终失败的消息可以被导入专门的 DLQ,方便统一管理和处理。📂
注意事项:
- DLX + TTL 机制依赖于 RabbitMQ broker 的稳定运行和正确配置。
- 如果你的业务逻辑需要根据重试次数采取不同策略(比如重试 3 次失败后换个处理逻辑),你需要在消息的 Header 里记录重试次数,并在消费者端读取和判断。RabbitMQ 在死信过程中会添加一些 Header 信息,比如
x-death
头部,包含了消息的死信原因、次数、队列等信息,你可以利用它来判断重试次数。 - 确保重试队列的 TTL 和业务处理时间、外部依赖恢复时间相匹配。
把 DLX + TTL 玩明白了,你的 RabbitMQ 消息可靠性就又上了一个大台阶!它和本地重试(Spring Retry)常常是好搭档,一个负责内部快速消化,一个负责外部排队等待,强强联合,构建最稳固的消息处理防线!🛡️💪
总结:如何构建可靠的消息消费策略?✨🛡️
结合咱们的讨论,一套可靠的 RabbitMQ 消息处理策略,就像给你的消息穿上多层“保险服”:
- 生产者确认机制: 确保消息安全送达 RabbitMQ 的 Exchange。✅📬
- 持久化功能: 确保 Exchange、Queue 和消息本身在 RabbitMQ 重启或崩溃时不会丢失。🏛️💾✉️
- 消费者确认机制: 确保消息被消费者成功处理后才从队列删除。对于需要精细控制重试行为的复杂场景,强烈推荐使用
AcknowledgeMode.MANUAL
手动确认模式!✍️🔒(对于auto
模式,它在方法不抛异常时自动 ACK,抛异常时自动 NACK。对于简单的场景够用,但如果你需要灵活控制 NACK(true)/NACK(false) 或结合MessageRecoverer
,手动模式是必选项)。 - 消费者失败重试机制: 这是处理“非成功”消息的核心!
- 可以开启 本地重试 (Spring Retry),在消费者内部进行几次快速重试。🏠🔄
- 重试多次或遇到特定错误后,通过发送 NACK 并
requeue=false
将消息送入 DLX + TTL 流程,实现延迟重试。📦⏳ - 可以配置
MessageRecoverer
(特别是RepublishMessageRecoverer
),将那些重试耗尽依然失败的消息隔离到专门的异常队列进行处理。📚➡️📂
通过合理组合这些机制,你就可以构建出一个既高效又可靠的 RabbitMQ 消息消费系统,从容应对各种“幺蛾子”般的消费失败场景,让你的应用更加健壮!🏆
希望这篇文章对你有所启发!快去试试搭建你的智能重试策略吧!Go~Go~Go!🚀😄
相关文章:
【MQ篇】RabbitMQ之消费失败重试!
目录 引言:消息不丢是底线,失败了优雅重试是修养!消费失败了,为啥不能老是原地复活?🤔智能重试策略一:本地重试(Spring Retry 的魔法)🏠✨智能重试策略二&…...
权力结构下的人才价值重构:从 “工具论” 到 “存在论” 的转变
引言 在现在的公司管理里,常常能听到这样一种说法:“我用你,你才是人才;不用你,你啥都不是。” 这其实反映了一种很常见的评判人才价值的标准,就是只看公司的需求,把人才当作实现公司目标的工…...
【上位机——MFC】视图
相关类 CView及其子类,父类为CWnd类,封装了关于视图窗口的各种操作,以及和文档类的数据交互。 视图窗口的使用 1.定义一个自己的视图类(CMyView),派生自CView,并重写父类成员纯虚函数OnDraw。 2.其余框架类和应用程…...
Unity:Sprite Shapes(精灵形状)
游戏世界的基本构建单位——精灵(Sprite) Sprite(精灵)是什么? Sprite指的是一张小图片,在游戏里代表一个角色、道具、背景元素。 在2D游戏里,比如滑雪游戏,角色、小树、雪地……很…...
火语言RPA--钉钉群通知
【组件功能】:向钉钉群发送文本或markdown消息 在钉钉群创建自定义机器人(Webhook),在组件配置Webhook地址、密钥、文本内容即可向钉钉群发送文本或markdown消息,还可以at群成员或所有人。 配置预览 配置说明 Webho…...
详细图解 Path-SAM2: Transfer SAM2 for digital pathology semantic segmentation
✨ 背景动机 数字病理中的语义分割(semantic segmentation)是非常关键的,比如肿瘤检测、组织分类等。SAM(Segment Anything Model)推动了通用分割的发展,但在病理图像上表现一般。 病理图像(Pa…...
驯龙日记:用Pandas驾驭数据的野性
引言:为什么选择Pandas? "NumPy是手术刀,Pandas是急救箱" 手术刀(NumPy):精密的数值计算 急救箱(Pandas):处理现实数据的全套工具 维度NumPy数组Pandas Se…...
产品经理面经(1)
今天开一个新的栏目,是关于产品经理方面的。产品经理这个岗位每年的需求都是不少的,尤其是近年来AI的兴起造就了产品经理与AI方面深度融合从而催生了“AI产品经理”这种类型的岗位。具体数据如下 总体规模: 2020 年:受疫情影响&am…...
【黑马JavaWeb+AI知识梳理】前端Web基础02 - JS+Vue+Ajax
JS(行为/交互效果) JavaScript(JS)跨平台、面向对象,是用来控制网页行为,实现页面交互效果的脚本语言。 和Java完全不同,但基础语法类似。 组成: ECMAScript:规定了JS…...
Unity Post Processing 小记 【使用泛光实现灯光亮度效果】
一、前言 本篇适用于Unity 2018 - 2019及以上版本,以默认渲染管线为例。文章内容源于个人研究尝试与网络资料收集,可能存在不准确之处。初衷是因新版本制作时老的Bloom插件失效,经研究后分享开启Bloom效果的方法。若在项目中使用Post Proces…...
NFC 碰一碰发视频贴牌技术,音频功能的开发实践与技术解析
在数字化营销与信息交互场景中,NFC 碰一碰技术凭借其便捷性和高效性,成为快速传递多媒体内容的新选择。通过 NFC 实现视频音频的快速传输,不仅能提升用户体验,还能为各类场景带来创新应用。本文将深入探讨该功能开发过程中的关键技…...
新型“电力寄生虫“网络钓鱼攻击瞄准能源企业与知名品牌
本周发布的综合威胁报告显示,自2024年以来,一场名为"电力寄生虫"(Power Parasites)的复杂网络钓鱼活动持续针对全球能源巨头和知名品牌展开攻击。 该攻击活动主要通过精心设计的投资骗局和虚假招聘信息,冒用…...
如何将数据输入到神经网络中
引言 在前面的文章学习中,我们初步了解到神经网络在人工智能领域扮演着至关重要的角色,它具备实现真正人工智能的潜力。真正的人工智能意味着机器能够像人类一样进行感知、学习、推理和决策等复杂活动。而神经网络作为实现这一目标的关键技术,…...
【quantity】2 Unit 结构体(unit.rs)
一、源码 下面代码实现了一个基于类型级别的物理量单位系统,使用Rust的类型系统在编译期保证单位运算的正确性。 use typenum::{Integer, Sum, Diff, Z0, // 0P1, P2, P3, P4, // 1, 2, 3, 4N1, N2, N3 // -1, -2, -3 }; use std::marker::PhantomData; use st…...
OpenCV 图形API(66)图像结构分析和形状描述符------将一条直线拟合到三维点集上函数fitLine3D()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 拟合一条直线到3D点集。 该函数通过最小化 ∑iρ(ri) 来将一条直线拟合到3D点集,其中 ri 是第 i 个点与直线之间的距离,…...
uniapp: 低功耗蓝牙(BLE)的使用
在微信小程序中实现蓝牙对接蓝牙秤的重量功能,主要依赖微信小程序提供的低功耗蓝牙(BLE)API。以下是一个清晰的步骤指南,帮助你完成从连接蓝牙秤到获取重量数据的开发流程。需要注意的是,具体实现可能因蓝牙秤的协议和…...
谢飞机的Java面试之旅:从Spring Boot到Kubernetes的挑战
场景:互联网大厂Java求职 在一家知名互联网大厂的面试现场,严肃的面试官坐在谢飞机的对面,开始了面试。 第一轮:基础技术与平台 面试官: 谢先生,您能简单介绍一下Java SE 8的主要新特性吗? 谢飞机: 当然,Java 8引入了Lambda表达式、Stream API和新的日期时间API。 …...
Shadertoy着色器移植到Three.js经验总结
Shadertoy是一个流行的在线平台,用于创建和分享WebGL片段着色器。里面有很多令人惊叹的画面,甚至3D场景。本人也移植了几个ShaderToy上的着色器。本文将详细介绍移植过程中需要注意的关键点。 1. 基本结构差异 想要移植ShaderToy的shader到three.js&am…...
基于BenchmarkSQL的OceanBase数据库tpcc性能测试
基于BenchmarkSQL的OceanBase数据库tpcc性能测试 安装BenchmarkSQL及其依赖安装软件依赖编译BenchmarkSQLBenchmarkSQL props文件配置数据库和测试表配置BenchmarkSQL压测装载测试数据TPC-C压测(固定事务数量)TPC-C压测(固定时长)生成测试报告重复测试流程梳理安装Benchmar…...
Flutter 泛型 泛型方法 泛型类 泛型接口
目录 泛型简单使用 泛型类的简单使用 泛型接口的使用 通俗理解:泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验) 泛型简单使用 main(){print(getData2("XXX"));getData2<String>("XXX");getData2<int>(1);}Str…...
边缘函数:全栈开发的最后1毫秒性能革命
一、边缘计算的时空折叠术 1. 传统CDN vs. 智能边缘网络 全球电商平台实测数据: 场景云端处理延迟边缘处理延迟转化率提升搜索建议320ms8ms18%个性化推荐450ms12ms27%实时库存检查680ms9ms42%欺诈检测920ms15ms63% 二、边缘全栈架构的量子纠缠 1. 代码的时空分布…...
网店专用版批量转账系统,覆盖淘宝、拼多多、抖店订单信息自动核对+插旗自动备注,支持微信支付宝批量转账
不少电商人在运营过程中,需要用转账工具来解决日常运营过程中的返款问题。 但在实际操作过程中,往往有很多问题。东哥在这里梳理下,方便大家了解: 1.错返/漏返的情况时有发生 为什么会错返和漏返? 实际来看&#x…...
AUTOSAR_RS_ClassicPlatformDebugTraceProfile
AUTOSAR经典平台调试、跟踪与分析支持 AUTOSAR组件调试、跟踪与分析功能详解 目录 简介ARTI核心扩展 核心特定ARTI扩展结构核心参数定义 操作系统和任务扩展 OS特定ARTI扩展任务特定ARTI扩展软件组件特定扩展 总体架构 组件结构接口定义 错误处理 默认错误跟踪器(DET) 总结 1.…...
vue中将html2canvas转成的图片传递给后台 Python Flask 服务
下面将详细介绍如何在 Vue 项目里把 html2canvas 转换得到的图片传递给后台的 Python Flask 服务。 前端(Vue)步骤 1. 安装依赖 首先要确保已经安装了 html2canvas 和 axios,若未安装,可在终端执行以下命令: npm i…...
基于深度学习的智能交通流量监控与预测系统设计与实现
基于深度学习的智能交通流量监控与预测系统设计与实现 摘要 随着城市化进程的加速和机动车保有量的激增,交通拥堵、事故频发、环境污染等问题日益严峻,对传统的交通管理方式提出了巨大挑战。智能交通系统(ITS)作为解决这些问题的…...
鸿蒙系统应用开发全栈指南
一、开发环境搭建与工具链配置 1. DevEco Studio深度解析 作为鸿蒙生态的官方IDE,DevEco Studio 4.2版本已集成ArkTS 3.0编译器与AI代码助手功能。安装过程需注意: 系统要求:Windows 10 21H2或macOS Monterey以上环境依赖:Node…...
STC32裸机项目集成FreeRTOS的实战问题解析
目录 🍅点击这里查看所有博文 随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记…...
振弦式应变计在混凝土结构长期监测中的应用与特点 久岩传感 GEO-explorer
振弦式应变计在混凝土结构长期监测中的应用与特点 久岩传感 GEO-explorer 振弦式应变计是一种专为长期埋设于水工建筑物及各类混凝土结构内部而设计的测量仪器,可广泛应用于梁体、柱体、桩基、挡土墙、隧道衬砌、桥墩及基岩等结构的应变与应力监测,同时具…...
AVFormatContext 再分析
说明 :将 avfromatContext 的变量依次打印分析,根据ffmpeg 给的说明,猜测,结合网上的文章字节写测试代码分析。 从常用到不常用依次分析 1. unsigned int nb_streams; 代表 avfromatContext 中 AVStream **streams 的个数 /** …...
力扣hot100,739每日温度(单调栈)详解
时隔多久又遇到单调栈的题了,上次记得是接雨水的题,简单讲一下单调栈的适用场景和定义。 意义:看名字就知道单调栈是一个栈里面的数据是单调的 。 解决问题: 单调栈主要用于解决需要**快速找到某个元素附近更大或更小的元素**的问题,其核心…...
怎么检测代理IP延迟?如何选择低延迟代理?
在跨境电商、数据采集以及社交媒体管理等活动中,代理IP的延迟是评估其性能的关键指标之一。高延迟的代理IP可能显著影响任务效率,特别是在需要高并发或大量请求的情况下。本文将介绍几种测试海外代理IP延迟的方法。 一、使用Ping命令测试延迟 Ping命令…...
QEMU 10.0 发布
QEMU 10.0 于 2025 年 4 月 23 日发布。此版本包含 2800 多个提交,来自 211 位作者。以下是一些主要的更新内容: CPU 和主板支持增强 x86 架构:优化了字符串操作指令,显著缩短启动时间。新增了 Intel Clearwater Forest 和 Sierra…...
C++和Java该如何选择?
我真诚的建议你选择C。因为国内Java程序员内卷太严重了,某些公司发布一个Java岗位,立刻就有几百人打招呼;而发布一个C岗位,打招呼的人数就那么十几个。 要知道,无论什么时候,只要你能够学得动C,…...
Javase 基础入门 —— 06 final + 单例
本系列为笔者学习Javase的课堂笔记,视频资源为B站黑马程序员出品的《黑马程序员JavaAI智能辅助编程全套视频教程,java零基础入门到大牛一套通关》,章节分布参考视频教程,为同样学习Javase系列课程的同学们提供参考。 01 final 关…...
web 开发中,前端部署更新后,该怎么通知用户刷新
web 开发中,前端部署更新后,该怎么通知用户刷新? 浏览器为什么存在刷新按钮?🔘 因为需要重新加载js,css,html。但为何需要重新加载这些东西?直白点说这些东西其实就是一个文档&…...
LaTex、pdfLaTex、XeLaTex和luaLaTex的区别和联系
之前一直搞不懂这些乱七八糟的Tex到底有啥区别,不同引擎不同编译器换来换去,查了些资料又问了下AI,总算是搞懂了。 大概是这样,很久以前有人写了个Tex排版引擎,输入一些代码命令,输出dvi文件(设…...
深入解析 npm 与 Yarn:Node.js 包管理工具对比与选型指南
在 Node.js 生态中,依赖管理是项目开发的核心环节。npm(Node Package Manager)和 Yarn 作为两大主流包管理工具,虽目标一致但各有特色。本文将从技术实现、使用场景、生态整合等维度深度对比,助你选择更适合的工具。…...
PDF嵌入图片
所需依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>9.0.0</version><type>pom</type> </dependency>源码 /*** PDF工具*/ public class PdfUtils {/*** 嵌入图…...
Coding Practice,48天强训(24)
Topic 1:判断是不是平衡二叉树(递归) 判断是不是平衡二叉树_牛客题霸_牛客网 /*** struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** 代码中的类名、方法名、参数名已经指定,请勿修改&…...
技术分享 | Oracle-RAC修改IP信息
本文为墨天轮数据库管理服务团队第61期技术分享,内容原创,作者为技术顾问胡振兴,如需转载请联系小墨(VX:modb666)并注明来源。 在生产中有时候会遇到网络变更,Oracle RAC IP信息更换等情况&…...
北京工业大学25计专上岸经验分享
1.个人情况介绍 本科就读于河北双非,专业为计算机科学与技术,四级三次498,六级两次460,拿过几次校级奖学金,竞赛经历有蓝桥杯国三、数学竞赛省二。本科成绩排名靠前,保研保7排8,遗憾选择考研继…...
rabbitmq常用命令
目录 1.查看集群状态 2.查看消息对列的堆积 3.重启mq服务 4.清理mq队列消息 1.查看集群状态 rabbitmqctl cluster_status 2.查看消息对列的堆积 rabbitmqctl list_queues rabbitmqctl list_queues | grep -v 0$ 3.重启mq服务 systemctl status rabbitmq-server.servic…...
Spring Cloud Alibaba 整合 Sentinel:实现微服务高可用防护
一、Sentinel 简介 Sentinel 是阿里巴巴开源的面向分布式服务架构的流量控制组件,主要提供以下核心功能: 流量控制:针对不同的调用关系,以不同的运行指标(如 QPS、线程数、系统负载等)为基准,对…...
机器人抓取位姿检测——GRCN训练及测试教程(Pytorch)
机器人抓取位姿检测——GRCN训练及测试教程(Pytorch) 这篇文章主要介绍了2020年IROS提出的一种名为GRCN的检测模型,给出了代码各部分的说明,并给出windows系统下可以直接复现的完整代码,包含Cornell数据集。 模型结构图 github源码地址:https://github.com/skumra/robo…...
《一键式江湖:Docker Compose中间件部署108式》开篇:告别“配置地狱”,从此笑傲云原生武林!》
(🗡️江湖险恶,少侠可曾受困?) 深夜🌙,你盯着屏幕泛红的终端报错,第3次从GitHub某个无名仓库扒下残缺的docker-compose.yaml, 却发现: RabbitMQ连不上&#…...
C语言内敛函数
目录 1、内敛函数的定义 2、内敛函数的特点 2.1 减少函数调用开销 2.2 代码膨胀 2.3 编译器决定 2.4 适用于小型函数 3、示例 4、注意事项 在C语言中,内敛函数(Inline Function)是一种通过编译器优化来减少函数调用开销的机制。它通过…...
DAY8-GDB调试及打桩
GDB打桩 1.类成员函数打桩 // example1.cpp #include <iostream>class Calculator { public:int add(int a, int b) {return a b;} };int main() {Calculator calc;std::cout << "Result: " << calc.add(2, 3) << std::endl;return 0; }(…...
三、UI自动化测试03--操作方法API
目录 一、元素操作⽅法二、浏览器操作⽅法1. Part1: 设置最⼤化/⼤⼩/位置扩展: Web/APP 项⽬⻚⾯布局坐标系示意2. Part2: 后退/前进/刷新3. Part3: 关闭/退出/获取⻚⾯标题和 URL 地址 三、获取元素信息⽅法1. Part1: 获取⼤⼩/⽂本/属性值2. Part2: 判断元素是否可⻅/可⽤/可…...
人工智能—— K-means 聚类算法
目录 摘要 16 K-means 聚类算法 16.1 本章工作任务 16.2 本章技能目标 16.3 本章简介 16.4 编程实战 16.5 本章总结 16.6 本章作业 本章已完结!!! 摘要 本章实现的工作是:首先采用Python语言读取样本数据(学生的语文、数…...
使用XMLSpy校验xml是否合法
# 背景说明 近期大部分地区都在做或将要做数据迁移,基本所有产品的迁移工具底层都依赖了XSD文件对迁移的结构化数据对应XML文件进行初步校验,但有些XSD的问题提示不太容易理解,正好N年前我做XX数据上报时用过XMLSpy可以直接校验每个xml是否合…...