探究Kafka原理-7.exactly once semantics 和 性能测试

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • 幂等性
    • 幂等性要点
    • kafka 幂等性实现机制
  • kafka 事务
    • 事务要点知识
    • 事务 api 示例
    • 事务实战案例
  • Kafka 速度快的原因
  • 分区数与吞吐量(性能测试)
    • 生产者性能测试
      • tpc_3: 分区数 3,副本数 1
      • tpc_4: 分区数 4,副本数 2
      • tpc_5:分区数 5,副本数 1
      • tpc_6:分区数 6,副本数 1
      • tpc_12:分区数 12
    • 消费者性能测试
    • 分区数与吞吐量实际测试
    • 分区数设置的经验参考

幂等性

幂等性要点

Kafka 0.11.0.0 版本开始引入了幂等性与事务这两个特性,以此来实现 EOS ( exactly once semantics ,精确一次处理语义)

生产者在进行发送失败后的重试时(retries),有可能会重复写入消息,而使用 Kafka 幂等性功能之后就可以避免这种情况。

开启幂等性功能,只需要显式地将生产者参数 enable.idempotence 设置为 true (默认值为 false):props.put(“enable.idempotence”,true);

在开启幂等性功能时,如下几个参数必须正确配置:

  • retries > 0
  • max.in.flight.requests.per.connection<=5
  • acks = -1

如有违反,则会抛出 ConfigException 异常;

kafka 幂等性实现机制

每一个 producer 在初始化时会生成一个 producer_id,并为每个目标分区维护一个“消息序列号”;

producer 每发送一条消息,会将对应的“序列号”加 1

broker 端会为每一对{producer_id,分区}维护一个序列号,对于每收到的一条消息,会判断服务端的 SN_OLD 和接收到的消息中的 SN_NEW 进行对比:

  • 如果 SN_OLD + 1 == SN_NEW,正常;
  • 如果 SN_NEW < SN_OLD + 1 说明是重复写入的数据,直接丢弃
  • 如果 SN_NEW>SN_OLD+1,说明中间有数据尚未写入,或者是发生了乱序,或者是数据丢失,将抛出严重异常:OutOfOrderSequenceException

在这里插入图片描述

producer.send(“aaa”) 消息 aaa 就拥有了一个唯一的序列号

如果这条消息发送失败,producer 内部自动重试(retry),此时序列号不变;

producer.send(“bbb”) 消息 bbb 拥有一个新的序列

注意:kafka 只保证 producer 单个会话中的单个分区幂等。

kafka 事务

从kafka读数据,写入mysql的场景中

为了让 偏移量更新 和 数据的落地 一荣俱荣,用了mysql的事务,这样就能实现上述场景中数据传输的端到端,精确一次性语义eos

但是万一存在如下场景:

要你从kafka的source_topic中读数据,做处理,然后写入kafka的dest_topic

这个场景,要想实现eos,就不能利用mysql中的事务了。

假设1-100的数据读了,处理完了,写入kafka目标topic,但是offset还是1,然后,程序崩溃了。

然后,重启,程序会从1开始读,会把1-100重新再读一次,再处理一次,再插入到目标topic一次。

这里和幂等性是有区别的(幂等性,是说producer.send(),在它内部发生重试的时候,可以由broker去除重复数据)

要解决上述场景中的数据重复的问题:需要将偏移量更新 和 数据落地,绑定在一个事务中。

mysql中事务解决上述问题,关键点在于mysql可以实现数据的回滚。

而kafka中的数据是支持不断追加,然后只读。

kafka中的事务,做到了什么效果:

kafka并不能真正把未提交事务的结果进行物理回滚,只做到了让下游消费者,只能看到提交了事务的结果。

事务要点知识

Kafka 的事务控制原理

主要原理:

  • 开始事务–>发送一个 ControlBatch 消息(事务开始)
  • 提交事务–>发送一个 ControlBatch 消息(事务提交)
  • 放弃事务–>发送一个 ControlBatch 消息(事务终止)

开始事务的必须配置参数

Properties props = new Properties();

props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"doit01:9092");

props.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

// acks
props.setProperty(ProducerConfig.ACKS_CONFIG,"-1");

// 生产者的重试次数
props.setProperty(ProducerConfig.RETRIES_CONFIG,"3");

// 飞行中的请求缓存最大数量
props.setProperty(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION,"3");

// 开启幂等性
props.setProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,"true");

// 设置事务 id
props.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"trans_001");

事务控制的代码模板

// 初始化事务
producer.initTransaction( )
    
// 开启事务
producer.beginTransaction( )
    
try{
    
	// 干活
	// 提交事务
	producer.commitTransaction( )
        
}catch (Exception e){
    
	// 异常回滚(放弃事务)
	producer.abortTransaction( )
        
}

在这里插入图片描述

消费者 api 是会拉取到尚未提交事务的数据的;只不过可以选择是否让用户看到!

是否让用户看到未提交事务的数据,可以通过消费者参数来配置:

isolation.level=read_uncommitted(默认值)

isolation.level=read_committed

kafka 还有一个“高级”事务控制,只针对一种场景:

用户的程序,要从 kafka 读取源数据,数据处理的结果又要写入 kafka

kafka能实现端到端的事务控制(比起上面的“基础”事务,多了一个功能,通过producer可以将consumer的消费偏移量绑定到事务上提交)

producer.sendOffsetsToTransaction(offsets,consumer_id)

事务 api 示例

为了实现事务,应用程序必须提供唯一 transactional.id,并且开启生产者的幂等性

properties.put ("transactional.id","transactionid00001");
properties.put ("enable.idempotence",true);

kafka 生产者中提供的关于事务的方法如下:

在这里插入图片描述

“消费 kafka-处理-生产结果到 kafka”典型场景下的代码结构示例

package cn.doitedu.kafka.transaction;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.time.Duration;
import java.util.*;

/***
* @author hunter.d
* @qq 657270652
* @wx haitao-duan
* @date 2020/11/15
**/
public class TransactionDemo {
	public static void main(String[] args) {
        Properties props_p = new Properties();
        
        props_p.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"doitedu01:9092,d
        oitedu02:9092");
                            
        props_p.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
        StringSerializer.class.getName());
                            
        props_p.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerial
        izer.class.getName());
                            
        props_p.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"tran_id_001");
        Properties props_c = new Properties();
                            
        props_c.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
        StringDeserializer.class.getName());
                            
        props_c.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
        StringDeserializer.class.getName());
                            
        props_c.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
        "doitedu01:9092,doitedu02:9092");
                            
        props_c.put(ConsumerConfig.GROUP_ID_CONFIG, "groupid01");
                            
        props_c.put(ConsumerConfig.CLIENT_ID_CONFIG, "clientid");
                            
        props_c.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
                            
        // 构造生产者和消费者
        KafkaProducer<String, String> p = new KafkaProducer<String, String>(props_p);
                            
        KafkaConsumer<String, String> c = new KafkaConsumer<String, String>(props_c);
                            
        c.subscribe(Collections.singletonList("tpc_5"));
                            
		// 初始化事务
		p.initTransactions();
        // consumer-transform-produce 模型业务流程
        while(true){
            // 拉取消息
            ConsumerRecords<String, String> records = c.poll(Duration.ofMillis(1000L));
			if(!records.isEmpty()){
                // 准备一个 hashmap 来记录:"分区-消费位移" 键值对
                HashMap<TopicPartition, OffsetAndMetadata> offsetsMap = new HashMap<>();
                // 开启事务
                p.beginTransaction();
				try {
                    // 获取本批消息中所有的分区
                    Set<TopicPartition> partitions = records.partitions();
                    // 遍历每个分区
                    for (TopicPartition partition : partitions) {
                        // 获取该分区的消息
                        List<ConsumerRecord<String, String>> partitionRecords =
                        records.records(partition);
                        // 遍历每条消息
                        for (ConsumerRecord<String, String> record : partitionRecords) {
                            // 执行数据的业务处理逻辑
                            ProducerRecord<String, String> outRecord = new
                            ProducerRecord<>("tpc_sink", record.key(), record.value().toUpperCase());
                            // 将处理结果写入 kafka
                            p.send(outRecord);
						}
                        // 将处理完的本分区对应的消费位移记录到 hashmap 中
                        long offset = partitionRecords.get(partitionRecords.size() -
                        1).offset();
						offsetsMap.put(partition,new OffsetAndMetadata(offset+1));
					}
                    // 向事务管理器提交消费位移
                    p.sendOffsetsToTransaction(offsetsMap,"groupid");
					// 提交事务
					p.commitTransaction();
				}catch (Exception e){
					// 终止事务
					p.abortTransaction();
				}
			}
		}
	}
}

事务实战案例

在实际数据处理中,consume-transform-produce 是一种常见且典型的场景;

在这里插入图片描述

在此场景中,我们往往需要实现,从“读取 source 数据,至业务处理,至处理结果写入 kafka”的整个流程,具备原子性:

要么全流程成功,要么全部失败!

(处理且输出结果成功,才提交消费端偏移量;处理或输出结果失败,则消费偏移量也不会提交)

要实现上述的需求,可以利用 Kafka 中的事务机制:

它可以使应用程序将消费消息、生产消息、提交消费位移当作原子操作来处理,即使该生产或消费会跨多个 topic 分区;

在 消 费 端 有 一 个 参 数 isolation.level , 与 事 务 有 着 莫 大 的 关 联 , 这 个 参 数 的 默 认 值 为“read_uncommitted”,意思是说消费端应用可以看到(消费到)未提交的事务,当然对于已提交的事务也是可见的。这个参数还可以设置为“read_committed”,表示消费端应用不可以看到尚未提交的事务内的消息。

在这里插入图片描述

控制消息(ControlBatch:COMMIT/ABORT)表征事务是被提交还是被放弃

Kafka 速度快的原因

  • 消息顺序追加(磁盘顺序读写比内存的随机读写还快)
  • 页缓存等技术(数据交给操作系统的页缓存,并不真正刷入磁盘;而是定期刷入磁盘)
  • 使用 Zero-Copy (零拷贝)技术来进一步提升性能;

扩展阅读:零拷贝

所谓的零拷贝是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之手;

零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换;对于 Linux 系统而言,零拷贝技术依赖于底层的 sendfile( )方法实现;对应于 Java 语言,FileChannal.transferTo( )方法的底层实现就是 sendfile( )方法;

  • 非零拷贝示意图

在这里插入图片描述

  • 零拷贝示意图

在这里插入图片描述

零拷贝技术通过 DMA (Direct Memory Access)技术将文件内容复制到内核模式下的 Read Buffer。不过没有数据被复制到 Socke Buffer,只有包含数据的位置和长度的信息的文件描述符被加到 Socket Buffer; DMA 引擎直接将数据从内核模式 read buffer 中传递到网卡设备

这里数据只经历了 2 次复制就从磁盘中传送出去了,并且上下文切换也变成了 2 次

零拷贝是针对内核模式而言的,数据在内核模式下实现了零拷贝;

分区数与吞吐量(性能测试)

Kafka 本 身 提 供 用 于 生 产 者 性 能 测 试 的 kafka-producer-perf-test.sh 和 用 于 消 费 者 性 能 测 试 的
kafka-consumer-perf-test. sh,主要参数如下:

  • topic 用来指定生产者发送消息的目标主题;
  • num-records 用来指定发送消息的总条数
  • record-size 用来设置每条消息的字节数;
  • producer-props 参数用来指定生产者的配置,可同时指定多组配置,各组配置之间以空格分隔与
    producer-props 参数对应的还有一个 producer-config 参数,它用来指定生产者的配置文件;
  • throughput 用来进行限流控制,当设定的值小于 0 时不限流,当设定的值大于 0 时,当发送的吞吐量大于该值时就会被阻塞一段时间。

经验:如何把 kafka 服务器的性能利用到最高,一般是让一台机器承载( cpu 线程数*2~3 )个分区
测试环境: 节点 3 个,cpu 2 核 2 线程,内存 8G ,每条消息 1k

测试结果: topic 在 12 个分区时,写入、读取的效率都是达到最高
写入: 75MB/s ,7.5 万条/s
读出: 310MB/s

当分区数>12 或者 <12 时,效率都比=12 时要低!

生产者性能测试

tpc_3: 分区数 3,副本数 1

[root@doitedu01 kafka_2.11-2.0.0]# bin/kafka-producer-perf-test.sh --topic tpc_3 --num-records 100000  --record-size 1024 --throughput -1 --producer-props bootstrap.servers=doitedu01:9092 acks=1

100000 records sent, 26068.821689 records/sec (25.46 MB/sec), 926.82 ms avg latency, 1331.00 ms max latency, 924 ms 50th, 1272 ms 95th, 1305 ms 99th, 1318 ms 99.9th.

tpc_4: 分区数 4,副本数 2

[root@doitedu01 kafka_2.11-2.0.0]# bin/kafka-producer-perf-test.sh --topic tpc_4 --num-records 100000
--record-size 1024 --throughput -1 --producer-props bootstrap.servers=doitedu01:9092 acks=1

100000 records sent, 25886.616619 records/sec (25.28 MB/sec), 962.06 ms avg latency, 1647.00 ms max latency, 857 ms 50th, 1545 ms 95th, 1622 ms 99th, 1645 ms 99.9th.

tpc_5:分区数 5,副本数 1

[root@doitedu01 kafka_2.11-2.0.0]# bin/kafka-producer-perf-test.sh --topic tpc_5 --num-records 100000
--record-size 1024 --throughput -1 --producer-props bootstrap.servers=doitedu01:9092 acks=1

100000 records sent, 28785.261946 records/sec (28.11 MB/sec), 789.29 ms avg latency, 1572.00 ms max latency, 665 ms 50th, 1502 ms 95th, 1549 ms 99th, 1564 ms 99.9th

tpc_6:分区数 6,副本数 1

[root@doitedu01 kafka_2.11-2.0.0]# bin/kafka-producer-perf-test.sh --topic tpc_6 --num-records 100000
--record-size 1024 --throughput -1 --producer-props bootstrap.servers=doitedu01:9092 acks=1

100000 records sent, 42662.116041 records/sec (41.66 MB/sec), 508.68 ms avg latency, 1041.00 ms max latency, 451 ms 50th, 945 ms 95th, 1014 ms 99th, 1033 ms 99.9th.

tpc_12:分区数 12

[root@doitedu01 kafka_2.11-2.0.0]# bin/kafka-producer-perf-test.sh --topic tpc_12 --num-records 100000
--record-size 1024 --throughput -1 --producer-props bootstrap.servers=doitedu01:9092 acks=1

100000 records sent, 56561.085973 records/sec (55.24 MB/sec), 371.42 ms avg latency, 1103.00 ms max latency, 314 ms 50th, 988 ms 95th, 1091 ms 99th, 1093 ms 99.9th.

消费者性能测试

[root@doitedu01 kafka_2.11-2.0.0]# bin/kafka-consumer-perf-test.sh --topic tpc_3 --messages 100000
--broker-list doitedu01:9092 --consumer.config x.propertie

结果数据个字段含义:

start.time:						2023-11-14 15:43:42:422
end.time :						2023-11-14 15:43:43:347
data.consumed.in.MB:			98.1377
MB.sec :						106.0948
data.consumed.in.nMsg :			100493
nMsg.sec :						108641.0811
rebalance.time.ms :				13
fetch.time.ms :					912
fetch.MB.sec :					107.6071
fetch.nMsg.sec :				110189.6930

结果中包含了多项信息,分别对应
起始运行时间(start. time)、
结束运行时 end.time)、
消息总量(data.consumed.in.MB ,单位为 MB ),
按字节大小计算的消费吞吐量(单位为 MB )、
消费的消息总数( data. consumed.in nMsg )、
按消息个数计算的吞吐量(nMsg.sec)、
再平衡的时间( rebalance time.ms 单位为 MB/s)、
拉取消息的持续时间(fetch.time.ms,单位为 ms)、
每秒拉取消息的字节大小(fetch.MB.sec 单位 MB/s)、
每秒拉取消息的个数( fetch.nM.sec)。
其中
fetch.time.ms= end.time - start.time - rebalance.time.ms

分区数与吞吐量实际测试

Kafka 只允许单个分区中的消息被一个消费者线程消费,一个消费组的消费并行度完全依赖于所消费的分区数;

如此看来,如果一个主题中的分区数越多,理论上所能达到的吞吐量就越大,那么事实真的如预想的一样吗?

我们以一个 3 台普通阿里云主机组成的 3 节点 kafka 集群进行测试,每台主机的内存大小为 8GB,磁
盘 为 40GB, 4 核 CPU 16 线 程 , 主 频 2600MHZ , JVM 版 本 为 1.8.0_112 , Linux 系 统 版 本 为
2.6.32-504.23.4.el6.x86_64。
创建分区数为 1、20、50、100、200、500、1000 的主题,对应的主题名称分别为 topic-1 topic 20
topic-50 topic-100 topic-200 topic-500 topic-1000 ,所有主题的副本因子都设置为 1。

生产者,测试结果如下

在这里插入图片描述

消费者,测试结果与上图趋势类同

如何选择合适的分区数?从某种意恩来说,考验的是决策者的实战经验,更透彻地说,是 Kafka 本身、业务应用、硬件资源、环境配置等多方面的考量而做出的选择。在设定完分区数,或者更确切地说是创建主题之后,还要对其追踪、监控、调优以求更好地利用它

一般情况下,根据预估的吞吐量及是否与 key 相关的规则来设定分区数即可,后期可以通过增加分区数、增加 broker 或分区重分配等手段来进行改进。

分区数设置的经验参考

如果一定要给一个准则,则建议将分区数设定为集群中 broker 的倍数,即假定集群中有 3 个 broker 节点,可以设定分区数为 3/6/9 等,至于倍数的选定可以参考预估的吞吐量。

或者根据机器配置的 cpu 线程数和磁盘性能来设置最大效率的分区数:= CPU 线程数 * 1.5~2倍

不过,如果集群中的 broker 节点数有很多,比如大几十或上百、上千,那么这种准则也不太适用。

还有一个可供参考的分区数设置算法:

每一个分区的写入速度,大约 40M/s

每一个分区的读取速度,大约 60M/s

假如,数据源产生数据的速度是(峰值)800M/s ,那么为了保证写入速度,该 topic 应该设置 20个分区(副本因子为 3)。

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

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

相关文章

盖茨表示GPT-5不会比GPT-4有太大改进;Intro to Large Language Models

&#x1f989; AI新闻 &#x1f680; 盖茨表示GPT-5不会比GPT-4有太大改进 摘要&#xff1a;比尔盖茨在与德国《商报》的采访中透露&#xff0c;虽然OpenAI内部有人相信GPT-5会优于GPT-4&#xff0c;但他认为目前的生成式人工智能已经达到极限。盖茨对GPT-5未来的发展并不乐观…

Vue基础入门(四):Vue3快速开发模板

快速开发Vue的解决方案 ​ Vue 的开发需要的 node 环境&#xff0c;其实上在开发的过程中会遇到一些你想不到的问题&#xff0c;比如 npm工具的版本和 node 环境不匹配&#xff08;你把其他项目导入到自己的环境&#xff09; ​ vue-element-admin&#xff08;是一个官方提供…

Ubuntu系统CLion安装

Ubuntu系统CLion安装 pycharm 同理。 参考官网安装过程&#xff1a;官网安装过程 下载linux tar.gz包 # 解压 sudo tar -xzvf CLion-*.tar.gz -C /opt/ sh /opt/clion-*/bin/clion.sh其中第二个命令是启动CLion命令 clion安装完以后&#xff0c;不会在桌面或者菜单栏建立图…

Containerd Container管理功能解析

Containerd Container管理功能解析 container是containerd的一个核心功能&#xff0c;用于创建和管理容器的基本信息。 本篇containerd版本为v1.7.9。 更多文章访问 https://www.cyisme.top 本文从ctr c create命令出发&#xff0c;分析containerd的容器及镜像管理相关功能。 …

[英语学习][3][Word Power Made Easy]的精读与翻译优化

[序言] 这次翻译校验, 难度有点大, 原版中英翻译已出现了严重地偏差. 昨晚11点开始阅读如下段落, 花费了1个小时也没有理解原作者的核心表达, 索性睡觉了. 今早学习完朗文单词之后, 9点半开始继续揣摩. 竟然弄到了中午11点30, 终于明白原作者要表达的意思了. 废话不多说&#x…

Selenium 学习(0.14)——软件测试之测试用例设计方法——因果图法2【基本步骤及案例】

1、因果图法的基本步骤 2、案例分析 1&#xff09;分析原因和结果 2&#xff09;关联原因和结果 投入1元5角或2元&#xff0c;按下“可乐”&#xff0c;送出“可乐”【暂时忽略找零】 投入2元&#xff0c;按下“可乐”或“雪碧”。找零5角&#xff0c;送出“可乐”或“雪…

大量用户弃用5G网络,四大运营商血亏? 喜闻乐见

家人们&#xff0c;老百姓讨厌5G网络&#xff0c;不是一天两天的事情了&#xff0c;有人认为5G网络是个坑&#xff0c;我们就应该永远用4G网络才对&#xff0c;国家为何要折腾这事&#xff1f;肯定是闲的。 正是这种思维的蔓延&#xff0c;导致了大量用户弃用5G网络。这事对四大…

搭建Angular并引入NG-ZORRO组件库

作者&#xff1a;baekpcyyy&#x1f41f; 1.安装node.js 注&#xff1a;安装 16.0 或更高版本的 Node.js node官网&#xff1a;https://nodejs.org/en 2.进入angular官网 https://angular.cn/guide/setup-local 新建一个文件夹 vsc打开 打开终端 1.首先安装angular手脚架…

高级/进阶”算法和数据结构书籍推荐

“高级/进阶”算法和数据结构书籍推荐《高级算法和数据结构》 高级算法和数据结构 为什么要选择本书 谈及为什么需要花时间学算法&#xff0c;我至少可以列举出三个很好的理由。 (1)性能&#xff1a;选择正确的算法可以显著提升应用程序的速度。仅就搜索来说&#xff0c;用二…

Python基础语法之学习input()函数

Python基础语法之学习input函数 前言一、代码二、效果 前言 一、代码 # 默认是字符串类型 number input("请输入一个数字&#xff1a;") print("输入的数字是",number)二、效果 没有人可以阻止你成为自己想成为的人&#xff0c;只有你自己才能放弃梦想。…

WinMerge使用教程,WinMerge下载

一、下载 官方下载 WinMerge - You will see the difference… 官方地址&#xff1a;https://winmerge.org/ 阿里云盘下载 文件内容对比工具WinMerge2.16.25.25 https://www.alipan.com/s/r7MzudB235x 点击链接保存&#xff0c;或者复制本段内容&#xff0c;打开「阿里云盘…

机器视觉:塑造未来的智能视界

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; IT杂谈 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1; 前言&#x1f324;️ 机器视觉技术的实现☁️ 图像采集☁️ 图像处理☁️ 数据建模☁️应用展示…

94.STM32外部中断

目录 1.什么是 NVIC&#xff1f; 2.NVIC寄存器 3.中断优先级 4.NVIC的配置 设置中断分组​编辑 配置某一个中断的优先级 5.什么是EXTI 6.EXTI和NVIC之间的关系 7.SYSCFG 的介绍 1.什么是 NVIC&#xff1f; NVIC是一种中断控制器&#xff0c;主要用于处理 ARM Cort…

java--子类中访问其他成员的特点

1.在子类方法中访问其他成员(成员变量、成员方法)&#xff0c;是依照就近原则的。 ①先子类局部范围找。 ②然后子类成员范围找。 ③然后父类成员范围找&#xff0c;如果父类范围还没有找到则报错。 2.如果父类中&#xff0c;出现了重名的成员&#xff0c;会优先使用子类的…

NV040C语音芯片:让自助ATM机使用更加安全快捷

近年来&#xff0c;移动支付方式的兴起、银行加强线上化服务、数字人民币项目推进等因素的影响&#xff0c;人们使用ATM机的频率呈现小幅度的下降趋势。然而&#xff0c;自助ATM机并未从我们的视野中消失&#xff0c;它们仍然在金融领域发挥着重要的作用。未来&#xff0c;ATM机…

Rust内存布局

题图忘了来自哪里.. 整型,浮点型,struct,vec!,enum 本文是对 Rust内存布局 的学习与记录 struct A { a: i64, b: u64,}struct B { a: i32, b: u64,}struct C { a: i64, b: u64, c: i32,}struct D { a: i32, b: u64, c: i32, d: u64,}fn main(…

关于网站的favicon.ico图标的设置需要注意的几点

01-必须在网页的head标签中放上下面的说明&#xff1a; <link rel"shortcut icon" href"/favicon.ico">否则&#xff0c;浏览器虽然能读到图标&#xff0c;但是不会把图标显示在标签上。 02-浏览器会默认到网站根目录中读取文件favicon.ico&#x…

学习笔记-瑞吉外卖项目实战(一)

软件开发整体介绍 软件开发流程 角色分工 软件环境 瑞吉外卖项目介绍 项目介绍 产品原型介绍 技术选型 功能架构 角色 开发环境搭建 数据 创建database reggie&#xff0c;在里面创建表&#xff1a; maven 创建springboot项目并导入相关依赖坐标&#xff1a; 我们可以在项目…

webGL网页游戏的开发步骤

开发基于 WebGL 的网页游戏涉及多个步骤&#xff0c;包括游戏概念的设计、图形资源的创建、编码和调试等。以下是一个一般性的步骤指南&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 确定游戏概念&a…

【办公软件】XML格式文件怎么转Excel表格文件?

首先我们有一个XML格式的文件&#xff0c;例如&#xff1a; 其中的数据是这样的&#xff1a; 我们现在在路径中选中这个XML格式的文件&#xff0c;然后鼠标右键&#xff0c;选择用WPS表格打开后&#xff0c;可见XML格式的文件已经自动转换成了Excel表啦。。。。