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://xiahunao.cn/news/3016011.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 函数的定义与调用

上文 C语言 函数概述 我们对函数进行了概述 本文 我们来说函数的定义和调用 C语言规定 使用函数之前&#xff0c;首先要对函数进行定义。 根据模块化程序设计思想&#xff0c;C语言的函数定义是互相平行、独立的&#xff0c;即函数定义不能嵌套 C语言函数定义 分为三种 有参函…

中国居民消费新特征:中枢回落,即时满足,去地产化

随着收入预期和财富效应的转变&#xff0c;居民更倾向于通过短期集中式的消费来获得即时满足的快乐&#xff0c;服务消费表现出了更强的韧性。服务消费强于商品消费、消费去地产化、汽车挑大梁的特征延续。 特征一&#xff1a;消费倾向高于2020-22年&#xff0c;低于2017-19年…

QX-mini51单片机学习(1)---电子电路基础

目录 1电平特性 2单片机io口简绍 3初识电容电阻 4初识电路原理图 5单片机最小系统结构 6单片机工作基本时序 1电平特性 单片机是一种数字集成芯片&#xff0c;数字电路中两种电平&#xff0c;高电平与低电平 高电平&#xff1a;5v 低电平&#xff1a;0v TTL电平信号…

非平衡数据处理-Tomek link算法介绍,代码和实战测评

作者Toby&#xff0c;来源公众号&#xff1a;Python风控模型&#xff0c;非平衡数据处理-Tomek link算法 概述 非平衡数据在金融风控领域、反欺诈客户识别、广告智能推荐和生物医疗中普遍存在。一般而言&#xff0c;不平衡数据正负样本的比例差异极大&#xff0c;如在Kaggle竞…

[华为OD]C卷 精准核算检测 100

题目&#xff1a; 为了达到新冠疫情精准防控的需要&#xff0c;为了避免全员核酸检测Q带来的浪费&#xff0c;需要精准圈定可 能被感染的人群。现在根据传染病流调以及大数据分析&#xff0c;得到了每个人之间在时间、空间上是 否存在轨迹的交叉现在给定一组确诊人员编号&…

多微信管理不过来?你需要一个微信神器

很多企业都在面临以下几个问题&#xff1a; 1、希望进行微信营销转型&#xff0c;但是不知道如何入手&#xff1b; 2、拥有多个微信号&#xff0c;但不想拿着多台手机&#xff0c;希望能够集中管理所有微信号&#xff1b; 3、希望使用app替代传统的营销体系&#xff0c;并确…

RS0108YQ20功能和参数介绍及高速数据传输中的优势

RS0108YQ20功能和参数介绍及高速数据传输中的优势-公司新闻-配芯易-深圳市亚泰盈科电子有限公司 RS0108YQ20是一款电平转换器&#xff0c;也称为电平移位器&#xff0c;它的主要功能是在不同的电源电压或逻辑电平之间提供双向信号转换。以下是RS0108YQ20的一些关键参数和功能特…

链表成环或多分枝问题

问题概述 这类问题主要和数论结合到一起。 这类问题主要的解决技巧就是画图&#xff0c;跟着图来写代码&#xff0c;不然代码5分钟&#xff0c;调试2小时。 因为每个节点可以指向下一个节点&#xff0c;那么两个节点就可以指向同一个节点&#xff1b;或者链表的末尾节点指向…

JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式

技术栈 1. 开发语言&#xff1a;JAVA 2. 数据库&#xff1a;MySQL 3. 后端框架&#xff1a;Spring boot 4. 前端框架&#xff1a;VUE2 5. 电子班牌&#xff1a; Android 7.1 6. 小程序&#xff1a;原生开发 7. 多学校Saas 模式 电子班牌是一款智慧校园管理工具&#xf…

自动语音识别

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

Linux动态库与静态库解析

文章目录 一、引言二、C/C源文件的编译过程三、静态库1、静态库的定义和原理2、静态库的优缺点3、静态库的创建和使用a、创建静态库b、使用静态库 四、动态库1、动态库的定义和原理2、动态库的优缺点3、动态库的创建和使用示例a、创建动态库b、使用动态库 五、动静态库的比较 一…

Python专题:一、安装步骤

1、下载地址&#xff1a;Welcome to Python.org 勾选这个add 其他的全部下一步即可。 运行出现这个即代表安装成功。 Python自带编辑器。 2、推荐使用的sublime 编辑器下载 全部下一步安装。

【QA】Java常见运算符

前言 本文主要讲述Java常见的运算符 运算符的概念 两个基本概念&#xff1a; 运算符&#xff1a;对字面量或者变量进行操作的符号 表达式&#xff1a;用运算符把字面量或者变量连接起来符合java语法的式子就可以称为表达式 示例&#xff1a; int a 10; int b 20; int …

2024.5.7

槽函数声明 private slots:void on_ed_textChanged();void on_pushButton_clicked(); }; 槽函数定义 void Widget::on_ed_textChanged()//文本框 {if(ui->ed1->text().length()>5&&ui->ed2->text().length()>5){ui->pushButton->setStyleSh…

鸿蒙OpenHarmony【基于Hi3516DV300开发板(时钟应用开发)】

概述 本文将介绍如何快速搭建基于OpenHarmony标准系统&#xff08;Hi3516DV300开发板&#xff09;的应用开发环境&#xff0c;并基于一个时钟APP示例逐步展示应用的创建、开发、调试和安装等流程。示例代码可以通过本链接获取。 时钟App是一款显示实时时间的应用&#xff0c;…

js实现json数据可编辑

背景 项目中有低代码平台&#xff0c;由于历史脏数据和非同步编辑的问题&#xff0c;偶尔会出现数据错乱的问题&#xff0c;希望有一个快捷的方式修改数据 之前在用Formily的时候有注意到designable/react 里面的json数据编辑功能非常不错如果能应用到项目里就完美了 design…

记录一个练手的js逆向password

很明显 请求加密了password 全局搜索 有个加密函数(搜不到的可以搜临近的其他的关键字 或者url参数) 搜索的时候一定要仔细分析 我就没有仔细分析 我搞了好久 又是xhr又是hook的(还没hook到) 我当时也是疏忽了 我寻思这个也不是js文件 直到后来 我怎么也找不到 我就猜想 不…

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

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

【Python爬虫实战入门】:全球天气信息爬取

文章目录 一、爬取需求二、所需第三方库2.1 简介 三、实战案例四、完整代码 一、爬取需求 目标网站&#xff1a;http://www.weather.com.cn/textFC/hb.shtml 需求&#xff1a;爬取全国的天气&#xff08;获取城市以及最低气温&#xff09; 目标url&#xff1a;http://www.weath…

C++ | Leetcode C++题解之第71题简化路径

题目&#xff1a; 题解&#xff1a; class Solution { public:string simplifyPath(string path) {auto split [](const string& s, char delim) -> vector<string> {vector<string> ans;string cur;for (char ch: s) {if (ch delim) {ans.push_back(mov…