当前位置: 首页 > news >正文

RabbitMQ 篇-深入了解延迟消息、MQ 可靠性(生产者可靠性、MQ 可靠性、消费者可靠性)

??博客主页:【_-CSDN博客】**
感谢大家点赞??收藏评论**

文章目录

???1.0 RabbitMQ 的可靠性

? ? ? ? 2.0 发送者的可靠性

? ? ? ? 2.1 生产者重试机制

? ? ? ? 2.2 生产者确认机制

? ? ? ? 2.2.1 开启生产者确认机制

? ? ? ? 2.2.2 定义 ReturnCallback 机制

? ? ? ? 2.2.3 定义 ConfirmCallback 机制

???3.0 MQ 可靠性

? ? ? ? 3.1 交换机持久化

? ? ? ? 3.2 队列持久化

? ? ? ? 3.3 消息持久化

? ? ? ? 3.4 LazyQueue 懒惰队列

? ? ? ? 3.4.1 声明懒惰队列的方式

? ? ? ? 4.0 消费者的可靠性

? ? ? ? 4.1 消费者确认机制

? ? ? ? 4.2 失败重试机制

? ? ? ? 4.3 失败处理策略

? ? ? ? 5.0 使用 DelayExchange 插件

? ? ? ? 5.1 安装 DelayExchange 插件

? ? ? ? 5.2 声明延迟交换机

???5.3 发送延迟消息


1.0 RabbitMQ 的可靠性

消息从生产者到消费者的每一步都可能导致消息丢失:

1)发送消息时丢失:

- 生产者发送消息时连接 MQ 失败。

- 生产者发送消息到达 MQ 后未找到 Exchange 。

- 生产者发送消息到达 MQ 的 Exchange 后,未找到合适的 Queue 。

- 消息到达 MQ 后,处理消息的进程发生异常。

2)MQ 导致消息丢失:

- 消息到达 MQ,保存到队列后,尚未消费就突然宕机了。

3)消费者处理消息时:

- 消息接收后尚未处理突然宕机。

- 消息接收后处理过程中抛出异常。

综上,要解决消息丢失问题,保证 MQ 可靠性,就必须从三个方面入手:

- 确保生产者一定把消息发送到 MQ 。

- 确保 MQ 不会将消息弄丢。

- 确保消费者一定要处理消息。

2.0 发送者的可靠性

通过生产者重试机制、确认机制来确保发送者的可靠性。

2.1 生产者重试机制

首先第一种情况,就是生产者发送消息时,出现了网络故障,导致与MQ的连接中断。为了解决这个问题,SpringAMQP 提供的消息发送时的重试机制。即:当 RabbitTemplate 与 MQ 连接超时后,多次重试。

修改 application.yml 文件,添加下面的内容:

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

利用命令停掉 RabbitMQ 服务:

docker stop mq

然后测试发送一条消息,会发现会每隔 1 秒重试 1 次,总共重试了 3 次。消息发送的超时重试机制配置成功了。

注意,当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过 SpringAMQP 提供的重试机制时阻塞式的重试,也就是说多次重试等待的过程中,当前线程式被阻塞的。

如果对业务性能有要求,建议禁用重试机制,如果一定要使用,请合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码。

2.2 生产者确认机制

一般情况下,只要生产者与 MQ 之间的网路连接顺畅,基本不会出现发送消息丢失的情况,因此大多数情况下我们无需考虑这种问题。

不过,在少数情况下,也会出现消息发送到 MQ 之后丢失的现象,比如:

- MQ 内部处理消息的进程发生了异常。

- 生产者发送消息到达 MQ 后未找到 Exchange 。

- 生产者发送消息到达 MQ 的 Exchange 后,未找到合适的 Queue,因此无法路由。

当消息投递到 MQ,但是路由失败时,通过 Return 返回异常信息,同时返回 ack 确认信息,代表投递成功。

1)临时消息投递到了 MQ,并且入队成功,返回 ACK,告知投递成功。

2)持久消息投递到了 MQ,并且入队完成持久化,返回 ACK,告知投递成功。

3)其他情况都会返回 NACK,告知投递失败。

其中 ACK 和 NACK 属于Publisher Confirm机制,ACK是投递成功;NACK 是投递失败。而 return 则属于Publisher Return机制。

2.2.1 开启生产者确认机制

默认两种机制都是关闭状态,需要通过配置文件来开启。

在 application.yml 中添加配置:

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

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

1)none:关闭 confirm 机制

2)simple:同步阻塞等待 MQ 的回执

3)correlated:MQ 异步回调返回回执

一般我们推荐使用 correlated,回调机制。

2.2.2 定义 ReturnCallback 机制

每个 RabbitTemplate 只能配置一个 ReturnCallback,因此可以在配置类中统一设置。

代码如下:

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());}});}
}

测试:

当发送消息的时候,设置一个不存在的路由:

在交换机中,不存在 “xbss” 路由关键字,则会执行returnedMessage 方法。

    // 首先引入依赖@Autowiredprivate RabbitTemplate rabbitTemplate;@Testvoid contextLoads() {//发送Object类型的消息textObject();}@Testpublic void textObject(){User user = new User(1,"xbs","123456",null,null);rabbitTemplate.convertAndSend("textExchange", "xbss", user, new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {message.getMessageProperties().setMessageId("123456");return message;}});}

执行结果:

2.2.3 定义 ConfirmCallback 机制

由于每个消息发送时的处理逻辑不一定相同,因此 ConfirmCallback 需要在每次发送消息时定义。具体来说,是在调用 RabbitTemplate 中的 convertAndSend 方法时,多传递一个参数:

代码如下:

    @Testvoid testPublisherConfirm() {// 1.创建CorrelationDataCorrelationData cd = new CorrelationData();// 2.给Future添加ConfirmCallbackcd.getFuture().thenAccept(result -> {if (result.isAck()) { // result.isAck(),boolean类型,true代表ack回执,false 代表 nack回执log.info("发送消息成功,收到 ack!");} else { // result.getReason(),String类型,返回nack时的异常描述log.info("发送消息失败,收到 nack, reason : {}", result.getReason());}}).exceptionally(ex -> {// 2.1.Future发生异常时的处理逻辑,基本不会触发log.error("send message fail", ex);return null;});// 3.发送消息rabbitTemplate.convertAndSend("xbs.direct", "red", "hello", cd);}

执行结果:

执行成功之后,日志输出:

接收消息:

注意

开启生产者确认比较消耗 MQ 性能,一般不建议开启。而且大家思考一下触发确认的几种情况:

  • 路由失败:一般是因为 RoutingKey 错误导致,往往是编程导致

  • 交换机名称错误:同样是编程错误导致

  • MQ 内部故障:这种需要处理,但概率往往较低。因此只有对消息可靠性要求非常高的业务才需要开启,而且仅仅需要开启 ConfirmCallback 处理 nack 就可以了。

3.0 MQ 可靠性

消息到达MQ以后,如果 MQ 不能及时保存,也会导致消息丢失,所以 MQ 的可靠性也非常重要。

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

1)交换机持久化

2)队列持久化

3)消息持久化

3.1 交换机持久化

在控制台的 Exchange 页面,添加交换机时可以配置交换机的 Durability 参数:

设置为 Durable 就是持久化模式,Transient 就是临时模式。

3.2 队列持久化

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

设置为 Durable 就是持久化模式,Transient 就是临时模式。

3.3 消息持久化

在控制台发送消息的时候,可以添加很多参数,而消息的持久化是要配置一个Delivery mode:设置为 Persistent 消息持久化。

说明

在开启持久化机制以后,如果同时还开启了生产者确认,那么 MQ 会在消息持久化以后才发送 ACK 回执,进一步确保消息的可靠性。

不过出于性能考虑,为了减少 IO 次数,发送到 MQ 的消息并不是逐条持久化到数据库的,而是每隔一段时间批量持久化。一般间隔在 100 毫秒左右,这就会导致 ACK 有一定的延迟,因此建议生产者确认全部采用异步方式。

3.4 LazyQueue 懒惰队列

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

  • 消费者宕机或出现网络故障

  • 消息发送量激增,超过了消费者处理速度

  • 消费者处理业务发生阻塞

2)一旦出现消息堆积问题,RabbitMQ 的内存占用就会越来越高,直到触发内存预警上限。此时 RabbitMQ 会将内存消息刷到磁盘上,这个行为成为 PageOut 。PageOut 会耗费一段时间,并且会阻塞队列进程。因此在这个过程中 RabbitMQ 不会再处理新的消息,生产者的所有请求都会被阻塞。

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

  • 接收到消息后直接存入磁盘而非内存

  • 消费者要消费消息时才会从磁盘中读取并加载到内存(也就是懒加载)

  • 支持数百万条的消息存储

而在 3.12 版本之后,LazyQueue 已经成为所有队列的默认格式。因此官方推荐升级 MQ 为 3.12 版本或者所有队列都设置为 LazyQueue 模式。

3.4.1 声明懒惰队列的方式

1)在添加队列的时候,添加 x-queue-mode参数即可设置队列为 Lazy 模式:

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

    @Beanpublic Queue queue3(){return QueueBuilder.durable("queue7") //队列名称.lazy() //开启懒惰模式.build();}

3)基于注解来声明队列并设置为 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);
}

4.0 消费者的可靠性

当 RabbitMQ 向消费者投递消息以后,需要知道消费者的处理状态如何。因为消息投递给消费者并不代表就一定被正确消费了,可能出现的故障有很多。

一旦发生上述情况,消息也会丢失。因此,RabbitMQ 必须知道消费者的处理状态,一旦消息处理失败才能重新投递消息。

4.1 消费者确认机制

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

  • ack:成功处理消息,RabbitMQ 从队列中删除该消息。

  • nack:消息处理失败,RabbitMQ 需要再次投递消息。

  • reject:消息处理失败并拒绝该消息,RabbitMQ 从队列中删除该消息。

2)一般 reject 方式用的较少,除非是消息格式有问题,那就是开发问题了。因此大多数情况下我们需要将消息处理的代码通过 try catch 机制捕获,消息处理成功时返回 ack,处理失败时返回 nack 。

由于消息回执的处理代码比较统一,因此 SpringAMQP 帮我们实现了消息确认。并允许我们通过配置文件设置 ACK 处理方式,有三种模式:

  • none:不处理。即消息投递给消费者后立刻 ack,消息会立刻从 MQ 删除。非常不安全,不建议使用

  • manual:手动模式。需要自己在业务代码中调用 api,发送 ack 或 reject,存在业务入侵,但更灵活

  • auto:自动模式。SpringAMQP 利用 AOP 对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回 ack. 当业务出现异常时,根据异常判断返回不同结果:

    • 如果是业务异常,会自动返回 nack;

    • 如果是消息处理或校验异常,自动返回 reject;

因此,推荐使用 aotu 自动模式。

application 配置如下:

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

在开启消费者确认机制时,手动抛出异常模拟接收信息失败时:

1)发送消息:

    // 首先引入依赖@Autowiredprivate RabbitTemplate rabbitTemplate;@Testvoid contextLoads() {//发送Object类型的消息testSendMessage();}@Testpublic void testSendMessage(){//发送消息rabbitTemplate.convertAndSend("yt","xbs","hello rabbitmq");}

2)接收消息:

    @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "wyt",durable = "true",arguments = @Argument(name = "x-queue-mode", value = "lazy")),exchange = @Exchange(name = "yt",type = ExchangeTypes.DIRECT),key = "xbs"))public void receiveMessage(String massage) throws InterruptedException {log.info("sendMessage发送的消息为: " + massage);//模拟接送消息失败throw new RuntimeException("模拟接收消息失败");}

执行结果:

由于抛出了异常,则返回给 MQ 为 nack,就会重复发送消息给客户端:

在队列中,由于消息没有被正确处理,则消息会一直在队列中且不断发送消息给客户端:

4.2 失败重试机制

当消费者出现异常后,消息会不断 requeue(重入队)到队列,再重新发送给消费者。如果消费者再次执行依然出错,消息会再次 requeue 到队列,再次投递,直到消息处理成功为止。

极端情况就是消费者一直无法执行成功,那么消息 requeue 就会无限循环,导致mq的消息处理飙升,带来不必要的压力:

应对上述情况 Spring 又提供了消费者失败重试机制:在消费者出现异常时利用本地重试,而不是无限制的 requeue 到 mq 队列。

修改 application.yml 文件,添加内容:

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

再来尝试手动抛出异常模拟接收消息失败:

结果如下:

MQ 向接收者发送了三次信息,接着进程结束。

此时队列中的信息不存在了,丢失了:

结论:

  • 开启本地重试时,消息处理过程中抛出异常,不会 requeue 到队列,而是在消费者本地重试

  • 重试达到最大次数后,Spring 会返回 reject,消息会被丢弃。

4.3 失败处理策略

在之前的测试中,本地测试达到最大重试次数后,消息会被丢弃。这在某些对于消息可靠性要求较高的业务场景下,显然不太合适了。

因此 Spring 允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由 MessageRecovery 接口来定义的,它有3个不同实现:

  • RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式

  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队

  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

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

代码实现:

1)定义处理失败消息的交换机和队列:

@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 {@Beanpublic DirectExchange errorMessageExchange(){return new DirectExchange("error.direct");}@Beanpublic Queue errorQueue(){return new Queue("error.queue", true);}@Beanpublic Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");}@Beanpublic MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");}
}

当重试失败之后,不会直接将消息丢失而是交给专门接收失败信息的交换机,再由专门的消费者进行消费,比如说监听到队列中有接收失败的信息,将其写入日志中等等处理的方法。

测试:

1)发送消息:

    // 首先引入依赖@Autowiredprivate RabbitTemplate rabbitTemplate;@Testvoid contextLoads() {//发送Object类型的消息testSendMessage();}@Testpublic void testSendMessage(){//发送消息rabbitTemplate.convertAndSend("yt","xbs","hello rabbitmq");}

2)接收消息:

接收消息之后,手动抛出异常模拟接收消息失败。

    @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "wyt",durable = "true",arguments = @Argument(name = "x-queue-mode", value = "lazy")),exchange = @Exchange(name = "yt",type = ExchangeTypes.DIRECT),key = "xbs"))public void receiveMessage(String massage) throws InterruptedException {log.info("sendMessage发送的消息为: " + massage);//模拟接送消息失败throw new RuntimeException("模拟接收消息失败");}

执行结果:

在本地重试三次发送失败之后,则将该消息交给 “errorMessageExchange” 交换机,再路由给 “errorMessageQueue” 队列,后续可以监听该队列进行接收消息再做处理。

5.0 使用 DelayExchange 插件

RabbitMQ 官方推出了一个插件,原生支持延迟消息功能。该插件的原理是设计了一种支持延迟消息功能的交换机,当消息投递到交换机后可以暂存一定时间,到期后再投递到队列中。

5.1 安装 DelayExchange 插件

基于 Docker 安装,所以需要先查看 RabbitMQ 的插件目录对应的数据卷。

docker volume inspect mq-plugins

结果如下:

[{"CreatedAt": "2024-06-19T09:22:59+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/mq-plugins/_data","Name": "mq-plugins","Options": null,"Scope": "local"}
]

插件目录被挂载到了 docker volume inspect mq-plugins 这个目录,我们上传插件到该目录下。

DelayExchange 插件下载地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

接着文件放到docker volume inspect mq-plugins 这个目录中。

接下来执行命令,安装插件:

docker exec -it mq rabbitmq-plugins enable rabbitmq_delayed_message_exchange

安装结果:

5.2 声明延迟交换机

1)基于注解方式:

@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "delay.queue", durable = "true"),exchange = @Exchange(name = "delay.direct", delayed = "true"),key = "delay"
))
public void listenDelayMessage(String msg){log.info("接收到delay.queue的延迟消息:{}", msg);
}

2)基于 @Bean 的方式:

package com.itheima.consumer.config;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Slf4j
@Configuration
public class DelayExchangeConfig {@Beanpublic DirectExchange delayExchange(){return ExchangeBuilder.directExchange("delay.direct") // 指定交换机类型和名称.delayed() // 设置delay的属性为true.durable(true) // 持久化.build();}@Beanpublic Queue delayedQueue(){return new Queue("delay.queue");}@Beanpublic Binding delayQueueBinding(){return BindingBuilder.bind(delayedQueue()).to(delayExchange()).with("delay");}
}

5.3 发送延迟消息

发送消息时,必须通过 x-delay 属性设定延迟时间:

@Test
void testPublisherDelayMessage() {// 1.创建消息String message = "hello, delayed message";// 2.发送消息,利用消息后置处理器添加消息头rabbitTemplate.convertAndSend("delay.direct", "delay", message, new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {// 添加延迟消息属性message.getMessageProperties().setDelay(5000);return message;}});
}

测试:

1)接收消息:

    @AutowiredMessageConverter messageConverter;@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "delay.queue",durable = "true",arguments = @Argument(name = "x-queue-mode", value = "lazy")),exchange = @Exchange(name = "delay.exchange",type = ExchangeTypes.DIRECT,delayed = "true"),key = "xbs"))public void receiveMessage(Message massage) throws InterruptedException {log.info("sendMessage发送的消息ID为: " + massage.getMessageProperties().getMessageId());log.info("sendMessage发送的消息延迟时间为: " + massage.getMessageProperties().getDelayLong());String str = (String) messageConverter.fromMessage(massage);log.info("sendMessage发送的消息内容为: " + str);}

2)发送消息:

    // 首先引入依赖@Autowiredprivate RabbitTemplate rabbitTemplate;@Testvoid contextLoads() {//发送Object类型的消息testSendDelayMessage();}@Testvoid testSendDelayMessage(){//发送消息rabbitTemplate.convertAndSend("delay.exchange", "xbs", "hello rabbitmq delay message", new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {//先设置唯一IDmessage.getMessageProperties().setMessageId("123456");//设置延迟发送时间message.getMessageProperties().setDelayLong(5000L);return message;}});}

执行结果:

注意:

延迟消息插件内部会维护一个本地数据库表,同时使用 Elang Timers 功能实现计时。如果消息的延迟时间设置较长,可能会导致堆积的延迟消息非常多,会带来较大的 CPU 开销,同时延迟消息的时间会存在误差。

因此,不建议设置延迟时间过长的延迟消息。

相关文章:

RabbitMQ 篇-深入了解延迟消息、MQ 可靠性(生产者可靠性、MQ 可靠性、消费者可靠性)

??博客主页:【_-CSDN博客】** 感谢大家点赞??收藏评论** 文章目录 ???1.0 RabbitMQ 的可靠性 ? ? ? ? 2.0 发送者的可靠性 ? ? ? ? 2.1 生产者重试机制 ? ? ? ? 2.2 生产者确认机制 ? ? ? ? 2.2.1 开启生产者确认机制 ? ? ? ? 2.2…...

Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解

文章目录 前言原型模式一、浅拷贝1、案例2、引用数据类型 二、深拷贝1、重写clone()方法2、序列化 总结 前言 先看一下传统的对象克隆方式: 原型类: public class Student {private String name;public Student(String name) {this.name name;}publi…...

LightRAG - 更快更便宜的GraphRAG

检索增强生成(Retrieval-Augmented Generation, RAG)已经成为提升大型语言模型(LLMs)能力的重要方法之一,通过整合外部知识,显著改善了生成内容的质量和相关性。 RAG 的局限性 传统的 RAG 系统虽然表现优…...

基于STM32的智能风扇控制系统

基于STM32的智能风扇控制系统 持续更新,欢迎关注!!! ** 基于STM32的智能风扇控制系统 ** 近几年,我国电风扇市场发展迅速,产品产出持续扩张,国家产业政策鼓励电风扇产业向高技术产品方向发展,国内企业新增投资项目投…...

Java面试问答FAQ

目录: 1、post为什么会发送两次请求?2、单核CPU支持多线程吗?3、ConcurrentHashMap 如何保证线程的安全性? 1、post为什么会发送两次请求? A:那是因为浏览器的安全策略(同源策略)决…...

PHP中类名加双冒号的作用

在 PHP 中,类名加双冒号(::) 是一种用于访问类的静态成员和常量的语法。它也可以用来调用类的静态方法和访问 PHP 的类相关关键词(如 parent、self 和 static)。以下是详细的解释和用法。 1. 用途概述 :: 被称为作用域…...

[极客大挑战 2019]PHP

访问www.zip拿到源码. 绕过这三处. 构造exp <?php class Name{private $username admin;private $password 100;}$select new Name();$resserialize($select); echo $res ?>O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin"…...

【versal】【petalinux】添加LED驱动

versal 添加LED驱动 ` 提示:本文使用外部kernel与uboot`一、LED1.1 LED功能1.2 LED节点1.3 LED操作命令1.3.1 点LED1.3.2 关闭LED二、LED驱动2.1 驱动文件2.2 设备树兼容属性三、 LED设备树配置3.1 设备树配置信息3.2 设备树配置信息讲解四、提示4.1 正确4.2 错误4.3提示:本文…...

【前端】JavaScript中的字面量概念与应用详解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;字面量1. 数字字面量2. 字符串字面量3. 布尔字面量4. 空值字面量&#xff08;null&#xff09;5. 对象字面量6. 数组字面量7. 正则表达式字面量8. 特殊值字面量9. 函数字…...

工作学习:切换git账号

概括 最近工作用的git账号下发下来了&#xff0c;需要切换一下使用的账号。因为是第一次弄&#xff0c;不熟悉&#xff0c;现在记录一下。 打开设置 路径–git—git remotes&#xff0c;我这里选择项是Manage Remotes&#xff0c;点进去就可以了。 之后会出现一个输入框&am…...

python-解决一元一次方程

【题目】解决一元一次方程数学问题 【问题描述】 我们要解决一个较为复杂的一元一次方程数学问题&#xff0c;方程形如&#xff1a;ax b c&#xff0c;其中a、b、c为已知常数&#xff0c;x为未知数。 现在我们要通过编程的方式解决这个问题&#xff0c;即找到方程的解x。 【…...

在线音乐播放器 —— 测试报告

自动化脚本源代码&#xff1a;Java: 利用Java解题与实现部分功能及小项目的代码集合 - Gitee.com 目录 前言 一、项目简介 1.项目背景 2.应用技术 &#xff08;1&#xff09;后端开发 &#xff08;2&#xff09;前端开发 &#xff08;3&#xff09;数据库 二、项目功能…...

6.算法移植第六篇 YOLOV5/rknn生成可执行文件部署在RK3568上

接上一篇文章best-sim.rknn模型生成好后&#xff0c;我们要将其转换成可执行文件运行在RK3568上&#xff0c;这一步需要在rknpu上进行&#xff0c;在强调一遍&#xff01;&#xff01;rknpu的作用是可以直接生成在开发板上运行的程序 退出上一步的docker环境 exit1.复制best-…...

【贪心算法第七弹——674.最长连续递增序列(easy)】

目录 1.题目解析 题目来源 测试用例 2.算法原理 3.实战代码 代码分析 1.题目解析 题目来源 674.最长递增子序列——力扣 测试用例 2.算法原理 贪心思路 3.实战代码 class Solution { public:int findLengthOfLCIS(vector<int>& nums) {int n nums.size();in…...

剖析前后端 API 接口参数设计:JSON 数据结构化全攻略

在当今软件开发领域&#xff0c;前后端分离架构已成为主流趋势。而 API 接口作为前后端之间数据交互的桥梁&#xff0c;其设计的合理性对系统的可维护性和扩展性起着至关重要的作用。JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&…...

【RISC-V CPU debug 专栏 2.3 -- Run Control】

文章目录 Run ControlHart 运行控制状态位状态信号操作流程时间与实现注意事项Run Control 在 RISC-V 调试架构中,运行控制模块通过管理多个状态位来对硬件线程(harts)的执行进行调节和控制。这些状态位帮助调试器请求暂停或恢复 harts,并在 hart 复位时进行控制。以下是运…...

力扣887:鸡蛋掉落问题

题目描述&#xff1a; 给你 k 枚相同的鸡蛋&#xff0c;并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。 已知存在楼层 f &#xff0c;满足 0 < f < n &#xff0c;任何从 高于 f 的楼层落下的鸡蛋都会碎&#xff0c;从 f 楼层或比它低的楼层落下的鸡蛋都不会破。…...

Matlab与python数据处理对比

MATLAB 和 Python 都是数据分析和科学计算中常用的编程语言&#xff0c;各自有其优点和应用场景。它们在数据处理方面有许多相似之处&#xff0c;但也有一些关键差异。以下是 MATLAB 和 Python 在数据处理方面的对比与总结。 ### 1. **语法与语言特性** - **MATLAB**: - 以…...

【系统架构设计师】高分论文:论软件架构的生命周期

更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 摘要正文摘要 2022 年5月,本人所在的某集团公司承接了财务共享服务平台综合管理系统的项目开发,该项目主要实现财务系统主流业务的集成共享。本人担任项目组成员中的系统架构设计师一职,全面负责项目的全生命周…...

gitlab工作笔记

gitlab常用操作 gitlab常用笔记docker 安装模式pull imagerun一个gitlab container atttach入containerdocker run 之后要等几分钟安装之后的初始配置初始密码在哪里 &#xff1a;第一次登录创建和检查第一个工程能否正常clone gitlab常用笔记 装过几次gitlab&#xff0c;但每…...

shell完结

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…...

优维HAO案例:全球TOP15汽车零件供应商「IT运维自动化」创新工程

撰文&#xff1a;鹿小U / 制图&#xff1a;脾气超好 又是一家很厉害的客户。 YADT是全(hu)球(zhēn)领(ji)先(sh)的汽车座椅供应商&#xff0c;拥有从汽车座椅零部件到整椅的完整生产制造能力&#xff0c;为中国几乎所有的汽车制造商提供汽车整椅产品和服务。 YADT在国内拥…...

语义版本控制

注意&#xff1a; 本文内容于 2024-11-27 22:25:05 创建&#xff0c;可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容&#xff0c;请访问原文地址&#xff1a;语义版本控制。感谢您的关注与支持&#xff01; 由于自己平时喜欢写点小玩意&#xff0c;自然而…...

C++设计模式行为模式———策略模式

文章目录 一、引言二、策略模式三、总结 一、引言 策略模式是一种行为设计模式&#xff0c; 它能让你定义一系列算法&#xff0c; 并将每种算法分别放入独立的类中&#xff0c; 以使算法的对象能够相互替换。与模板方法模式类似&#xff0c;都是以扩展的方式来支持未来的变化。…...

Pgsql:json字段查询与更新

1.查询json字段的值 SELECT attribute_data->>设施类别 mycol, * FROM gis_coord_data WHERE attribute_data->>设施类别阀门井 查询结果如下&#xff1a; 2.更新json字段中的某个属性值 UPDATE gis_coord_data SET attribute_data(attribute_data::jsonb ||{&quo…...

鸿蒙面试 --- 性能优化

性能优化可以从三个方面入手 感知流畅、渲染性能、运行性能 感知流畅 在应用开发中&#xff0c;动画可以为用户界面增添生动、流畅的交互效果&#xff0c;提升用户对应用的好感度。然而&#xff0c;滥用动画也会导致应用性能下降&#xff0c;消耗过多的系统资源&#xff0c;…...

【逐行注释】自适应观测协方差R的AUKF(自适应无迹卡尔曼滤波,MATLAB语言编写),附下载链接

文章目录 自适应R的UKF逐行注释的说明运行结果部分代码各模块解释 自适应R的UKF 自适应无迹卡尔曼滤波&#xff08;Adaptive Unscented Kalman Filter&#xff0c;AUKF&#xff09;是一种用于状态估计的滤波算法。它是基于无迹卡尔曼滤波&#xff08;Unscented Kalman Filter&…...

mac下安装Ollama + Open WebUI + Llama3.1

本文介绍mac下安装Ollama Open WebUI Llama3.1 8b具体步骤。 目录 推荐配置Ollama Open WebUI Llama3.1简介安装Ollama安装Open WebUI 推荐配置 m1以上芯片&#xff0c;16g内存&#xff0c;20g以上硬盘空间 Ollama Open WebUI Llama3.1简介 Ollama: 下载&#xff0c;管理…...

Python Tornado框架教程:高性能Web框架的全面解析

Python Tornado框架教程&#xff1a;高性能Web框架的全面解析 引言 在现代Web开发中&#xff0c;选择合适的框架至关重要。Python的Tornado框架因其高性能和非阻塞I/O特性而备受青睐。它特别适合处理大量并发连接的应用&#xff0c;比如聊天应用、实时数据处理和WebSocket服务…...

QT-installEventFilter

installEventFilter 是 Qt 框架中的一个方法&#xff0c;用于在对象之间建立事件过滤机制。具体来说&#xff0c;它允许一个对象&#xff08;称为事件过滤器&#xff09;监视另一个对象&#xff08;称为被监视对象&#xff09;的事件&#xff0c;并在这些事件被处理之前对其进行…...

ZYNQ详解

ZYNQ是Xilinx公司推出的一系列SoC&#xff08;System-on-Chip&#xff0c;系统级芯片&#xff09;产品家族&#xff0c;它将传统的FPGA&#xff08;可编程逻辑器件&#xff09;与嵌入式处理器相结合&#xff0c;形成了一种集成了硬件和软件处理能力的单一芯片解决方案。以下是对…...

代码随想录打卡DAY21

算法记录第21天 [二叉树] 1.LeetCode 538. 把二叉搜索树转换为累加树 题目描述&#xff1a; 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原…...

LeetCode【代码随想录】刷题(数组篇)

704.二分查找 力扣题目链接 题目&#xff1a;给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 思路&#xff1a;简单的二…...

Python知识第十二天分享

假设有一个文件 num.txt&#xff0c;里面存储了如下的一些数字&#xff0c;内容如下&#xff1a; 10 18 30 11 12 15 编写一个 python 程序&#xff0c;读取文件中的内容&#xff0c;存储成 python 中的列表形式&#xff1a; sum 0 with open(./num.txt, r) as nu_f:list1 …...

[SWPUCTF 2021 新生赛]easy_md5

借鉴博客 [SWPUCTF 2021 新生赛]easy_md5_青少年ctf easymd5-CSDN博客 我们直接看见源码 <?php highlight_file(__FILE__);include flag2.php;if (isset($_GET[name]) && isset($_POST[password])){$name $_GET[name];$password $_POST[password];if ($name …...

C# 开发应用篇——C# 基于WPF实现数据记录导出excel详解

目录 引言 一、基于 EPPlus 库实现WPF导出EXCEL功能 项目准备 创建 WPF 界面 后端代码 EPPlus LicenseContext 属性设置 关键点说明 二、基于 ClosedXML 库实现WPF导出EXCEL功能 安装 ClosedXML 使用 ClosedXML 创建和操作 Excel 文件 读取 Excel 文件 常…...

MongoDB相关问题

视频教程 【GeekHour】20分钟掌握MongoDB Complete MongoDB Tutorial by Net Ninja MongoDB开机后调用缓慢的原因及解决方法 问题分析&#xff1a; MongoDB开机后调用缓慢&#xff0c;通常是由于以下原因导致&#xff1a; 索引重建&#xff1a; MongoDB在启动时会重建索引…...

从入门到精通数据结构----四大排序(上)

目录 首言&#xff1a; 1. 插入排序 1.1 直接插入排序 1.2 希尔排序 2. 选择排序 2.1 直接选择排序 2.2 堆排序 3. 交换排序 3.1 冒泡排序 3.2 快排 结尾&#xff1a; 首言&#xff1a; 本篇文章主要介绍常见的四大排序&#xff1a;交换排序、选择排序、插入排序、归并排…...

数据结构-堆的实现和应用

目录 1.堆的概念 2.堆的构建 3.堆的实现 4.堆的功能实现 4.1堆的初始化 4.2堆的销毁 4.3堆的插入 4.3.1向上调整 4.4堆的删除 4.4.1向下调整法 ​编辑4.5取堆顶 5. 向上调整法和向下调整法比较 6.堆的应用 6.1TOP-K问题 6.2TOP-K思路 6.2.1用前n个数据来建堆 6.…...

Spring MVC

1. 用户发起请求 用户行为&#xff1a;用户在浏览器中输入URL或点击链接&#xff0c;向Web服务器&#xff08;如Tomcat&#xff09;发起一个HTTP请求。请求传输&#xff1a;请求被发送到Web容器&#xff0c;Web容器根据配置将请求转发给DispatcherServlet。 2. 前端控制器&am…...

linux ubuntu的脚本知

目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 九、后台进程的控制 N、其它可以参考的网址 脚本功能强大…...

Spring Boot 动态数据源切换

背景 随着互联网应用的快速发展&#xff0c;多数据源的需求日益增多。Spring Boot 以其简洁的配置和强大的功能&#xff0c;成为实现动态数据源切换的理想选择。本文将通过具体的配置和代码示例&#xff0c;详细介绍如何在 Spring Boot 应用中实现动态数据源切换&#xff0c;帮…...

Design Linear Filters in the Frequency Domain (MATLAB帮助文档)

Design Linear Filters in the Frequency Domain 这个帮助文档写得很好&#xff0c;简单明了&#xff0c;一句废话没有。 This topic describes functions that perform filtering in the frequency domain. 2-D Finite Impulse Response (FIR) Filters The Image Processi…...

Java知识及热点面试题总结(二)

1、什么是死锁(deadlock)? 两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。 如何避免线程死锁? 只要破坏产生死锁的四个条件中的其中一个就可以了。 破坏互斥条件&#xff1a;这个条件我们没有办法…...

开源加密库mbedtls及其Windows编译库

目录 1 项目简介 2 功能特性 3 性能优势 4 平台兼容性 5 应用场景 6 特点 7 Windows编译 8 编译静态库及其测试示例下载 1 项目简介 Mbed TLS是一个由ARM Maintained的开源项目&#xff0c;它提供了一个轻量级的加密库&#xff0c;适用于嵌入式系统和物联网设备。这个项…...

架构01-演进中的架构

零、文章目录 架构01-演进中的架构 1、原始分布式时代&#xff1a;Unix设计哲学下的服务探索 &#xff08;1&#xff09;背景 时间&#xff1a;20世纪70年代末到80年代初计算机硬件&#xff1a;16位寻址能力、不足5MHz时钟频率的处理器、128KB左右的内存转型&#xff1a;从…...

npm-运行项目报错:A complete log of this run can be found .......npm-cache_logs\

1.问题 没有找到对应的某种依赖&#xff0c;node_modules出现问题。 2.解决 (1)查看对应依赖是否引入或者是由于合并分支错误 引入js或依赖不存在。谨慎删除依赖包 (2)查找对应引入依赖进行安装最后解决方法-删除依赖包清除缓存 npm cache clean --force (2)重新向同事引入…...

C++中的函数对象

C 中函数对象的定义和特点 定义&#xff1a;函数对象&#xff08;Function Object&#xff09;也叫仿函数&#xff08;Functor&#xff09;&#xff0c;是一个类&#xff0c;这个类重载了函数调用运算符()。当创建这个类的对象后&#xff0c;可以像使用函数一样使用这个对象&am…...

godot游戏引擎_瓦片集和瓦片地图介绍

在 Godot 中&#xff0c;TileSet 和 TileMap 是用于处理瓦片地图的两个关键概念&#xff0c;它们的作用和用途有明显的区别。以下是两者的详细对比&#xff1a; 1. TileSet&#xff08;瓦片集&#xff09; TileSet 是资源&#xff0c;定义瓦片的内容和属性。 特点&#xff1a…...

[C++ 核心编程]笔记 4.1 封装

4.1.1 封装的意义 封装是C面向对象三大特性之一 封装的意义: 将属性和行为作为一个整体&#xff0c;表现生活中的事物将属性和行为加以权限控制 封装意义一: 在设计类的时候&#xff0c;属性和行为写在一起&#xff0c;表现事物 语法: class 类名{ 访问权限: 属性 /行为 }…...