RabbitMQ 79b5ad38df29400fa52ef0085a14b02f

RabbitMQ

一、什么是消息队列


消息队列可以看作是一个存放消息的容器,其中,生产者负责生产数据到消息队列中,而消费者负责消费数据。消息队列是分布式系统中重要的组件,目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ。

消息队列主要解决了应用耦合、异步处理、流量削锋等问题。

二、Rabbit特点


RabbitMQ 是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件,它实现了高效、可靠、可扩展的消息传递机制。以下是 RabbitMQ 的一些主要特点:

  • 可靠性:RabbitMQ 提供了消息持久化、确认机制、事务等功能,确保消息传递的可靠性。
  • 灵活性:RabbitMQ 支持多种消息传递模式,例如点对点、发布/订阅、通配符等,可以根据不同的应用场景选择合适的模式。
  • 可扩展性:RabbitMQ 支持集群部署,可以通过添加节点来扩展消息处理能力。
  • 与多种编程语言兼容:RabbitMQ 提供了多种客户端库,可以与多种编程语言进行集成,例如 Java、Python、Go 等。
  • 丰富的功能:RabbitMQ 提供了多种功能,例如消息优先级、消息 TTL、延迟队列等,可以满足不同的业务需求。
  • 可视化管理界面:RabbitMQ 提供了一个可视化的管理界面,可以方便地管理队列、交换机、绑定关系等。

三、RabbitMQ的组成部分

  • Broker:消息队列服务进程。此进程包括两个部分:Exchange和Queue。
  • Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列
  • Queue:消息队列,存储消息的队列。
  • Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。
  • Consumer:消息消费者。消费队列中存储的消息。

在这里插入图片描述

如图所示,RabbitMQ的工作流程分为以下几个部分

  • 消息生产者连接到RabbitMQ Broker,创建connection,开启channel。
  • 生产者声明交换机类型、名称、是否持久化等。
  • 生产者发送消息,并指定消息是否持久化等属性和routing key。
  • exchange收到消息之后,根据routing key路由到跟当前交换机绑定的相匹配的队列里面。
  • 消费者监听接收到消息之后开始业务处理。

四、RabbitMQ中Exchange


在RabbitMQ中,消息发送方不是直接将消息发送到queue中,而是先发送给exchange,由exchange再发送给对应的队列。exchange的类型有:**direct, topic, headers , fanout 。**默认的exchange,名为"",是一个没有名字的direct类型的exchange。发送到它的消息是基于路由键(routing key)路由到队列的。

4.1 Direct

direct,需要将一个队列绑定到交换机上,要求该消息与一个特定的routing key完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求routing key为 “green”,则只有routing key为“green”的消息才被转发,不会转发routing key为"red",只会转发routing key为"green”

4.2 Topic

可以理解为通配符匹配,将routing key和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配0个或多个词,符号“*”只能匹配一个词。

在这里插入图片描述

4.3 Fanout

Fanout 不需要指定routing key。你只需要简单的将队列绑定到交换机上。一个发送到该类型交换机的消息都会被广播到与该交换机绑定的所有队列上。
在这里插入图片描述

1.rabbitMQ的消息持久化的做法不是每接受一条消息就立即调用fsync,而是对接收到的消息进行缓存,接着再批量进行持久化。

3.rabbitMQ中的**AnonymousQueue** 默认是非持久化、自动删除的队列。

当消费者连接断开时,AnonymousQueue会被自动删除,队列中的消息也会被删除,不会持久化。

4.4 Headers

不处理routing key,而是根据发送的消息内容中的headers属性进行匹配。在绑定Queue与Exchange时指定一组键值对;当消息发送到RabbitMQ时会取到该消息的headers与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的

在这里插入图片描述

RabbitMQ入门实战

首先在application.yml中配置RabbitMQ信息

spring:
    rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest

接着添加配置类,在配置类中实现相关bean的注入

@Configuration
public class DirectRabbitConfig {

    @Bean
    public Queue rabbitmqDemoDirectQueue() {

        return new AnonymousQueue();
        /**
         *   等价于  return new Queue("direct_demo",false,false,false);
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         * */
    }

    @Bean
    public DirectExchange rabbitmqDemoDirectExchange() {
        //Direct交换机
        return new DirectExchange("demo_exchange", true, false);
    }

    @Bean
    public Binding bindDirect() {
        //链式写法,将队列和交换机进行绑定,并设置匹配键
        return BindingBuilder
                //绑定队列
                .bind(rabbitmqDemoDirectQueue())
                //到交换机
                .to(rabbitmqDemoDirectExchange())
                //并设置匹配键
                .with("demo");
    }

		@Bean
    public Sender sender() {
				//生产者类
        return new Sender();
    }
		
		@Bean
    public Receiver receiver() {
				//消费者类
        return new Receiver();
    }
}

接下来,创建发送消息的Sender类和消费消息的Receiver类

/**
 * 生产者类
 */
public class Sender {

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private DirectExchange direct;

		private final String MESSAGE = "Hello world";
		
		private final String key = "demo";

    @Scheduled(fixedDelay = 1000, initialDelay = 500)
    public void send() {
        template.convertAndSend(direct.getName(), key, MESSAGE);
        System.out.println("sending message: " + MESSAGE);
    }

}
/**
 * 消费者类
 */
@RabbitListener(queues = "#{rabbitmqDemoDirectQueue.name}")
public class Receiver {

    @RabbitHandler
    public void receive(String msg) {
        System.out.println("receive message: " + msg);
    }
}

最后,创建启动类,并加上@EnableScheduling 注解

@SpringBootApplication
@EnableScheduling
public class RabbitDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(RabbitAmqpTutorialsApplication.class, args);
    }
}

打开http://localhost:15672,可以看到RabbitMQ消息发送情况等信息

在这里插入图片描述

五、RabbitMQ的一些机制

5.1 消息应答

一般情况下,RabbitMQ中的broker向消费者发送一条消息后,便立即将该消息标记为删除。由于消费者处理一个消息可能需要一段时间,假如在处理消息中途消费者挂掉了,我们会丢失其正在处理的消息以及后续发送给该消费这的消息。为了保证消费者消费的可靠性,RabbitMQ 引入消息应答机制,即:**消费者在接收消息并且处理完该消息之后,才告知 RabbitMQ 可以把该消息删除了。**RabbitMQ 中消息应答方式有两种:自动应答(默认)、手动应答。

  • 自动应答

自动应答是RabbitMQ默认采用的消息应答方式,是指broker不在乎消费者对消息处理是否成功,都会告诉队列删除消息。如果处理消息失败,又没有捕获异常,则会实现自动补偿(队列重新向消费者投递消息);如果捕获异常了,broker以为消息消费成功,就会将消息从队列中删除,导致数据丢失。

  • 手动应答

手动应答,是指消费者处理完业务逻辑之后,手动返回ack(通知)告诉broker消息处理完了,你可以删除消息了;或者手动返回nack消息,告诉broker消息处理失败,别删除消息。如果消费者挂了而没有发送ACK或NACK,那么RabbitMQ会认为该消息未被处理,会将该消息重新分发给其他消费者,直到有一个消费者成功处理并发送ACK或NACK为止。这个过程被称为消息的重新入队列。在重新入队列的过程中,RabbitMQ会在消息的属性中增加一个计数器,表示该消息被重新分发的次数。如果该计数器的值超过了一个预定的阈值,那么RabbitMQ可能会将该消息标记为“死信”,并将其发送到一个指定的死信交换机(dead letter exchange)中。这个机制可以避免消息在系统中无限制地被重新分发,从而引起系统性能问题。

在springboot的application.yml中可以设置RabbitMQ的应答机制

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual

消费者

@RabbitListener(queues = "#{Queue1.name}")
    public void onMessage1(Message message, Channel channel) throws Exception {
        try {
            MessageProperties messageProperties = message.getMessageProperties();
            byte[] body = message.getBody();
            String content = new String(body, 0, body.length, messageProperties.getContentEncoding());
            System.out.println("receiver1: " + content);
            channel.basicAck(messageProperties.getDeliveryTag(), true);
        } catch (Exception e) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }

5.2 持久化

默认情况下 RabbitMQ 退出或由于某种原因崩溃时,它会清空队列和消息,除非告知它不要这样做。确保消息不会丢失需要做两件事:我们需要将队列和消息都标记为持久化

  • 队列持久化

  • 在这里插入图片描述

  • 消息持久化(在Spring Boot中使用RabbitTemplate发送消息时,默认情况下消息是持久化的)

@Autowired
private RabbitTemplate rabbitTemplate;

public void sendMessage(String message) {
    MessageProperties properties = new MessageProperties();
    properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 设置消息的持久化属性
    Message rabbitMessage = new Message(message.getBytes(), properties);
    rabbitTemplate.convertAndSend("exchangeName", "routingKey", rabbitMessage);
}

5.3 不公平分发

RabbitMQ 默认分发消息采用的轮询分发模式,但是在某种场景下这种策略并不是很好,比方说有两个消费者在处理任务,其中 consumer01 处理任务的速度非常快,而 consumer02 处理速度却很慢,此时如果我们还是采用轮询分发,就会使处理速度快的 consumer01 很大一部分时间处于空闲状态,而 consumer02 一直在干活。

可以通过设置channel的prefetchCount 为1,来实现不公平分发。该参数表示,该消费者当前只能处理一个消息。

channel.basicQos(1);

或者application.yml中设置

spring.rabbitmq.listener.simple.acknowledge-mode=manual      
spring.rabbitmq.listener.simple.prefetch=1

5.4 预取值

该值定义channel上允许的未确认消息的最大数量。一旦数量达到配置的数量,RabbitMQ 将停止在channel上传递更多消息,除非至少有一个未处理的消息被确认。假设在channel上有未确认的消息 5、6、7,8,并且channel的预取计数设置为 4,此时 RabbitMQ 将不会在该channel上再传递任何消息,除非至少有一个未应答的消息被 ack。比方说 tag=6 的消息刚刚被确认 ACK,RabbitMQ 将会感知这个情况到并再发送一条消息。对于自动确认机制,其预取值可以看做是无限。

5.5 发布确认

生产者将channel设置成 confirm 模式,一旦channel进入 confirm 模式,所有在该channel上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。

confirm 模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等channel返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息,生产者应用程序同样可以在回调方法中处理该 nack 消息。

发布确认机制有三种策略:单个确认发布批量确认发布异步确认发布

  • 单个确认发布

是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布

  • 批量确认发布

与单个等待确认消息相比,先发布一批消息然后一起确认可以极大地提高吞吐量,当然这种方式的缺点就是:当发生故障导致发布出现问题时,不知道是哪个消息出现问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。当然这种方案仍然是同步的,也一样阻塞消息的发布。

  • 异步确认发布

异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,他是利用回调函数来达到消息可靠性传递的,这个中间件也是通过函数回调来保证是否投递成功,

要开启发布确认,需要配置application.yml

spring:
  #项目名称
  application:
    name: rabbitmq-provider
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: rabbitMQ
    password: rabbitMQ
    #确认消息已发送到交换机(Exchange)
#    publisher-confirm-type: SIMPLE
    publisher-confirm-type: CORRELATED
    #确认消息已发送到队列(Queue)
    publisher-returns: true

然后,在rabbitTemplate中进行设置

@Slf4j
@Configuration
public class RabbitConfig {

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
       // Mandatory为true时,消息通过交换器无法匹配到队列会返回给生产者,为false时匹配不到会直接被丢弃
        rabbitTemplate.setMandatory(true);

        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *  ConfirmCallback机制只确认消息是否到达exchange(交换器),不保证消息可以路由到正确的queue;
             *  需要设置:publisher-confirm-type: CORRELATED;
             *  springboot版本较低 参数设置改成:publisher-confirms: true
             *
             *  以实现方法confirm中ack属性为标准,true到达
             *  config : 需要开启rabbitmq得ack publisher-confirm-type
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("ConfirmCallback  确认结果 (true代表发送成功) : {}  消息唯一标识 : {} 失败原因 :{}",ack,correlationData,cause);
            }
        });

        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            /**
             *  ReturnsCallback 消息机制用于处理一个不可路由的消息。在某些情况下,如果我们在发送消息的时候,当前的 exchange 不存在或者指定路由 key 路由不到,这个时候我们需要监听这种不可达的消息
             *   就需要这种return机制
             *
             *  config : 需要开启rabbitmq发送失败回退; publisher-returns 或rabbitTemplate.setMandatory(true); 设置为true
             */
            @Override
            public void returnedMessage(ReturnedMessage returned) {
//                实现接口ReturnCallback,重写 returnedMessage() 方法,
//                方法有五个参数
//                message(消息体)、
//                replyCode(响应code)、
//                replyText(响应内容)、
//                exchange(交换机)、
//                routingKey(队列)。

                log.info("ReturnsCallback    returned : {}",returned);
            }
        });

        return rabbitTemplate;
    }

}

5.6 死信队列

死信就是无法被消费的消息。一般来说,producer 将消息投递到 broker 或者直接到 queue 中,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

死信产生的原因:

  • 消息 TTL 过期
  • 队列达到最大长度(队列满了无法再添加数据到 mq 中)
  • 消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false

5.7 延迟队列

延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

RabbitMQ 中的 TTL

TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒。一条消息如果在TTL 设置的时间内没有被消费,则会成为"死信"。如果同时配置了队列的TTL 和消息的TTL,那么较小的那个值将会被使用。

实现延迟队列的两种方式:

  • 通过设置队列TTL+死信实现消息延迟

代码架构图如下所示,其中有两个direct类型的交换机XY,其中Y为死信交换机;还有三个队列QAQBQD,QA和QB为普通队列,其中QA中消息的ttl为10s,QB中消息的ttl为40s,QD为死信队列。队列与交换机之间的routing-key如图中连线上标注所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbRRw2rv-1691716049857)(RabbitMQ%2079b5ad38df29400fa52ef0085a14b02f/Untitled%208.png)]

  • 通过设置消息TTL+死信实现消息延迟

以上延时队列的实现目前只有 10S 和 40S 两个时间选项,如果需要一个小时后处理,那么就需要增加TTL为一个小时的队列,如果是预定会议室然后提前通知这样的场景,岂不是要增加无数个队列才能满足需求?

因此我们需要做出一些优化,在这里新增了一个队列 QC,绑定关系如下,该队列不设置 TTL 时间,我们通过指定消息的 TTL 来实现消息的延迟

在这里插入图片描述

  • 下载延迟队列插件

参见:https://blog.csdn.net/qq_45173404/article/details/121687489

5.9 幂等性

所谓幂等性就是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。比如用户购买商品后支付后已经扣款成功,但是返回结果时出现网络异常,用户并不知道自己已经付费成功,于是再次点击按钮,此时就进行了第二次扣款,这次的返回结果成功。但是扣了两次用户的钱,这就出现了不满足幂等性,即用户对统一操作发起了一次或多次请求

对应消息队列 MQ 中出现的幂等性问题就是消息重复消费。比如消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者,消费者在给 MQ 返回 ack 时网络中断,故 MQ 未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者的重复消费。

幂等性解决方案:https://zhuanlan.zhihu.com/p/176944177

5.10 优先级队列

在RabbitMQ中,队列需要设置为优先级队列的同时消息也必须设置消息的优先级才能生效,而且消费者需要等待消息全部发送到队列中才去消费因为这样才有机会对消息进行排序。

代码实现队列优先级

在这里插入图片描述

5.11 惰性队列

当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限。之后发送的消息就会成为死信,可能会被丢弃,这就是消息堆积问题。

从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是惰性队列。惰性队列的特征如下:

  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存
  • 支持数百万条的消息存储

代码实现惰性队列
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/68563.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

http、https笔记

目录 HTTP 基本概念状态码:get和post的区别:http 常⻅字段:http的缺点: HTTP/1.1HTTP/3HTTPSHTTPS和HTTP区别对称加密和⾮对称加密⾮对称加密 HTTP 基本概念 状态码: 1xx 中间状态,比如post的continue 20…

【LeetCode】1572.矩阵对角线元素的和

题目 给你一个正方形矩阵 mat,请你返回矩阵对角线元素的和。 请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。 示例 1: 输入:mat [[1,2,3],[4,5,6],[7,8,9]] 输出:25 解释:对角线的和为&a…

3.4 网络安全管理设备

数据参考:CISP官方 目录 IDS (入侵检测系统)网络安全审计漏洞扫描系统VPN(虚拟专网)堡垒主机安全管理平台 一、IDS (入侵检测系统) 入侵检测系统(IDS)是一种网络安全设备,用于监测和检测网络中的入侵行…

nginx编译以及通过自定义生成证书配置https

1. 环境准备 1.1 软件安装 nginx安装编译安装以及配置https,需要gcc-c pcre-devel openssl openssl-devel软件。因此需要先安装相关软件。 yum -y install gcc-c pcre-devel openssl openssl-devel wgetopenssl/openssl-devel:主要用于nginx编译的htt…

Docker安装Hadoop分布式集群

一、准备环境 docker search hadoop docker pull sequenceiq/hadoop-docker docker images二、Hadoop集群搭建 1. 运行hadoop102容器 docker run --name hadoop102 -d -h hadoop102 -p 9870:9870 -p 19888:19888 -v /opt/data/hadoop:/opt/data/hadoop sequenceiq/hadoop-do…

windows环境下编译OpenJDK12

环境:Windows11 目录: 1、下载OpenJDK12源码 下载地址: https://hg.openjdk.org/jdk/jdk12 点击zip下载到本地。 解压到本地。 Tip:注意本地路径中最好不要包含中文或空格。 2、阅读一遍doc/building.html 如果只是想构建J…

数组对象去重的几种方法

场景: let arrObj [{ name: "小红", id: 1 },{ name: "小橙", id: 1 },{ name: "小黄", id: 4 },{ name: "小绿", id: 3 },{ name: "小青", id: 1 },{ name: "小蓝", id: 4 } ]; 方法一:…

2023年第2季社区Task挑战赛升级新玩法,等你来战!

第1季都有哪些有趣的作品? 在大家的共建下,FISCO BCOS开源生态不断丰富完善,涌现了众多实用技术教程和代码:基于数字身份凭证的业务逻辑设计,贡献了发放数字身份凭证的参考实现;提供企业碳排放、慈善公益等…

【基础类】—原型链系统性知识

一、创建对象有几种方法 字面量创建对象 1-1. 什么是字面量 字面量就是所见即所,指的是常量;用来为变量赋值时的常数量 代码例子:123;‘ABC’, {name: ‘张三’}, undefined , true 生活例子:门店的招牌&a…

[C++项目] Boost文档 站内搜索引擎(4): 搜索的相关接口的实现、线程安全的单例index接口、cppjieba分词库的使用、综合调试...

有关Boost文档搜索引擎的项目的前三篇文章, 已经分别介绍分析了: 项目背景: 🫦[C项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍…文档解析、处理模块parser的实现: 🫦[C项目] Boost文档 站内搜索引擎(2): 文档文本解析模块…

CDN(内容分发网络)

CDN的全称是 Content Delivery Network, 即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞&a…

CentOS7 安装 MongoDB5

MongoDB是一种NoSQL数据库,它存储数据的方式与传统的关系型数据库不同。MongoDB使用文档数据库模型,将数据存储在自包含的、可扩展的BSON文档中。MongoDB具有高可用性、自动分片、动态查询能力、灵活性等优点,适合于许多不同的应用场景。 下…

ACM Journals的Word模板使用心得

按照说明一步一步按照顺序调整格式,体力活,考验耐心细心。 两个模板,第一个是 Submission Template投稿用的,第二个是Primary Article Template接收后用的。 及时保存备份,以便恢复到最初满意的状态。 格式确定后&a…

EMC框架简单归纳

电磁干扰的产生原因&#xff1a;电压/电流的变化中不必要的部分。 电磁干扰的耦合途径有两种&#xff1a;导线传导和空间辐射。 导线传导干扰原因是电流总是走“最小阻抗”路径。以屏蔽线为例&#xff0c;低频&#xff08;f<1kHz&#xff09;时&#xff0c;导线的电阻起到主…

函数的递归与迭代

递归经典问题&#xff1a;&#xff08;自行尝试&#xff09; 1、汉诺塔问题 2、青蛙跳台阶问题 练习1、 练习2、

API HOOK技术在MFC程序破解过程中的应用

更新&#xff0c;修改了一下typora的上传脚本&#xff0c;把图片全部上传到看雪上了 本文已于2023-08-02首发于个人博客 图片加载不出来&#xff0c;放了一个PDF版本在附件里 文中有几张图片是动图&#xff0c;如果不会动&#xff0c;可以去我的个人博客看 最近破解了一个M…

W5500-EVB-PICO 做TCP Server进行回环测试(六)

前言 上一章我们用W5500-EVB-PICO开发板做TCP 客户端连接服务器进行数据回环测试&#xff0c;那么本章将用开发板做TCP服务器来进行数据回环测试。 TCP是什么&#xff1f;什么是TCP Server&#xff1f;能干什么&#xff1f; TCP (Transmission Control Protocol) 是一种面向连…

MYSQL进阶-查询优化- 实战 STATUS

回城传送–》《100天精通MYSQL从入门到就业》 文末有送书活动&#xff0c;可以参加&#xff01; 文章目录 一、练习题目二、SQL思路SQL进阶-查询优化- SHOW STATUS初始化数据解法SHOW STATUS是什么实战经验&#xff1a;常用的mysql状态查询1、QPS(每秒处理的请求数量)计算思路…

flutter相关URL schemes

先看效果 使用 url_launcher库 做唤起其他app操作 url_launcher | Flutter Package 配置 安卓 flutter 项目目录下的 android\app\src\main\AndroidManifest.xml 如果不配置的话 有些手机就打不开app <queries><!-- If your app checks for SMS support --><…