[RabbitMQ] 保证消息可靠性的三大机制------消息确认,持久化,发送方确认
🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- 1. 消息确认
- 1.1 消息确认机制
- 1.2 手动确认方法
- 1.3 代码示例
- 1.3.1 NONE
- 1.3.2 AUTO
- 1.3.3 MANUAL
- 2. 持久性
- 2.1 交换机持久化
- 2.2 队列持久化
- 2.3 消息持久化
- 3. 发送方确认
- 3.1 confirm确认模式
- 3.2 return回退模式
- 4. 常见面试题
1. 消息确认
1.1 消息确认机制
生产者发送消息之后,到达消费端之后,可能会有以下情况:一种是消息处理成功,一种是消息处理异常.
如果消息处理异常的情况下,这条消息就会被删除,此时就会造成消息的丢失.此时我们就需要保证消息可靠地到达消费者,RabbitMQ为此提供了消息确认的机制.
消费者在订阅队列的时候,可以之地宁autoAck
参数,根据这个参数,消息确认的机制可以分为一下的两种:
- 自动确认,
autoAck
参数为true.RabbitMQ的队列会把发送出去的消息自动置为确认,然后自动从内存或者硬盘中删除,而不管消费者是否真正地消费到了这些消息. - 手动确认,
autoAck
参数为false.RabbitMQ的队列会等待消费者显示调用Basic.Ack命令,回复确认信号之后才从内存或者硬盘中进行删除.
对于手动确认的队列中的消息而言,队列中的消息分为两部分:
⼀是等待投递给消费者的消息.
二是已经投递给消费者,但是还没有收到消费者确认信号的消息.
如果RabbitMQ一直没有收到消费者的确认消息信号,则RabbitMQ会安排这个消息重新进入队列,等待投递个下一个消费者,当然下一个消费者还是有可能是原来的消费者.
1.2 手动确认方法
消费者在收到消息之后,可以选择确认,也可以选择直接拒绝或者跳过,RabbitMQ也提供了不同的确认应答的方式.一共有以下三种:
- 肯定确认:
Channel.basicAck(long deliveryTag, boolean multiple)
这种方法表示的是,消费者已经成功接收到消息,可以将其丢弃了.参数分别表示的是:- deliveryTag: 消息的id,是消息的唯一标识.
- multiple: 是否批量确认,值为true则会一次性ack所有小于或等于指定deliveryTag的消息.值为false,则只确认当前指定deliveryTag的消息.这种方式可以在一定程度上减少网络的开销.
- 否定确认:
Channel.basicReject(long deliveryTag, boolean requeue)
消费者拒绝接收这个消息.参数说明:- requeue: 表示在拒绝这条消息之后,这条消息会不会被放回到队列中.
- 否定确认:
Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)
次方法可以让消费者批量拒绝消息,参数说明:- multiple: 设置为true表示拒绝deliveryTag编号之前的所有未被消费消息.其他的参数和上面的类似.
1.3 代码示例
下面我们通过SpringBoot来演示消息确认的机制.
Spring-AMQP对消息确认的机制提供了三种策略.
public enum AcknowledgeMode {NONE,MANUAL,AUTO;
}
- NONE: 在这种模式之下,不管消费者是否成功处理了消息,RabbitMQ会自动确认消息,从RabbitMQ的队列中自动移除消息,如果消息处理失败,消息会发生丢失.
- AUTO: 如果不对这个配置项进行配置的话,这个就是默认的确认机制,在这种模式之下,消费者会自动确认消息,但是如果在消费者处理消息的过程中抛出了异常,该消息不会被确认.
- MANUAL: 手动确认,消费者必须在成功处理之后显示调用
basicAck
方法来确认消息,如果消息未被确认
,RabbitMQ会认为消息没有被处理成功,并且会在消费者可用的时候重新投递该消息.如果消息被拒绝,可以按照自己的需求来处理消息是否重新进入队列中.
1.3.1 NONE
- 配置确认机制
spring:application:name: rabbitmq-springrabbitmq:host: 39.105.137.64port: 5672username: jiangruijiapassword: ******virtual-host: /listener:simple:acknowledge-mode: none
- 发送消息
首先配置交换机和队列
public static final String ACK_QUEUE = "ack_queue";
public static final String ACK_EXCHANGE = "ack_exchange";
@Bean
public DirectExchange ackExchange(){return ExchangeBuilder.directExchange(Constant.ACK_EXCHANGE).durable(true).build();
}
@Bean
public Queue ackQueue(){return QueueBuilder.durable(Constant.ACK_QUEUE).build();
}
@Bean
public Binding ackBinding(@Qualifier("ackExchange") DirectExchange exchange,@Qualifier("ackQueue") Queue queue){return BindingBuilder.bind(queue).to(exchange).with("ack");
}
通过接口发送消息:
@RequestMapping("/ack")
public String ack(){rabbitTemplate.convertAndSend(Constant.ACK_EXCHANGE,"ack","Ack消息");return "发送成功";
}
- 使用Postman发送消息
我们先把消费者注释掉,不要运行消费者.
在发送消息之后,我们看到队列中的消息有一条未消费的消息.
消费者消费者之后:
我们看到控制台中接收到了消息,处理异常,但是队列中的消息已经被标记为了确认.
1.3.2 AUTO
- 配置消息确认机制
spring:application:name: rabbitmq-springrabbitmq:host: 182.92.204.253port: 5672username: jiangruijiapassword: *****virtual-host: /listener:simple:acknowledge-mode: auto
- 运行程序
我们发现消息在不停地重发,并且消息的tag值在不停地增加.
在管理界面上,我们发现未确认的消息一直为1,这是由于在消费者处理信息的时候出现了异常,这时候RabbitMQ会不断地对消息进行重发,消息不会被确认.导致了消息一直都是unacked状态.
1.3.3 MANUAL
- 修改确认机制
listener:simple:acknowledge-mode: manual
- 修改消费者逻辑为手动确认
首先我们正常处理消息,先不要抛出异常
@Component
public class AckListener {@RabbitListener(queues = Constant.ACK_QUEUE)public void listener(Message message, Channel channel) throws IOException{System.out.printf("接收到消息:%s,消息tag:%d\n",new String(message.getBody(),"UTF-8"),message.getMessageProperties().getDeliveryTag());System.out.println("处理消息");
// int i = 3/0;//消息处理异常channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);}
}
调用接口发送消息:
我们看到运行结果是正常的.队列中的消息成功被消费.
接下来我们把异常放开:
@Component
public class AckListener {@RabbitListener(queues = Constant.ACK_QUEUE)public void listener(Message message, Channel channel) throws IOException{System.out.printf("接收到消息:%s,消息tag:%d\n",new String(message.getBody(),"UTF-8"),message.getMessageProperties().getDeliveryTag());System.out.println("处理消息");int i = 3/0;//消息处理异常channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);}
}
我们发现队列中的消息发生了积压,消息直接被退回的队列,控制台抛出了异常,消息没有被处理成功.
之后我们可以把代码改为出现异常的时候拒绝接收,其中basicReject
的requeue
属性配置为true,让消息拒绝之后重新入队.
@Component
public class AckListener {@RabbitListener(queues = Constant.ACK_QUEUE)public void listener(Message message, Channel channel) throws IOException{System.out.printf("接收到消息:%s,消息tag:%d\n",new String(message.getBody(),"UTF-8"),message.getMessageProperties().getDeliveryTag());try {System.out.println("处理消息");Thread.sleep(1000);int i = 3/0;//消息处理异常channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);}catch (Exception e){channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}
}
运行结果: 消费异常会被捕捉,拒绝接收回到队列之后,会不停进行重试.
这里与直接发生异常回到队列的结果不一样,发生异常回到队列不会反复重新处理,如果捕捉到异常之后拒绝接收,后续还是会不停地重新处理,与atuo的结果差不多.
2. 持久性
前面的消息确认机制是保证了消息从队列中到达消费者的过程中不发生丢失.但是如果RabbitMQ由于某种异常情况以外退出或者崩溃,交换机,队列或者消息可能会发生丢失.
RabbitMQ中的持久化分为三个部分: 交换机持久化,队列持久化,消息持久化.
2.1 交换机持久化
我们之前在进行RabbitMQ配置的时候,就曾经反复使用交换机的持久化.他是在声明交换机的时候,通过durable
方法的参数设置为true
来实现的.
相当于将交换机的属性在服务器内部保存,当MQ的服务器发生意外或关闭之后,重启 RabbitMQ 时不需要重新去建立交换机,交换机会自动建立,相当于一直存在.
public DirectExchange directExchange(){return ExchangeBuilder.directExchange(Constant.DIRECT_EXCHANGE).durable(true).build();
}
2.2 队列持久化
和交换机持久化一样,我们在之前也一直使用queue的持久化.持久化是通过在声明队列的时候,使用durable
方法把其中的属性设置为队列名来实现的.
public Queue directQueue1(){return QueueBuilder.durable(Constant.DIRECT_QUEUE1).build();
}
当我们在查看队列持久化的源码的时候,我们发现队列的durable属性默认为true.
public static QueueBuilder durable(String name) {return (new QueueBuilder(name)).setDurable();
}
private QueueBuilder setDurable() {this.durable = true;return this;
}
我们也可以使用nonDurable
方法来创建非持久化的队列.
public Queue directQueue1(){return QueueBuilder.nonDurable(Constant.DIRECT_QUEUE1).build();
}
2.3 消息持久化
消息实现持久化,需要把消息的投递模式设置为2(也就是MessageProperties
中的deliveryMode
设置为MessageDeliveryMode.PERSISTENT
),MessageDeliveryMode为一个枚举类.
public enum MessageDeliveryMode {NON_PERSISTENT,//⾮持久化 PERSISTENT;//持久化
只有我们同时设置了队列和消息的持久化,才可以保证RabbitMQ服务在重启之后,消息依然是存在的.如果只是设置了队列的持久化或者是消息的持久化,重启之后消息依然会丢失.
接下来我们让生产者来发送一条持久化的消息:
交换机和队列的配置还是我们之前的配置
@RequestMapping("/ack")
public String ack(){String s = "hello ack";Message message = new Message(s.getBytes(StandardCharsets.UTF_8),new MessageProperties());message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);rabbitTemplate.convertAndSend(Constant.ACK_EXCHANGE,"ack",message);return "发送成功";
}
在默认的条件下,消息是持久化,除非队列被声明为非持久化或者是在发送消息的时候消息被标记为持久化.
如果所有的消息都被标记为了持久化,会严重影响RabbitMQ的性能.这是因为写硬盘会拖慢速度.对于可靠性不是那么高的消息可以不采用持久化处理以提高整体的吞吐量.在选择是否要将消息持久化时,需要在可靠性和吐吞量之间做⼀个权衡.
如果把交换机,队列,消息都设置为持久化,就可以保证百分之百数据不丢失了吗?答案是不是的.
- 如果消费者在消费队列消息的时候,设置的是自动确认,那么当消费者还没有来得及处理消息,消费者就宕机了,就会造成消息丢失,这种情况就需要我们把消息的确认机制设置为手动确认即可.
- 如果消息在RabbitMQ中进行持久化的时候,在存硬盘的过程中发生了宕机,消息还没有来得及落盘,那么这些消息也会消失.
这种情况如何解决呢?第一种方法就是使用仲裁队列,我们后面说明.第二种方法就是发送端引入发送方确认的机制来保证消息的可靠性.下面我们就来详细介绍发送方确认机制.
3. 发送方确认
在使用RabbitMQ的时候,可以通过持久化来解决RabbitMQ宕机而导致的消息丢失的问题,如果有一种情况,消息在到达服务器之前就已经发生了丢失,这时候消息根本没有到达RabbitMQ,持久化也解决不了这个问题.
RabbitMQ为我们提供了两种方案,一种是事务机制,一种是发送方确认机制.
由于事务机制比较消耗性能,一般情况下,我们不会使用事务.我们主要介绍发送方确认的机制.
RabbitMQ为我们提供了两个方式来控制消息可靠性的投递:
- confirm确认模式
- return退回模式
3.1 confirm确认模式
- 配置RabbitMQ
rabbitmq:host: 182.92.204.253port: 5672username: jiangruijiapassword: ******virtual-host: /listener:simple:acknowledge-mode: manualpublisher-confirm-type: correlated #消息发送确认
- 设置确认回调逻辑并发送消息
无论消息发送是成功还是失败,都会调用ConfirmCallback
中的confirm
方法.如果消息成功发送到Broker,则ack为true.如果发送失败,ack为false,并且cause提供失败的原因.
首先配置带有发送方确认机制的RabbitTemplate,使用setConfirmCallback
来设置发送方确认,其中使用匿名内部类的方式重写confirm
方法,在其中编写确认成功和确认失败的逻辑.
@Bean
public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory){RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {//设置消息队列的确认机制@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {if (ack){//如果确认成功,ack为trueSystem.out.println("发送方确认成功:"+correlationData.getId());//correlationData中包含消息的id}else {System.out.println("发送方确认失败:"+correlationData.getId()+",原因:"+cause);}}});return rabbitTemplate;
}
confirm
中的三个参数的意思分别是:
- correlationData:消息发送时候的一些附加信息,其中包含一个id属性,通常用于在确认回调中识别特定的消息.
- ack: 交换机是否收到消息发送方的信息,收到为true,未收到为false.
- cause: 当消息确认失败的时候,这个字符串会提供失败的原因.
注意: 在我们在SpringIoC容器中自定义一个RabbitTemplate的Bean对象的时候,在我们对RabbitTemplate对象进行DI注入的时候,由于Spring的Bean的优先级管理机制,Spring不再会调用RabbitMQ原生的Bean对象,而是调用我们自定义的Bean对象.
配置交换机和队列
@Bean
public DirectExchange confirmExchange(){return ExchangeBuilder.directExchange(Constant.CONFIRM_EXCHANGE).durable(true).build();
}
@Bean
public Queue confirmQueue(){return QueueBuilder.durable(Constant.CONFIRM_QUEUE).build();
}
@Bean
public Binding confirmBinding(@Qualifier("confirmExchange") DirectExchange exchange,@Qualifier("confirmQueue") Queue queue){return BindingBuilder.bind(queue).to(exchange).with("confirm");
}
之后在生产者使用@Resource
注解注入我们写好的具有发送者确认机制的RabbitTemplate
.之后进行消息发送.
@Resource
private RabbitTemplate confirmRabbitTemplate;
@RequestMapping("/confirm")
public String confirm(){CorrelationData correlationData = new CorrelationData("1");//指定消息id,用于生产者进行消息确认confirmRabbitTemplate.convertAndSend(Constant.CONFIRM_EXCHANGE,"confirm","confirm消息",correlationData);return "发送成功";
}
在发送消息的时候.我们需要指定消息的correlationData
中的id
,这个id可以让生产者进行对应消息的确认.
我们在之前的文章中也提到过发布确认模式,其中包含了一个确认监听器,叫做ConfirmListener
接口,其中提供了handleAck
和handleNack
,一个用于处理消息确认成功时候的业务逻辑,一个是消息否定确认时候的业务逻辑.和ConfirmCallback.confirm
方法中的ack参数类似.
3. 测试
我们使用Postman对接口进行调用,观察控制台.
我们看到发送方成功确认了消息的接收,说明了消息已经成功到达了交换机.
如果我们把交换机的名称改掉.
@RequestMapping("/confirm")
public String confirm(){CorrelationData correlationData = new CorrelationData("1");//指定消息id,用于生产者进行消息确认confirmRabbitTemplate.convertAndSend("Constant.CONFIRM_EXCHANGE","confirm","confirm消息",correlationData);return "发送成功";
}
我们看到发送方消息确认失败了,说明消息没有正确地到达交换机.
3.2 return回退模式
消息到达exchange之后,会根据路由规则进行匹配,把消息放入Queue中.Exchange到Queue的过程,如果一条消息服务被任何队列消费,可以选择把消息发回给生产者,我们可以设置一个一个返回回调方法,对消息进行处理.
- 配置RabbitMQ
和confirm模式相同 - 设置返回回调逻辑并发送消息
配置具有return退回模式的RabbitTemplate.
@Bean
public RabbitTemplate returnRabbitTemplate(ConnectionFactory connectionFactory){RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);rabbitTemplate.setMandatory(true);rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {@Overridepublic void returnedMessage(ReturnedMessage returned) {System.out.println("消息被退回:"+returned);}});return rabbitTemplate;
}
这里在使用RabbitTemplate
中的setMandatory
方法的时候,如果设置为true,这个属性就是告诉Broker,如果一条消息没有被任何一条队列消费,那么就触发ReturnsCallback
.
其中ReturnedMessage
中有以下属性:
public class ReturnedMessage {private final Message message;private final int replyCode;private final String replyText;private final String exchange;private final String routingKey;
message表示返回消息的对象,包含了消息体和消息的属性.
replyCode表示Broker提供的回复码,表示消息无法路由的原因,有点类似与错误码.
replyText表示无法路由消息的额外信息和错误描述.
exchange,routingKey分别表示交换机的名称和路由键.
发送消息
@Resource
private RabbitTemplate returnRabbitTemplate;
@RequestMapping("/return")
public String confirmReturn(){CorrelationData correlationData = new CorrelationData("2");returnRabbitTemplate.convertAndSend(Constant.RETURN_EXCHANGE,"return","return消息",correlationData);return "发送成功";
}
- 测试
使用Postman调用接口,观察控制台日志.
当发送成功的时候,我们看到控制台上什么都没有打印,而且我们查看管理界面,发现队列中已经有消息准备被消费了.
如果发送的路由关键字配置错误.
@RequestMapping("/return")
public String confirmReturn(){CorrelationData correlationData = new CorrelationData("2");returnRabbitTemplate.convertAndSend(Constant.RETURN_EXCHANGE,"return1","return消息",correlationData);return "发送成功";
}
我们再次进行测试,发现消息被回退:
4. 常见面试题
这一个板块,会涉及到一个非常常见的面试题.就是如何保证RabbitMQ消息的可靠性.
我们可以根据消息可能丢失的场景来解决:
- 消息从生产者到交换机期间发生丢失
- 出现原因: 网络问题等
- 解决办法: confirm确认模式
- 消息从交换机无法路由到指定队列
- 出现原因: 交换机与队列配置错误
- 解决办法: return确认模式
- 消息队列自身数据发生丢失
- 出现原因: RabbitMQ服务器宕机
- 解决办法: 消息,队列,交换机持久化
- 消费者异常,导致消息丢失
- 出现原因: 消费者宕机,消费者业务逻辑异常
- 解决办法: 消息确认.
对于解决办法,我们需要根据上述的讲解进行具体描述
相关文章:

[RabbitMQ] 保证消息可靠性的三大机制------消息确认,持久化,发送方确认
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...
aws服务--机密数据存储AWS Secrets Manager(1)介绍和使用
一、介绍 1、简介 AWS Secrets Manager 是一个完全托管的服务,用于保护应用程序、服务和 IT 资源中的机密信息。它支持安全地存储、管理和访问应用程序所需的机密数据,比如数据库凭证、API 密钥、访问密钥等。通过 Secrets Manager,你可以轻松管理、轮换和访问这些机密信息…...

Java设计模式笔记(一)
Java设计模式笔记(一) (23种设计模式由于篇幅较大分为两篇展示) 一、设计模式介绍 1、设计模式的目的 让程序具有更好的: 代码重用性可读性可扩展性可靠性高内聚,低耦合 2、设计模式的七大原则 单一职…...

Unity3d C# 实现一个基于UGUI的自适应尺寸图片查看器(含源码)
前言 Unity3d实现的数字沙盘系统中,总有一些图片或者图片列表需要点击后弹窗显示大图,这个弹窗在不同尺寸分辨率的图片查看处理起来比较麻烦,所以,需要图片能够根据容器的大小自适应地进行缩放,兼容不太尺寸下的横竖图…...

【es6进阶】vue3中的数据劫持的最新实现方案的proxy的详解
vuejs中实现数据的劫持,v2中使用的是Object.defineProperty()来实现的,在大版本v3中彻底重写了这部分,使用了proxy这个数据代理的方式,来修复了v2中对数组和对象的劫持的遗留问题。 proxy是什么 Proxy 用于修改某些操作的默认行为࿰…...

w~视觉~3D~合集3
我自己的原文哦~ https://blog.51cto.com/whaosoft/12538137 #SIF3D 通过两种创新的注意力机制——三元意图感知注意力(TIA)和场景语义一致性感知注意力(SCA)——来识别场景中的显著点云,并辅助运动轨迹和姿态的预测…...

IT服务团队建设与管理
在 IT 服务团队中,需要明确各种角色。例如系统管理员负责服务器和网络设备的维护与管理;软件工程师专注于软件的开发、测试和维护;运维工程师则保障系统的稳定运行,包括监控、故障排除等。通过清晰地定义每个角色的职责࿰…...

一文学习开源框架OkHttp
OkHttp 是一个开源项目。它由 Square 开发并维护,是一个现代化、功能强大的网络请求库,主要用于与 RESTful API 交互或执行网络通信操作。它是 Android 和 Java 开发中非常流行的 HTTP 客户端,具有高效、可靠、可扩展的特点。 核心特点 高效…...

自研芯片逾十年,亚马逊云科技Graviton系列芯片全面成熟
在云厂商自研芯片的浪潮中,亚马逊云科技无疑是最早践行这一趋势的先驱。自其迈出自研芯片的第一步起,便如同一颗石子投入平静的湖面,激起了层层涟漪,引领着云服务和云上算力向着更高性能、更低成本的方向演进。 早在2012年&#x…...
Stable Diffusion 3 部署笔记
SD3下载地址:https://huggingface.co/stabilityai/stable-diffusion-3-medium/tree/main https://huggingface.co/spaces/stabilityai/stable-diffusion-3-medium comfyui 教程: 深度测评:SD3模型表现如何?实用教程助你玩转Stabl…...

微信小程序WXSS全局样式与局部样式的使用教程
微信小程序WXSS全局样式与局部样式的使用教程 引言 在微信小程序的开发中,样式的设计与实现是提升用户体验的关键部分。WXSS(WeiXin Style Sheets)作为微信小程序的样式表语言,不仅支持丰富的样式功能,还能通过全局样式与局部样式的灵活运用,帮助开发者构建美观且易于维…...

Docker 部署 MongoDB
🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot 🍃 vue-uniapp-template 🌺 仓库主页: GitCode💫 Gitee …...

Unity图形学之法线贴图原理
1.正常贴图:RGBA 4通道 每个通道取值范围 0-255 贴图里面取值是 0-1 2.法线贴图:法线怎么存入正常贴图的过程 每个通道里面存储的是一个向量(x,y,z,w) 通常我们会对应xyzw为rgba 存储值的范围也是0-1向量的取值范围是 -1到1法线怎么存入正常贴图的过程&…...

爬虫开发(5)如何写一个CSDN热门榜爬虫小程序
笔者 綦枫Maple 的其他作品,欢迎点击查阅哦~: 📚Jmeter性能测试大全:Jmeter性能测试大全系列教程!持续更新中! 📚UI自动化测试系列: SeleniumJava自动化测试系列教程❤ 📚…...

JVM系列之OOM观测准备
OOM, 全称 “Out Of Memory”,即内存用完的意思。JVM 因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时(可分配内存大于需要分配的内存), 就会抛出 java.lang.OutOfMemoryError。在实际的生产应用中,一旦…...
Qt | 开发技能提升档次哈
点击上方"蓝字"关注我们 01、Creator常用快捷键 >>> F1 查看帮助 F2 跳转到函数定义 Shift+F2 声明和定义之间切换 F3 查找下一个 F4 头文件和源文件之间切换 Ctrl+1 欢迎模式 Ctrl+2 编辑模…...
D79【 python 接口自动化学习】- python基础之HTTP
day79 requests模块发送请求 学习日期:20241125 学习目标:http定义及实战 -- requests模块进行get请求带参数&requests模块进行post请求 学习笔记: requests模块进行get请求 import requestsparams{"shouji":"130999…...
C++【日志模块中的writer类】前文中 循环队列用法
用到前文中的循环队列模板 /* ** File name: LogWriter.h ** Author: ** Date: 2024-11-4 ** Brief: 日志写入类 ** Note: 日志写入类,负责将日志写入文件和连接客户端。 ** Copyright (C) 1392019713qq.com All rights reserve…...

Linux:文件管理(一)——文件描述符fd
目录 一、文件基础认识 二、C语言操作文件的接口 1.> 和 >> 2.理解“当前路径” 三、相关系统调用 1.open 2.文件描述符 3.一切皆文件 4.再次理解重定向 一、文件基础认识 文件 内容 属性。换句话说,如果在电脑上新建了一个空白文档࿰…...

【C++初阶】第3课—类和对象(类的默认成员函数)
文章目录 1. 类的默认成员函数2. 构造函数3. 拷贝构造函数3.1 传值传参3.2 传值返回3.3 深拷贝和浅拷贝3.4 总结 4. 析构函数5. 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载5.3 日期类的实现 6. 取地址运算符重载6.1 const 成员函数6.2 取地址运算符重载 1. 类的默认成员函…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...