【RabbitMQ高级特性】消息可靠性原理

news/2024/9/18 15:07:00/ 标签: rabbitmq

1. 消息确认机制

1.1 介绍

我们可以看到RabbitMQ的消息流转图:

当消息从Broker投递给消费者的时候会存在以下两种情况:

  1. consumer消费消息成功
  2. consumer消费消息异常

如果说RabbitMQ在每次将消息投递给消费者的时候就将消息从Broker中删除,此时如果消息处理异常,就会造成消息丢失的情况!因此RabbitMQ提供了消息确认机制(Message Acknowledge),消费者可以设置autoAck参数来进行确认:

  • 自动确认:当设置autoAck参数为true时,RabbitMQ就会将自己发送出去的消息置为确认,并从内存和硬盘上移除,不管消费者是否消费消息成功,适用于消息可靠性要求不高的场景
  • 手动确认:当设置autoAck参数为false时,RabbitMQ会等待消费者显示调用Basic.Ack命令,如果确认消费成功则进行删除消息操作,适用于消息可靠性较高的场景

当autoAck参数设置为false的时候,消息会被分为两部分:一部分是等待进行投递的消息,另一部分是已经投递但是还没有等到消费者回复的消息,其结构如下:

从RabbitMQ的Web管理平台也可以看到这两种状态:
image.png

1.2 手动确认方法

消费者在收到消息之后,可以进行确认应答,也可以进行拒绝确认,RabbitMQ也提供的不同的确认方法API,在消费者端可以使用channel的以下三种不同API进行应答:

  1. 肯定应答:channel.basicAck(long deliveryTag, boolean multiple)

表示消息已经被消费者正确处理,通知RabbitMQ可以将消息进行移除了
参数说明:

  • deliveryTag:是消息的唯一标识,是一个64位递增的长整数,该参数由每个channel进行单独维护,即在每个channel内部deliveryTag是不重复的
  • multiple:是否进行批量确认,在某些情况下为了减少网络传输带宽,可以对连续的多个deliveryTag进行批量确认,当值设置为true的时候则会将ack<=deliveryTag的消息全部确认;如果值设置为false则只会将对应deliveryTag的消息进行确认
  1. 否定确认:channel.basicReject(long deliveryTag, boolean requeue)

表示消费者拒绝该消息
参数说明:

  • deliveryTag:参考basicAck
  • requeue:表示拒绝该消息之后该消息如何处理,如果设置为true,则RabbitMQ会重新将该消息放入队列,以便投递给下一个订阅的消费者;如果设置为false,则RabbitMQ会将该消息从队列中移除
  1. 否定确认:channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)

表示消费者拒绝该消息,并且可以批量拒绝消息
参数说明(参考上方)

1.3 代码演示

下面我们基于Spring-AMQP演示消息的确认机制,该确认机制有三种模式可以配置(需要注意与上述client模式有些不同):

1.3.1 NONE模式

该模式类似于上述讲的自动确认模式:即只要Broker将消息投递给消费者就会删除队列中的消息,而不管消费者有没有消费成功,可能会造成消息丢失场景!

  1. 配置确认机制为NONE模式:
spring:application:name: mq-advancedrabbitmq:username: guestpassword: guesthost: 127.0.0.1port: 5672virtual-host: springboot-mqlistener:simple:acknowledge-mode: NONE # NONE模式
  1. 发送消息
@RequestMapping("/none")
public String testNone() {rabbitTemplate.convertAndSend("", QueueConstant.ACK_QUEUE, "test none mode");return "消息发送成功!";
}
  1. 监听消息
@Component
public class AckListener {@RabbitListener(queues = QueueConstant.ACK_QUEUE)public void ackListener(Message message, Channel channel) {long deliveryTag = message.getMessageProperties().getDeliveryTag();String body = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("接收到消息: " + body + " deliveryTag: " + deliveryTag);System.out.println("开始处理消息...");int ret = 3 / 0;System.out.println("消息处理完毕...");}
}

此时就会出现以下情况:在消费者中抛出异常,但是RabbitMQ中消息已经丢失!

1.3.2 AUTO模式(默认)

该模式作用如下:

  • 当消费者业务代码处理正常时就会对消息进行确认
  • 但是如果消费者业务代码中抛出了异常,就会对消息进行否定确认并重新投递
  1. 配置确认机制为AUTO模式:
spring:application:name: mq-advancedrabbitmq:username: guestpassword: guesthost: 127.0.0.1port: 5672virtual-host: springboot-mqlistener:simple:acknowledge-mode: AUTO # AUTO模式
  1. 发送消息
@RequestMapping("/none")
public String testNone() {rabbitTemplate.convertAndSend("", QueueConstant.ACK_QUEUE, "test none mode");return "消息发送成功!";
}
  1. 监听消息
@Component
public class AckListener {@RabbitListener(queues = QueueConstant.ACK_QUEUE)public void ackListener(Message message, Channel channel) {long deliveryTag = message.getMessageProperties().getDeliveryTag();String body = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("接收到消息: " + body + " deliveryTag: " + deliveryTag);System.out.println("开始处理消息...");int ret = 3 / 0;System.out.println("消息处理完毕...");}
}

此时就会出现以下情况:在消费者中抛出异常,但是消息不会丢失,而是源源不断投递给可用的消费者!
image.png

1.3.3 MANUAL模式

该模式就可以进行手动确认:

  1. 配置确认机制为MANUAL模式:
spring:application:name: mq-advancedrabbitmq:username: guestpassword: guesthost: 127.0.0.1port: 5672virtual-host: springboot-mqlistener:simple:acknowledge-mode: MANUAL # MANUAL模式
  1. 发送消息
@RequestMapping("/none")
public String testNone() {rabbitTemplate.convertAndSend("", QueueConstant.ACK_QUEUE, "test none mode");return "消息发送成功!";
}
  1. 监听消息
@Component
public class AckListener {@RabbitListener(queues = QueueConstant.ACK_QUEUE)public void ackListener(Message message, Channel channel) throws IOException {long deliveryTag = message.getMessageProperties().getDeliveryTag();try {String body = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("接收到消息: " + body + " deliveryTag: " + deliveryTag);System.out.println("开始处理消息...");int ret = 3 / 0;System.out.println("消息处理完毕...");channel.basicAck(deliveryTag, false);} catch (Exception e) {channel.basicReject(deliveryTag, true);}}
}

此时就会出现以下情况:如果处理成功,就会进行basicAck肯定确认,但是如果捕获到了异常就进行拒绝确认,并将消息重新入队投递给下一个消费者使用!
image.png

2. 持久化机制

2.1 介绍

我们再次回看RabbitMQ的消息流转图:

前面我们通过消息确认机制保证了Broker能够将消息可靠地投递给Consumer消费者端,但是现在还存在一个问题:当消息存储在Broker中,但是RabbitMQ服务器遇到断电重启的情况如何保证将消息恢复呢?RabbitMQ就提供了 持久化机制 ,在RabbitMQ中有以下三种持久化:

  1. 队列持久化
  2. 交换机持久化
  3. 消息持久化

2.2 队列持久化

队列的持久化是通过在声明队列的时候设置参数durable为true实现的

  • 如果队列不进行持久化,那么在重启的时候关于队列的元数据信息就会丢失(此时哪怕消息进行了持久化也无法恢复消息了,因为消息保存在队列中)
  • 如果将队列设置为持久化,此时队列相关的元数据就可以从硬盘上进行恢复,但是并不能保证内部的消息不丢失,如果想要让消息不丢失,还需要设置消息的持久化

我们之前所创建队列的代码默认设置为持久化:

/*** 声明持久化队列*/
@Bean("persistQueue")
public Queue persistQueue() {return QueueBuilder.durable(QueueConstant.PERSIST_QUEUE).build();
}

追踪durable方法源码:
image.png
继续追踪setDurable方法源码可以发现默认是进行持久化的!
image.png
如果我们想要设置队列为非持久化,可以使用如下代码:

/*** 声明非持久化队列*/
@Bean("nonPersistQueue")
public Queue nonPersistQueue() {return QueueBuilder.nonDurable(QueueConstant.NON_PERSIST_QUEUE).build();
}

2.3 交换机持久化

交换机的持久化是通过在声明交换机的时候设置参数durable为true实现的
同队列一样,只有设置为持久化,才会将有关交换机的元数据信息保存在硬盘上,在重启RabbitMQ服务器的时候才会读取然后恢复交换机数据信息,我们可以通过在声明交换机的时候设置durable(true | false)显示声明是否持久化:

/*** 声明持久化交换机*/
@Bean("persistDirectExchange")
public DirectExchange persistDirectExchange() {return ExchangeBuilder.directExchange(ExchangeConstant.PERSIST_EXCHANGE).durable(true).build();
}
/*** 声明非持久化交换机*/
@Bean("nonPersistDirectExchange")
public DirectExchange nonPersistDirectExchange() {return ExchangeBuilder.directExchange(ExchangeConstant.NON_PERSIST_EXCHANGE).durable(false).build();
}

2.4 消息持久化

如果想要让消息进行持久化,我们就需要设置消息的投递模式MessageProperties.deliveryModePERSISITENT,使用RabbitTemplate发送持久化消息代码如下:

@RestController
public class PersistController {@Resourceprivate RabbitTemplate rabbitTemplate;@RequestMapping("/persist")public String sendPersist() {MessageProperties messageProperties = new MessageProperties();messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);Message message = new Message("persist info".getBytes(), messageProperties);rabbitTemplate.convertAndSend(ExchangeConstant.PERSIST_EXCHANGE, "persist", message);return "发送成功!";}
}

如果想要设置消息的不持久化,则对应代码如下:

@RequestMapping("/nonPersist")
public String sendNonPersist() {MessageProperties messageProperties = new MessageProperties();messageProperties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);Message message = new Message("non-persist info".getBytes(), messageProperties);rabbitTemplate.convertAndSend(ExchangeConstant.NON_PERSIST_EXCHANGE, "non-persist", message);return "发送成功!";
}

3. 发送方确认机制

3.1 介绍

我们再次回看RabbitMQ的消息流转图:

现在我们通过消息确认机制保证从Broker到Consumer链路上消息可靠性,通过持久化机制保证Broker内部消息可靠性,但是此时还存在着问题:如果说消息在生产者投递给Broker过程中由于网络等问题导致消息丢失、或者Broker处于重启等服务不可用状态该怎么办呢?即生产者如何保证消息能够可靠到达RabbitMQ服务器?
RabbitMQ为了解决这个问题,提供了以下两种机制:

  1. 事务机制(性能较低,此处不介绍)
  2. 发送方确认机制(Publisher Confirm)

在发送方确认机制中,可以配置以下两种模式:

  1. confirm确认模式:

确认模式指的是在发送者发送消息时设置一个ConfirmCallback的监听器,无论消息是否到达对应的Exchange,这个监听都会执行。如果消息到达对应的Exchange,则对应ACK参数为true,反之没有到达Exchange则ACK参数为false

  1. return回退模式

我们期待Exchange能够依据特定的路由规则将消息投递给对应的队列,但是如果设置的路由键错误或者队列不存在时导致消息迟迟没有投递给队列,此时我们希望可以将消息退回给生产者,退回模式指的是在发送者发送消息时设置一个ReturnsCallback的监听器对退回的消息进行处理

🔑 总结:确认模式和退回模式并不是互斥的,两者可以同时设置!确认模式主要解决的是保证消息可靠到达Exchange的问题,而退回模式保证的是消息可靠到达Queue的问题

3.2 代码演示

3.2.1 Confirm确认模式

配置步骤如下:

  1. 进行confirm模式配置
  2. 在发送方设置ConfirmCallback并发送消息
  3. 测试

接下来看实现步骤:

  1. 配置confirm模式开启
spring:application:name: mq-advancedrabbitmq:username: guestpassword: guesthost: 127.0.0.1port: 5672virtual-host: springboot-mqpublisher-confirm-type: correlated # 开启发送者确认模式
  1. 声明队列与交换机
public interface ExchangeConstant {String CONFIRM_EXCHANGE = "confirm.exchange";
}
public interface QueueConstant {String CONFIRM_QUEUE = "confirm.queue";
}
@Configuration
public class RabbitMQConfig {/*** 声明发送者确认模式队列*/@Bean("confirmQueue")public Queue confirmQueue() {return QueueBuilder.durable(QueueConstant.CONFIRM_QUEUE).build();}/*** 声明发送者确认模式交换机*/@Bean("confirmExchange")public DirectExchange confirmExchange() {return ExchangeBuilder.directExchange(ExchangeConstant.CONFIRM_EXCHANGE).durable(true).build();}/*** 声明发送者确认模式交换机*/@Bean("confirmBinding")public Binding confirmBinding(@Qualifier("confirmExchange") DirectExchange exchange, @Qualifier("confirmQueue") Queue queue) {return BindingBuilder.bind(queue).to(exchange).with("confirm");}
}
  1. 编写发送者代码
@Configuration
public class RabbitTemplateConfig {@Bean("rabbitTemplate")public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {return new RabbitTemplate(connectionFactory);}@Bean("confirmRabbitTemplate")public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);// 设置confirm回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("执行了confirm方法...");if (b) {// 到达交换机System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId())  + "到达交换机");} else {// 没有到达交换机System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId()) + "没有到达交换机, 原因是: " + s);}}});return rabbitTemplate;}
}
@RestController
public class ConfirmController {@Resource(name = "confirmRabbitTemplate")private RabbitTemplate rabbitTemplate;@RequestMapping("/confirm")public String confirm() {// 发送消息CorrelationData correlationData = new CorrelationData("1");rabbitTemplate.convertAndSend(ExchangeConstant.CONFIRM_EXCHANGE, "confirm", "test confirm...", correlationData);return "发送消息成功!";}
}
  1. 测试接口

image.png
发现如果交换机名称设置正确则当消息到达交换机时回调被执行,我们尝试设置一个不存在的交换机名称查看现象:
image.png
此时就会走没有到达交换机的逻辑,此处就可以进行重新投递消息等业务逻辑!

💡 答疑解惑:为什么此处我们明确注入一个自己创建出来的RabbitTemplate,而不使用Spring提供的呢?有以下两点原因:

  1. 这是因为Spring默认配置Bean为单例的,因此如果使用Spring提供的RabbitTemplate设置回调函数则会影响其余接口同样使用回调
  2. 我们不能重复在controller层代码中重复多次调用setConfirmCallback回调,因为明确规定每个RabbitTemplate只能设置一次ConfirmCallback

3.2.2 Return退回模式

配置步骤如下:

  1. 进行return模式配置
  2. 在发送方设置setMandatory(true)表示进行退回
  3. 设置ReturnsCallback回调逻辑并发送消息
  4. 测试

接下来看实现步骤:

  1. 配置return模式开启(同confirm模式一致)
spring:application:name: mq-advancedrabbitmq:username: guestpassword: guesthost: 127.0.0.1port: 5672virtual-host: springboot-mqpublisher-confirm-type: correlated # 开启发送者确认模式
  1. 设置ReturnsCallback回调逻辑并发送消息
@Configuration
public class RabbitTemplateConfig {@Bean("rabbitTemplate")public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {return new RabbitTemplate(connectionFactory);}@Bean("confirmRabbitTemplate")public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);// 设置confirm回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("执行了confirm方法...");if (b) {// 到达交换机System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId())  + "到达交换机");} else {// 没有到达交换机System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId()) + "没有到达交换机, 原因是: " + s);}}});// 设置return回调rabbitTemplate.setMandatory(true);rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {@Overridepublic void returnedMessage(ReturnedMessage returnedMessage) {System.out.println("收到回退消息: " + returnedMessage);}});return rabbitTemplate;}
}
@RestController
public class ConfirmController {@Resource(name = "confirmRabbitTemplate")private RabbitTemplate rabbitTemplate;@RequestMapping("/confirm")public String confirm() {// 发送消息CorrelationData correlationData = new CorrelationData("1");rabbitTemplate.convertAndSend(ExchangeConstant.CONFIRM_EXCHANGE, "confirm", "test confirm...", correlationData);return "发送消息成功!";}@RequestMapping("/returns")public String returns() {// 发送消息CorrelationData correlationData = new CorrelationData("2");rabbitTemplate.convertAndSend(ExchangeConstant.CONFIRM_EXCHANGE, "confirm", "test return...", correlationData);return "发送消息成功!";}
}
  1. 下面进行测试(设置不存在的routingkey)

image.png
此时证明当消息长期存放在exchange中没有投递到queue的时候就会触发消息退回回调


http://www.ppmy.cn/news/1516616.html

相关文章

RAFT:Adapting Language Model to Domain Specific RAG

论文链接 简单来说&#xff0c;就是你SFT微调的时候得考虑RAG的场景。 RAG什么场景&#xff1f;你检索top-k回来&#xff0c;里面有相关doc有不相关doc&#xff0c;后者是影响性能的重要原因&#xff0c;LLM需要有强大的识别能力才能分得清哪块和你的query相关。微调就是为了这…

Apache CloudStack Official Document 翻译节选(九)

关于 Apache CloudStack 的 最佳实践 &#xff08;三&#xff09; 配置云外的 防火墙与交换机 对Cisco VNMC&#xff08;Cisco Virtual Network Management Center&#xff09;设备集成云外的客户机网路防火墙&#xff1a; 思科虚拟网络管理中心为思科网络虚拟服务提供了中心…

【Rust光年纪】深度解读:Rust语言中各类消息队列客户端库详细对比

选择最佳 Rust 消息队列客户端库&#xff1a;全面对比与分析 前言 随着现代应用程序的复杂性不断增加&#xff0c;消息队列成为构建可靠、高性能系统的重要组件。本文将介绍一些用于Rust语言的消息队列客户端库&#xff0c;包括AMQP、Apache Kafka、NSQ、Apache Pulsar和Rock…

GoWeb 设置别名和多环境配置

别名 vite.config.ts中添加代码如下即可 //设置别名resolve: {alias: {"": path.resolve(process.cwd(),"src"),//用替代src}}随后即可使用 配置多环境 vite.config.ts中添加代码如下 envDir: ./viteenv,//相对路径随后在项目根目录创建对应的viteenv…

什么是 SQL 注入,有哪些类型,如何预防?

如果说数据是系统的核心&#xff0c;那么SQL注入就是直插系统核心的漏洞。一直以来SQL注入漏洞就被列入OWASP最常见和影响最广泛的十大漏洞列表中。 SQL注入漏是系统漏洞中一种比较严重的漏洞&#xff0c;如果说数据是系统的核心&#xff0c;那么SQL注入就是直插系统核心的漏洞…

Web应用服务器Tomcat

一、Tomcat的功能介绍 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和 并发访问用户不是很多的场合下被普遍使用&#xff0c;Tomcat 具有处理HTML页面的功能&#xff0c;它还是一个Servlet和 JSP容器。 官网…

PHP多门店民宿酒店预订系统小程序源码

&#x1f3e8;✨「多门店酒店民宿预订系统」——一键解锁全球住宿新体验&#xff01;&#x1f30d;&#x1f3e0; &#x1f31f; 开篇种草&#xff1a;旅行新伙伴&#xff0c;预订无忧&#xff01; 嘿小伙伴们&#xff0c;是不是每次计划旅行都被繁琐的酒店民宿预订搞得头大&…

uniapp封装请求

封装请求有两种&#xff1a; 一种是在服务端判断token是否失效&#xff0c;一种是在小程序端判断token是否过期&#xff0c;&#xff0c; 第二种在前端判断要简单些&#xff0c;&#xff0c;在拿到token的时候&#xff0c;并在前端设置一个token的过期时间的毫秒值&#xff0c…

分布式核心问题总结

一、幂等性 所谓幂等就是一次或多次操作同一个资源&#xff0c;所产生的影响均一致。产生问题的原因&#xff1a;网络阻塞和延迟、用户重复操作一锁 二判 三更新 三步严格控制顺序&#xff0c;确保加锁成功后进行数据查询和判断&#xff0c;幂等性判断通过后再更新&#xff0…

OpenCV绘图函数(3)判断点一条直线是否在一个矩形范围内的函数clipLine()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 裁剪线段与图像矩形相交的部分。 cv::clipLine 函数计算出完全位于指定矩形内的线段部分。如果线段完全位于矩形之外&#xff0c;则返回 false。…

HarmonyOS--认证服务-操作步骤

HarmonyOS–认证服务 文章目录 一、注册华为账号开通认证服务二、添加项目&#xff1a;*包名要与项目的包名保持一致三、获取需要的文件四、创建项目&#xff1a;*包名要与项目的包名保持一致五、添加json文件六、加入请求权限七、加入依赖八、修改构建配置文件&#xff1a;bui…

【C#】【EXCEL】Bumblebee/Classes/ExEnums.cs

文章目录 Bumblebee/Classes/ExEnums.csFlow diagramDescriptionCode Bumblebee/Classes/ExEnums.cs Flow diagram #mermaid-svg-FB98N7ZCCccQ4Z38 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FB98N7ZCCccQ4Z38…

20.缓存的更新策略

定义 缓存中的旧数据与数据库不一致。 缓存更新策略的类型 1.内存淘汰&#xff0c;利用redis的内存淘汰机制&#xff0c;当内存不足时自动淘汰部分数据。下次查询时更新缓存。redis默认开启了此机制。这种保证数据的一致性差。 2.超时剔除&#xff0c;给缓存数据添加TTL时间…

一文搞清全钢防静电地板的生产过程

防静电地板是各类大小机房不可缺少的地面材料。其中全钢防静电地板应用较为广泛&#xff0c;全钢防静电地板为全钢结构特征&#xff0c;底边选用深级伸拉钢板&#xff0c;造成窝状结构特征&#xff0c;提升了全钢防静电地板的抗压强度&#xff0c;表层选用硬质SPCC钢板&#xf…

WPF书籍阅读不指南

<wpf编程宝典> 对于刚匆忙学完 c# 的我来说,非常费劲. 只能 依靠<WPF深入浅出>>来对照看. ...就算是两本书互相看,还是十分难受,非常不理解...... 古老丛书<<windows Presentation Foundation 编程指南>> 一本 非常非常厚 的书,看着也难受,案例非常…

c#透明悬浮球实现 从零开始用C#写一个桌面应用程序(三)

目标&#xff1a;透明悬浮球 记录日期&#xff1a;20240308 要求基础&#xff1a;C#语言基础部分事件与委托&#xff0c;c#桌面程序基础操作 注&#xff1a;可见前文 http://t.csdnimg.cn/9uWK8 今天开始做一个悬浮球软件。本以为最难的是让悬浮球的具体功能&#xff0c…

【Qt】常见控件 —— QPushButton | QRadioButton

文章目录 QPushButtonQPushButton 的基本功能介绍QPushButton 添加快捷键通过图片实现 上下左右实现方向键的槽函数设置快捷键连发功能 QRadioButtonQRadioButton 的基本功能介绍通过 QRadioButton 选择性别具有排他效果禁用 选项 槽函数的使用情况基于 QRadioButton 实现一个简…

爬虫使用代理IP:提升数据抓取效率的实践

爬虫使用代理IP的技巧和方法 在进行网络爬虫时&#xff0c;使用代理IP可以帮助你提高数据抓取效率和保护隐私。本文将介绍爬虫使用代理IP的技巧和方法&#xff0c;帮助你更好地进行数据抓取。 为什么爬虫需要使用代理IP 在进行大规模数据抓取时&#xff0c;目标网站可能会检…

数据仓库: 4- 数据质量管理 5- 元数据管理

目录 4- 数据质量管理4.1 数据清洗4.1.1 数据清洗的重要性4.1.2 数据清洗常见的问题4.1.3 数据清洗的步骤4.1.3.1 数据质量评估:4.1.3.2 制定清洗规则:4.1.3.3 执行清洗操作:4.1.3.4 验证清洗结果:4.1.3.5 迭代优化: 4.1.4 数据清洗的常用方法4.1.5 数据清洗的最佳实践4.1.6 总…

外贸管理系统采购销售报关计算机毕业设计VUE/PYTHON/MYSQL

开发一个基于Vue、Python和MySQL的外贸管理系统&#xff0c;用于处理采购、销售以及报关等业务流程。这样的系统通常涉及前端界面展示、后端逻辑处理以及数据库存储等多个部分。下面是一些关键组件的设计建议&#xff1a;1. 技术栈选择 前端: Vue.js 后端: Python (Flask/Djang…