一场 Kafka CRC 异常引发的血案

news2024/9/20 6:09:28

一、问题概述

客户的生产环境突然在近期间歇式的收到了Kafka CRC的相关异常,异常内容如下

Record batch for partition skywalking-traces-0 at offset 292107075 is invalid, cause: Record is corrupt (stored crc = 1016021496, compute crc = 1981017560)

报错没有规律性,有可能半天都不出现一次,也有可能一小时出现2、3次

而这个报错会导致Kafka的Consumer hang死,即无法继续消费后续的消息,只能手动重启Consumer才能继续,是非常严厉的报错,导致生产不可用

简单解释一下这个异常的原因,Kafka会在每个Batch的header中存储持久化下来的消息体的CRC,所谓CRC可以简单理解为TCP的checkSum,而后Consumer在收到这条消息后,将Batch header中存储的CRC取出来,然后再根据统一的CRC算法计算收到的消息体的CRC值,而后拿上这两个值做一下比对,如果一样,说明消息没有被篡改,如果不一样,就会扔出CRC异常

由于是公司的重保客户,又出现如此严重的惊天bug,一时间公司的高层领导均高度重视此事,每天晚上的夕会(均是领导)也都要单独询问解决进度,一时间,压力山大

二、分析定位

2.1、问题分析

在公司另外一个项目中(简称A客户,我们当前要处理的简称B客户),这个报错并不陌生,且报错的内容一模一样,那这两者会不会是同一个bug呢?答案是否定的

  • A客户:确实会报CRC异常,但稳定复现,只要出现了,不论Consumer重启多少次,也不论多少个Consumer来消费,均是会报CRC异常。(最终定位是A客户的磁盘出现的坏点,导致数据异常,更换磁盘后,问题消失)
  • B客户:问题只会出现一次,如果手动重启Consumer,该问题便会消失,如果换个Consumer来消费,问题也会消失,好像它只是昙花一现,且再也无法复现,如同鬼魅

那这个问题不是常规问题,就比较棘手了

2.2、Kafka自身bug ?

笔者长期活跃在Kafka社区,是Kafka社区的Contributer,且在阿里的公有云工作多年,但这个报错在之前却是一次没有遇到过

在我印象中,CRC错误已经好久没见到了,而后我排查了社区所有CRC有关的issue list,发现只有在0.10之前的低版本中出现过类似异常,而我们的B客户生产环境使用的是2.8.2,这是整个2版本中的最后一个版本,而Kafka 2.0以后的版本却很少出现这个异常

而Kafka自身计算CRC的逻辑却非常简洁,只有几十行java代码,综上,笔者认为Kafka自身出bug的概率相对较小,不能把重要精力放在这块

2.3、报错倾向分析

接下来我们便对这个异常的报错倾向开始分析,经过各种排查,它具备以下特质:
 

  • 间歇性、概率性、不确定性;有时候可能半天都不出现一次,也有可能一小时出现2、3次
  • 无法复现;出现问题的消息,重新消费,CRC异常会消失
  • B客户现场有8套环境,现只有一套环境存在此问题,对比其他7套环境,粗略看在磁盘型号、CPU型号等硬件设备是保持一致的
  • 查看出问题的Broker,其不具备统一性,也没有落在同一台宿主机上
  • 怀疑是内存条错误,排查对应的日志没有发现异常信息
  • 与操作系统的同学确认,咱们现在虽然使用的是我们自研的OS,但TCP协议这块却没有改动过,使用的是Linux原生的
  • 在家里的环境模拟网络丢包、错包的场景,没有复现CRC异常

至此,好像均一切正常,粗略排查后,没有发现有价值的线索。因此我们便将重心转移至“网络”+“磁盘”这两块看似不太可能出现问题的基础能力上

三、网络排查

3.1、埋点

进行网络排查的话,我们就要执行TCP抓包,即执行tcpdump命令,但抓包也不是简单在机器上执行一个命令那么简单,当前的部署环境是3台Broker、3台Consumer,异常会暴露在Consumer中,但单台Consumer可能会与多台Broker建连

因此抓包的思路是在3台Broker上均创建监听,且在某一台consumer也创建监听,而后观察TCP发送出去的数据与接收到的数据是否一致

首先在3台Broker上执行dump命令,由于出现问题的概率不高,可能导致网络dump包非常大,因此在命令中将TCP包拆成500M大小的文件,且只保留最近的5个文件,3台Broker的IP如下:

  • 10.0.0.70
  • 10.0.0.71
  • 10.0.0.72

而3台Consumer的IP为:

  • 10.0.0.18
  • 10.0.0.19
  • 10.0.0.20

首先在1台consumer上执行如下命令:

sudo nuhup tcpdump -i vethfe2axxxx -nve host 10.0.0.70 or host 10.0.0.71 or host 10.0.0.72 -w broker_18.pcap -C 500 -W 5 -Z ccadmin &
sudo nuhup tcpdump -i vethfe2axxxx -nve host 10.0.0.70 or host 10.0.0.71 or host 10.0.0.72 -w broker_19.pcap -C 500 -W 5 -Z ccadmin &
sudo nuhup tcpdump -i vethfe2axxxx -nve host 10.0.0.70 or host 10.0.0.71 or host 10.0.0.72 -w broker_20.pcap -C 500 -W 5 -Z ccadmin &

其次在3台Broker上分别执行如下:

sudo nuhup tcpdump -i xxxxxxxxx1 -nve host 10.0.0.18 -w broker_18.pcap -C 500 -W 5 -Z ccadmin &
sudo nuhup tcpdump -i xxxxxxxxx2 -nve host 10.0.0.19 -w broker_19.pcap -C 500 -W 5 -Z ccadmin &
sudo nuhup tcpdump -i xxxxxxxxx3 -nve host 10.0.0.20 -w broker_20.pcap -C 500 -W 5 -Z ccadmin &

简单解释一下命令含义:

  • -i vethfe2axxxx 后面添加的是网卡名称
  • -nve host 需要添加的是需要监听的IP
  • -w broker_18.pcap 会将记录生成到broker_18.pcap的文件中
  • -C 500 每隔500M生成一个文件
  • -W 5 保留最近生成的5个文件
  • -Z ccadmin 指定用户名

3.2、异常浮现

而后我们在屏幕前等待了漫长的近2个小时后,终于捕获到了一条异常信息,因此马不停蹄地将数据下载下来,进行对照分析。首先看到的便是Wireshark帮助解析生成的前后报文

报文总长度为2712个字节,约3K的数据,但我们惊讶地发现,其中有15个字节的数据出现了偏差

至此,定位可能就是网络传输出现了问题,不过我们还需要做更多的校验工作。Wireshark工具虽然好用,但却不是万能的,它只能解析Kafka的传输协议,无法对内容进行校验与还原

(我们在样本中随机取了3个正常交互的消息进行对比,发现在网络发送前与发送后的数据完全一致)

3.3、Kafka协议解析

Kafka的协议分为传输协议及存储协议,笔者对它们相对比较熟悉,因此开始直接对前后传输的报文进行了解析,首先是Request

import org.apache.kafka.common.requests.FetchRequest;
import org.apache.kafka.common.requests.RequestHeader;

import javax.xml.bind.DatatypeConverter;
import java.nio.ByteBuffer;

public class MyTest {
    public static void main(String[] args) {

        // 08-30 15:06:40.820028
        String hexString =
            "0e 1f 48 2d 7e 32 06 82 25 e9 a3 d9 08 00 45 00 " +
                "00 ab 3f b3 40 00 40 06 35 ed 62 02 00 55 62 02 " +
                "00 54 eb 2e 23 85 68 57 32 37 b5 08 3f b2 80 18 " +
                "7d 2c c5 4a 00 00 01 01 08 0a b7 d1 e6 5c 26 30 " +
                "b9 11 00 00 00 73 00 01 00 0c 11 85 6f bf 00 15 " +
                "62 72 6f 6b 65 72 2d 31 30 30 32 2d 66 65 74 63 " +
                "68 65 72 2d 30 00 00 00 03 ea 00 00 01 f4 00 00 " +
                "00 01 00 a0 00 00 00 72 52 11 e0 11 85 6f bf 02 " +
                "13 5f 5f 63 6f 6e 73 75 6d 65 72 5f 6f 66 66 73 " +
                "65 74 73 02 00 00 00 15 00 00 00 03 00 00 00 00 " +
                "13 36 67 3a 00 00 00 03 00 00 00 00 00 00 00 00 " +
                "00 10 00 00 00 00 01 01 00";
        // FetchRequestData(clusterId=null, replicaId=1002, maxWaitMs=500, minBytes=1, maxBytes=10485760,
        // isolationLevel=0, sessionId=1917981152, sessionEpoch=293957567,
        // topics=[FetchTopic(topic='__consumer_offsets', partitions=[FetchPartition(partition=21, currentLeaderEpoch=3,
        // fetchOffset=322332474, lastFetchedEpoch=3, logStartOffset=0, partitionMaxBytes=1048576)])], forgottenTopicsData=[], rackId='')



        StringBuilder sb = new StringBuilder();
        char[] charArray = hexString.toCharArray();
        for (char c : charArray) {
            if (c != ' ') {
                sb.append(c);
            }
        }
        hexString = sb.toString();
        byte[] byteArray = DatatypeConverter.parseHexBinary(hexString);
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        System.out.println("len is : " + byteBuffer.array().length);
        byteBuffer.get(new byte[66]);
        byteBuffer.getInt();

        RequestHeader header = RequestHeader.parse(byteBuffer);
        System.out.println(header);
        FetchRequest fetchRequest = FetchRequest.parse(byteBuffer, (short) 12);
        System.out.println(fetchRequest);
    }
}

最终输出如下

len is : 185
RequestHeader(apiKey=FETCH, apiVersion=12, clientId=broker-1002-fetcher-0, correlationId=293957567)
FetchRequestData(clusterId=null, replicaId=1002, maxWaitMs=500, minBytes=1, maxBytes=10485760, isolationLevel=0, sessionId=1917981152, sessionEpoch=293957567, topics=[FetchTopic(topic='__consumer_offsets', partitions=[FetchPartition(partition=21, currentLeaderEpoch=3, fetchOffset=322332474, lastFetchedEpoch=3, logStartOffset=0, partitionMaxBytes=1048576)])], forgottenTopicsData=[], rackId='')

且TCP传输前后的内容一致,因此问题没有出现在Request上

接下来就需要进行Response的二进制协议解析,代码如下

import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.record.MemoryRecords;
import org.apache.kafka.common.requests.FetchResponse;
import org.apache.kafka.common.requests.ResponseHeader;

import javax.xml.bind.DatatypeConverter;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;

public class MyTest2 {
    public static void main(String[] args) {

        08-30 15:06:41.853611
               String hexString =
                   "0e 1f 48 2d 7e 32 06 82 25 e9 a3 d9 08 00 45 00 " +
                       "00 ab 3f b3 40 00 40 06 35 ed 62 02 00 55 62 02 " +
                       "00 54 eb 2e 23 85 68 57 32 37 b5 08 3f b2 80 18 " +
                       "7d 2c c5 4a 00 00 01 01 08 0a b7 d1 e6 5c 26 30 " +
                       "b9 11 00 00 00 f9 11 85 6f c1 00 00 00 00 00 00 " +
                       "00 72 52 11 e0 02 13 5f 5f 63 6f 6e 73 75 6d 65 " +
                       "72 5f 6f 66 66 73 65 74 73 03 00 00 00 03 00 00 " +
                       "00 00 00 00 15 09 0c 9c 00 00 00 00 15 09 0c 9c " +
                       "00 00 00 00 00 00 00 00 00 ff ff ff ff 89 01 " +
        
                       "      00 00 00 00 15 09 0c 9c 00 00 00 7c 00 00 " +
                       "00 03 02 08 16 d8 10 00 00 00 00 00 00 00 00 01 " +
                       "91 a2 1b 8f 3c 00 00 01 91 a2 1b 8f 3c ff ff ff " +
                       "ff ff ff ff ff ff ff 00 00 00 00 00 00 00 01 92 " +
                       "01 00 00 00 56 00 01 00 0b 43 43 4d 2d 65 6e 63 " +
                       "72 79 70 74 00 16 43 53 53 2d 49 49 53 2d 53 69 " +
                       "74 75 61 74 69 6f 6e 51 75 65 72 79 00 00 00 00 " +
                       "30 00 03 00 00 00 00 00 00 00 00 ff ff ff ff 00 " +
                       "00 00 00 01 91 a2 1b 8f 3b 00 " +
        
        
        
                       "      00 00 00 00 15 00 00 00 00 00 00 13 36 67 " +
                       "47 00 00 00 00 13 36 67 47 00 00 00 00 00 00 00 " +
                       "00 00 ff ff ff ff 01 00 00 00 ";


//        String hexString =
//                "0e 1f 48 2d 7e 32 06 82 25 e9 a3 d9 08 00 45 00 " +
//                "00 ab 3f b3 40 00 40 06 35 ed 62 02 00 55 62 02 " +
//                "00 54 eb 2e 23 85 68 57 32 37 b5 08 3f b2 80 18 " +
//                "7d 2c c5 4a 00 00 01 01 08 0a b7 d1 e6 5c 26 30 " +
//                "b9 11 00 00 04 65 11 85 6f c0 00 00 00 00 00 00 " +
//                "00 72 52 11 e0 03 13 5f 5f 63 6f 6e 73 75 6d 65 " +
//                "72 5f 6f 66 66 73 65 74 73 02 00 00 00 15 00 00 " +
//                "00 00 00 00 13 36 67 3b 00 00 00 00 13 36 67 3b " +
//                "00 00 00 00 00 00 00 00 00 ff ff ff ff da 07 " +
//
//                "      00 00 00 00 13 36 67 3b 00 00 03 cd 00 00 " +
//                "00 03 02 5c 37 79 85 00 00 00 00 00 0b 00 00 01 " +
//                "91 a2 1b 8f 3c 00 00 01 91 a2 1b 8f 37 ff ff ff " +
//                "ff ff ff ff ff ff ff 00 00 00 00 00 00 00 0c 96 " +
//                "01 00 00 00 5a 00 01 00 0b 43 43 4d 2d 65 6e 63 " +
//                "72 79 70 74 00 16 43 53 53 2d 49 49 53 2d 53 69 " +
//                "74 75 61 74 69 6f 6e 51 75 65 72 79 00 00 00 00 " +
//                "30 00 03 00 00 00 00 00 00 00 00 ff ff ff ff 00 " +
//                "00 00 00 01 91 a2 1b 8f 3b 00 " +
//
//
//
//                "      00 00 00 00 15 00 00 00 00 00 00 13 36 67 " +
//                "47 00 00 00 00 13 36 67 47 00 00 00 00 00 00 00 " +
//                "00 00 ff ff ff ff 01 00 00 00 ";


        StringBuilder sb = new StringBuilder();
        char[] charArray = hexString.toCharArray();
        for (char c : charArray) {
            if (c != ' ') {
                sb.append(c);
            }
        }
        hexString = sb.toString();
        byte[] byteArray = DatatypeConverter.parseHexBinary(hexString);
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        System.out.println("len is : " + byteBuffer.array().length);
        byteBuffer.get(new byte[66]);
        byteBuffer.getInt();


        ResponseHeader responseHeader = ResponseHeader.parse(byteBuffer, (short) 0);
        System.out.println("responseHeader is " + responseHeader);
        FetchResponse<MemoryRecords> fetchResponse = FetchResponse.parse(byteBuffer, (short) 11);
        System.out.println(fetchResponse);
        LinkedHashMap<TopicPartition, FetchResponse.PartitionData<MemoryRecords>> map = fetchResponse.responseData();
        System.out.println("map size is : " + map.size());
        System.out.println("map is : " + map);
        for (Map.Entry<TopicPartition, FetchResponse.PartitionData<MemoryRecords>> entry : map.entrySet()) {
            System.out.println();
            System.out.println();
            System.out.println();
            System.out.println();
            System.out.println("TP is : " + entry.getKey());
            FetchResponse.PartitionData<MemoryRecords> value = entry.getValue();
            MemoryRecords records = value.records();
            records.batches().forEach(batch -> {
                System.out.println("isValid: " + batch.isValid());
                System.out.println("crc : " + batch.checksum());
                System.out.println("baseOffset : " + batch.baseOffset());
            });
        }
    }
}

其中关键的部分是查看2个CRC的逻辑:

  • 一块是从协议体中获取到的,即Broker计算的CRC内容,这个对比传输前后均一致
  • 一块是根据消息内容动态计算的,消息传输前,动态计算的CRC内容能与自身存储的对齐,但消息接收后,动态计算的CRC便发生了偏差

下图是真实生产业务日志中爆出的异常

其中发现计算出来的CRC是2481280076。而后将我们dump下来的二进制进行协议解码,执行上述代码后发现结果与生产环境的一致

四、磁盘排查

后续我们对发送出去的内容与磁盘的内容做了对比,内容一致,因此bug不是因磁盘导致

五、结论

由于出现问题的消息在TCP发送前后出现了diff,而正常收到的消息,在TCP发送前后均一致,因此

定位为网络出现了问题

网络的同学也认为这个现象不符合预期,已经开始介入了排查

总结:其实根据这些年处理Kafka异常的经验,这个bug第一时间拿到的时候,就感觉不像是Kafka自己的问题,关键是异常报错在Kafka的Broker端,我们需要不断提升自证清白的能力

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

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

相关文章

时间同步服务

多主机协作工作时&#xff0c;各个主机的时间同步很重要&#xff0c;时间不一致会造成很多重要应用的故障&#xff0c;如&#xff1a;加密协 议&#xff0c;日志&#xff0c;集群等。 利用NTP&#xff08;Network Time Protocol&#xff09; 协议使网络中的各个计算机时间达到…

网络安全运维培训一般多少钱

在当今数字化时代&#xff0c;网络安全已成为企业和个人关注的焦点。而网络安全运维作为保障网络安全的重要环节&#xff0c;其专业人才的需求也日益增长。许多人都对网络安全运维培训感兴趣&#xff0c;那么&#xff0c;网络安全运维培训一般多少钱呢? 一、影响网络安全运维培…

RISC-V (十一)软件定时器

主要的思想&#xff1a;硬件定时器是由硬件的定时器设备触发的。软件定时器在硬件定时器的基础上由软件控制实现多个定时器的效果。主要的思路是在trap_handler函数中加入软件代码&#xff0c;使其在设定的时间点 去执行想要执行的功能函数。 定时器的分类 硬件定时器&#xf…

Linux 复制目录和文件

概述 cp 命令主要可用于复制文件或目录。 cp 是单词 copy 的缩写。 语法 cp 命令的语法如下: cp [选项] source dest。即复制 source 文件到 dest。 该命令支持的选项有: 选项说明-r递归复制整个文件夹-i若目标文件已经存在,则会询问是否覆盖-p保留源文件或目录的所有属性…

安卓玩机工具-----ADB方式的刷机玩机工具“秋之盒”’ 测试各项功能预览

秋之盒 安卓玩机工具-秋之盒是一款ADB刷机工具箱&#xff0c;基于谷歌ADB的一款绿色安装&#xff0c;具备了海量扩展模块,支持ADB刷机救砖、一键激活黑域、adb指令修复等功能&#xff0c;是一款开源、免费、易用的手机刷机工具&#xff01; 并且是一款开源、免费、易用的图形化…

OneHotEncoder一个不太合理的地方

OneHotEncoder&#xff0c;在Xtrain上fit&#xff0c;在Xtest上transform 如果遇到某个值出现在Xtest&#xff0c;而没有在Xtrain出现过时&#xff0c;会抛出如下错误&#xff1a; OneHotEncoder Found unknown categories [xxx] in column xx during transform OneHotEncoder …

简单实用的php全新实物商城系统

免费开源电商系统,提供灵活的扩展特性、高度自动化与智能化、创新的管理模式和强大的自定义模块,让电商用户零成本拥有安全、高效、专业的移动商城。 代码是全新实物商城系统源码版。 代码下载

Prometheus 服务监控

官网&#xff1a;https://prometheus.io Prometheus 是什么 Prometheus 是一个开源的系统监控和报警工具&#xff0c;专注于记录和存储时间序列数据&#xff08;time-series data&#xff09;。它最初由 SoundCloud 开发&#xff0c;并已成为 CNCF&#xff08;云原生计算基金会…

基于EPS32C3电脑远程开机模块设计

基于EPS32C3电脑远程开机模块设计 前言 缘起&#xff0c;手头资料太多了&#xff0c;所以想组一台NAS放在家里存储数据。在咸鱼淘了一套J3160主板加机箱&#xff0c;加上几块硬盘组建NAS。 对于NAS&#xff0c;我的需求是不用的时候关机(节省功耗)&#xff0c;要用的时候开机…

每日OJ_牛客_骆驼命名法(递归深搜)

目录 牛客_骆驼命名法&#xff08;简单模拟&#xff09; 解析代码 牛客_骆驼命名法&#xff08;简单模拟&#xff09; 骆驼命名法__牛客网 解析代码 首先一个字符一个字符的读取内容&#xff1a; 遇到 _ 就直接跳过。如果上一个字符是 _ 则下一个字符转大写字母。 #inclu…

【MRI基础】TR 和 TE 时间概念

重复时间 (TR) 磁共振成像 (MRI) 中的 TR&#xff08;重复时间&#xff0c;repetition time&#xff09;是施加于同一切片的连续脉冲序列之间的时间间隔。具体而言&#xff0c;TR 是施加一个 RF&#xff08;射频&#xff09;脉冲与施加下一个 RF 脉冲之间的持续时间。TR 以毫秒…

LEAN 类型理论之注解(Annotations of LEAN Type Theory)-- 小结(Summary)

在证明LEAN类型理论的属性前&#xff0c;先对LEAN类型理论所定义的所有推演规则做一个小结&#xff0c;以便后面推导LEAN类型理论的属性。各部分的注解请查看对应文章。 注&#xff1a;这些都是在《LEAN类型理论》中截取出来的&#xff0c;具体内容&#xff0c;读者可参考该论…

ApacheKafka中的设计

文章目录 1、介绍1_Kafka&MQ场景2_Kafka 架构剖析3_分区&日志4_生产者&消费者组5_核心概念总结6_顺写&mmap7_Kafka的数据存储形式 2、Kafka的数据同步机制1_高水位&#xff08;High Watermark&#xff09;2_LEO3_高水位更新机制4_副本同步机制解析5_消息丢失问…

Redis典型应用 - 分布式锁

文章目录 目录 文章目录 1. 什么是分布式锁 2. 分布式锁的基本实现 3. 引入过期时间 4. 引入校验Id 5. 引入 watch dog(看门狗) 6. 引入redlock算法 工作原理 Redlock的优点&#xff1a; 总结 1. 什么是分布式锁 在一个分布式系统中,也可能会出现多个节点访问一个共…

QT 编译报错:C3861: ‘tr‘ identifier not found

问题&#xff1a; QT 编译报错&#xff1a;C3861&#xff1a; ‘tr’ identifier not found 原因 使用tr的地方所在的类没有继承自 QObject 类 或者在不在某一类中&#xff0c; 解决方案 就直接用类名引用 &#xff1a;QObject::tr( )

关于易优cms自定义字段不显示的问题

今天在该易优cms自定义字段&#xff0c;折腾了大半天没显示出来&#xff0c;原来是改错对方了。 主要引用的时候 要放在list标签内&#xff0c;不要看文档&#xff0c;把addfields 放在list标签上 例如 {eyou:list loop8} <li><a href"{$field.arcurl}">…

基于yolov8的电动车佩戴头盔检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的电动车佩戴头盔检测系统利用了YOLOv8这一先进的目标检测模型&#xff0c;旨在提高电动车骑行者的安全意识&#xff0c;减少因未佩戴头盔而导致的交通事故风险。YOLOv8作为YOLO系列的最新版本&#xff0c;在检测速度和精度上均进行了优化&#xff0c;…

✨机器学习笔记(一)—— 监督学习和无监督学习

1️⃣ 监督学习&#xff08;supervised learning&#xff09; ✨ 两种主要类型的监督学习问题&#xff1a; 回归&#xff08;regression&#xff09;&#xff1a;predict a number in infinitely many possible outputs. 分类&#xff08;classification&#xff09;&#xff1…

C#串口助手初级入门

1.创建项目 修改项目名称与位置&#xff0c;点击创建 2.进入界面 在视图中打开工具箱&#xff0c;鼠标拖动&#xff0c;便可以在窗口添加控件&#xff0c;右边可以查看与修改属性 3.解决方案资源管理器 发布之前&#xff0c;需要修改相关的信息&#xff0c;比如版本号&#x…

Lombok jar包引入和用法

大家好&#xff0c;今天分享一个在编写代码时的快捷方法。 当我们在封装实体类时&#xff0c;会使用set、get等一些方法。如下图&#xff0c;不但费事还影响代码的美观。 那么如何才能减少代码的冗余呢&#xff0c;首先lib中导入lombok的jar包并添加库。 此处我已导入&#xf…