kafka3

news2024/12/22 23:21:05

分区副本机制

kafka 0.8.0 版本开始引入了分区副本引入了数据冗余

用CAP理论来说,就是通过副本及副本leader动态选举机制提高了kafka的 分区容错性可用性

但从而也带来了数据一致性的巨大困难!

6.6.2分区副本的数据一致性困难

kafka让分区多副本同步的基本手段是: follower副本定期向leader请求数据同步!

既然是定期同步,则leader和follower之间必然存在各种数据不一致的情景!

  • 问题1:分区副本间动态不一致

  • 问题2:消费者所见不一致

如果此时leader宕机,follower1或follower2被选为新的leader,则leader换届前后,消费者所能读取到的数据发生了不一致;

  • 问题3:分区副本间最终不一致

一致性问题解决方案(HW

动态过程中的副本数据不一致,是很难解决的;

kafka先尝试着解决上述“消费者所见不一致”及“副本间数据最终不一致”的问题;

解决方案的核心思想

  • 在动态不一致的过程中,维护一条步进式的临时一致线”(既所谓的High Watermark)
  • 高水位线HW = ISR副本中最小LEO(副本的最大消息位移+1)
  • 底层逻辑就是:offset<HWmessage,是各副本间一致的且安全的!
  • 解决消费者所见不一致(消费者只允许看到HW以下的message
  • 解决分区副本数据最终不一致follower数据按HW截断)

HW方案的天生缺陷

如前所述,看似HW解决了“分区数据最终不一致”的问题,以及“消费者所见不一致”的问题,但其实,这里面存在一个巨大的隐患,导致:

  • “分区数据最终不一致”的问题依然存在
  • producer设置acks=all后,依然有可能丢失数据的问题

产生如上结果的根源是:HW高水位线的更新,与数据同步的进度,存在迟滞!

第一次fetch请求,分leader端和follower端:

leader端:

  1. 读取底层log数据。
  1. 根据fetch带过来的offset=0的数据(就是follower的LEO,因为follower还没有写入数据,因此LEO=0),更新remote LEO为0。
  1. 一轮结束后尝试更新HW,做min(leader LEO,remote LEO)的计算,结果为0。
  1. 把读取到的三条log数据,加上leader HW=0,一起发给follower副本。

follower端:

  1. 写入数据到log文件,更新自己的LEO=3。
  1. 更新HW,做min(leader HW,follower LEO)的计算,由于leader HW=0,因此更新后HW=0。

可以看出,第一次fetch请求后,leader和follower都成功写入了三条消息,但是HW都依然是0,对消费者来说都是不可见的,还需要第二次fetch请求。

第二次fetch请求,分leader端和follower端:

leader端:

  1. 读取底层log数据。
  1. 根据fetch带过来的offset=3的数据(上一次请求写入了数据,因此LEO=3),更新remote LEO为3。
  1. 尝试更新HW,做min(leader LEO,remote LEO)的计算,结果为3。
  1. 把读取到的log数据(其实没有数据),加上leader HW=3,一起发给follower副本。

follower端:

  1. 写入数据到log文件,没有数据可以写,LEO依然是3。
  1. 更新HW,做min(leader HW,follower LEO)的计算,由于leader HW=3,因此更新后HW=3。

这个时候,才完成数据的写入,并且分区HW(分区HW指的就是leader副本的HW)更新为3,代表消费者可以消费offset=0,1,2的三条消息了,上面的过程就是kafka处理消息写入和备份的全流程。

从以上步骤可看出,leader 中保存的 remote LEO 值的更新(也即HW的更新)总是需要额外一轮 fetch RPC 请求才 能完成,这意味着在 leader 切换过程中,会存在数据丢失以及数据不一致的问题!

HW会产生数据丢失和副本最终不一致问题

数据丢失的问题(即使produce设置acks=all,依然会发生)

如上图所示:

  • 状态起始:最新消息c已同步,但是水位线还没开始同步
  •  在此时leader崩溃(即 follower 没能通过下一轮请求来更新 HW 值)
  • follower成为了leader,会自动将 LEO 值调整到之前的 HW 值,即会进行日志截断
  • 然后,原来的leader重启上线,会向新的leader发送请求请求,收到 fetch 响应后,拿到 HW 值,并更新本地 HW 值,发现我也要截取,悲剧发生了,数据丢了

副本间数据最终不一致的问题(即使produce设置acks=all,依然会发生)

如上图所示:

  • 状态起始:最新消息c已同步,但是水位线还没开始同步
  •  在此时leader崩溃(即 follower 没能通过下一轮请求来更新 HW 值)
  • follower成为了leader,会自动将 LEO 值调整到之前的 HW 值,即会进行日志截断
  • 在截断日志之后,也就是这个d被截断了之后,我又加了一条数据是e
  • 然后,原来的leader重启上线,会向新的leader发送请求请求,收到 fetch 响应后,拿到 HW 值,并更新本地 HW 值,发现我的数据和leader的数据一样,好的,我就不用截取了,我更新HW就好了,就这样,一个新的悲剧又发生了,数据不一致了

只要新一届leader在老leader重启上线前,接收了新的数据,就可能发生上图中的场景,根源也在于HW的更新落后于数据同步进度

Leader-Epoch机制的引入

为了解决 HW 更新时机是异步延迟的,而 HW 又是决定日志是否备份成功的标志,从而造成数据丢失和数据不一致的现象,Kafka 引入了 leader epoch 机制;

在每个副本日志目录下都创建一个 leader-epoch-checkpoint 文件,用于保存 leader 的 epoch 信息;

leader-epoch的含义

如下,leader epoch 长这样:

它的格式为 (epoch offset),epoch指的是 leader 版本,它是一个单调递增的一个正整数值,每次 leader 变更,epoch 版本都会 +1,offset 是每一代 leader 写入的第一条消息的位移值,比如:

(0,0)

(1,300)

以上第2个版本是从位移300开始写入消息,意味着第一个版本写入了 0-299 的消息。

leader epoch 具体的工作机制

  • 当副本成为 leader 时:

这时,如果此时生产者有新消息发送过来,会首先更新leader epoch 以及LEO ,并添加到 leader-epoch-checkpoint 文件中;

  • 当副本变成 follower 时:

发送LeaderEpochRequest请求给leader副本,该请求包括了follower中最新的epoch 版本;

leader返回给follower的响应中包含了一个LastOffset,如果 follower last epoch = leader last epoch(纪元相同),则 LastOffset = leader LEO,否则取follower last epoch 中最小的 leader epoch 的 start offset 值;

举个例子:假设 follower last epoch = 1,此时 leader (1, 20) (2, 80) (3, 120),则 LastOffset = 80

follwer 拿到 LastOffset 之后,会对比当前 LEO 值是否大于 LastOffset,如果当前 LEO 大于 LastOffset,则从 LastOffset 截断日志;

follower 开始发送 fetch 请求给 leader 保持消息同步。

leader epoch 如何解决HW的备份缺陷

  • 解决数据丢失和数据不一致的问题

如上图所示:

follower当选leader后,收到纪元消息,发现 LastOffset等于当前 LEO 值,故不用进行日志截断。

follower重启后同步消息,发现自己也不用截取,数据一致,齐活儿

当然,如果说后来增加消息以后,也不需要截取,直接同步数据就行(当ack=-1)

LEO/HW/LSO等相关术语速查

LEO:(last end offset)就是该副本中消息的最大偏移量的值+1 ;

HW:high watermark)各副本中LEO的最小值。这个值规定了消费者仅能消费HW之前的数据;

LW:(low watermark)一个副本的log中,最小的消息偏移量;  应该是和log里面的偏移量有关系

LSO:(last stable offset) 最后一个稳定的offset;对未完成的事务而言,LSO 的值等于事务中第一条消息的位置(firstUnstableOffset),对已完成的事务而言,它的值同 HW 相同;

LEOHW 与数据一致性密切相关;

如图,各副本中最小的LEO是3,所以HW是3,所以,消费者此刻最多能读到Msg2;

不清洁选举[了解]

不清洁选举,是指允许“非ISR副本”可以被选举为leader;非ISR副本被选举为leader,将极大增加数据丢失及数据不一致的可能性!由参数 unclean.leader.election.enable=false(默认) 控制;

  • 初始状态: follower2严重落后于leader,并且不属于ISR副本

  • 此刻,所有ISR副本宕机:

  • Follower2成为新的leader,并接收数据

  • 之前宕机副本重启,按照最新leader的最新leo进行截断,产生数据丢失及不一致

幂等性

幂等性要点

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幂等性实现机制

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

2)producer每发送一条消息,会将<producer_id,分区>对应的“序列号”加1

3)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的事务控制原理

主要原理:  开始事务-->发送一个ControlBatch消息(事务开始)

                  提交事务-->发送一个ControlBatch消息(事务提交)

                  放弃事务-->发送一个ControlBatch消息(事务终止)

  • 开启事务的必须配置参数(我不支持数据得回滚,但是我能做到,一荣俱荣,一损俱损)

Java
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");

事务控制的代码模板

Java
// 初始化事务
producer.initTransaction( )

// 开启事务   
producer.beginTransaction( )
   

// 干活

// 提交事务
producer.commitTransaction( )

// 异常回滚(放弃事务) catch里面
producer.abortTransaction( )

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

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

isolation.level=read_uncommitted(默认值)

isolation.level=read_committed

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

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

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

Java
producer.sendOffsetsToTransaction(offsets,consumer_id)

事务api示例

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

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

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

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

Java
package com.doit.day04;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
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.errors.ProducerFencedException;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;

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

        Properties props = new Properties();
        //
消费者的
        props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"linux01:9092");
        props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "shouwei");
        //自动提交偏移量
        props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
        props.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");

        //写生产者的一些属性
        props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"linux01:9092");
        props.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        //设置ack 开启幂等性必须设置的三个参数
        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");
        //开启事务
        props.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"id_fro_39_19");

        //消费数据
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
        //初始化事务
        producer.initTransactions();
        //订阅主题
        consumer.subscribe(Arrays.asList("eventlog"));
        while (true){
            //拉取数据
            ConsumerRecords<String, String> poll = consumer.poll(Duration.ofMillis(Integer.MAX_VALUE));
            try {
                //开启事务
                producer.beginTransaction();
                for (ConsumerRecord<String, String> record : poll) {
                    String value = record.value();
                    //将value的值写入到另外一个topic中
                    producer.send(new ProducerRecord<String,String>("k2k",value));
                }
                producer.flush();
                //提交偏移量
                consumer.commitAsync();
                //提交事务
                producer.commitTransaction();

            } catch (ProducerFencedException e) {
                //放弃事务
                producer.abortTransaction();
            }
        }
    }
}

6.8.3事务实战案例

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

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

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

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

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

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

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

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

分区数与吞吐量

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 22线程,内存8G ,每条消息1k

测试结果:  topic12个分区时,写入、读取的效率都是达到最高

写入: 75MB/s  7.5万条/s

读出: 310MB/s 31万条/s

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

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

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

相关文章

多模态学习

什么是多模态学习&#xff1f; 模态 模态是指一些表达或感知事物的方式&#xff0c;每一种信息的来源或者形式&#xff0c;都可以称为一种模态 视频图像文本音频 多模态 多模态即是从多个模态表达或感知事物 多模态学习 从多种模态的数据中学习并且提升自身的算法 多…

【k8s 系列】k8s 学习三,docker回顾,k8s 起航

k8s 逐渐已经作为一个程序员不得不学的技术&#xff0c;尤其是做云原生的兄弟们&#xff0c;若你会&#xff0c;那么还是挺难的 学习 k8s &#xff0c;实践尤为重要&#xff0c;如果身边有自己公司就是做云的&#xff0c;那么云服务器倒是不用担心&#xff0c;若不是&#xff…

IMX6ULL PHY 芯片驱动

前言 之前使用 IMX6ULL 开发板时遇到过 nfs 挂载不上的问题&#xff0c;当时是通过更换官方新版 kernel 解决的&#xff0c;参考《挂载 nfs 文件系统》。 今天&#xff0c;使用自己编译的 kernel 又遇到了该问题&#xff0c;第二次遇到了&#xff0c;该正面解决了。 NFS 挂载…

18JS09——作用域

作用域 一、作用域1、作用域 二、变量的作用域1、变量作用域的分类2、全局变量3、局部变量4、全局变量和局部变量区别 三、作用域链 目标&#xff1a; 1、作用域 2、变量的作用域 3、作用域链 一、作用域 1、作用域 通常来说&#xff0c;一段程序代码中所用到的名字并不总是有…

python基础----06-----文件读写追加操作

一 文件编码概念 思考&#xff1a;计算机只能识别: 0和1&#xff0c;那么我们丰富的文本文件是如何被计算机识别&#xff0c;并存储在硬盘中呢? 答案&#xff1a;使用编码技术(密码本)将内容翻译成0和1存入。 常见编码有UTF8&#xff0c;gbk等等。不同的编码&#xff0c;将内…

vulnhub靶场之DC-3渗透教程(Joomla CMS)

目录 0x01靶机概述 0x02靶场环境搭建 0x03主机发现 0x04靶场渗透过程 ​ 0x05靶机提权 0x06渗透实验总结 0x01靶机概述 靶机基本信息&#xff1a; 靶机下载链接https://download.vulnhub.com/dc/DC-3-2.zip作者DCAU发布日期2020年4月25日难度中等 0x02靶场环…

【Flink】DataStream API使用之输出算子(Sink)

输出算子&#xff08;Sink&#xff09; Flink作为数据处理框架&#xff0c;最终还是需要把计算处理的结果写入到外部存储&#xff0c;为外部应用提供支持。Flink提供了很多方式输出到外部系统。 1. 连接外部系统 在Flink中我们可以在各种Fuction中处理输出到外部系统&#xf…

C#读写参数到APP.Config

C#读写参数到APP.Config 介绍程序Demo常见错误 介绍 系统在开发时&#xff0c;可能需要设置默认参数&#xff0c;比如数据库的链接参数&#xff0c;某个参数的默认数据等等。对于这些数据&#xff0c;可直接在app.config中读取。 在读写时&#xff0c;需要先了解configuratio…

echo命令在Unix中的作用以及其常见用法

在Unix系统中&#xff0c;"echo"是一个常用的命令&#xff0c;用于在终端或脚本中输出文本。它可以将指定的字符串或变量的值打印到标准输出&#xff0c;从而向用户提供信息或进行调试。 本文将详细介绍"echo"命令在Unix中的作用以及其常见用法。 基本语法…

Keras-3-实例1-二分类问题

1. 二分类问题 1.1 IMDB 数据集加载 IMDB 包含5w条严重两极分化的评论&#xff0c;数据集被分为 2.5w 训练数据 和 2.5w 测试数据&#xff0c;训练集和测试集中的正面和负面评论占比都是50% from keras.datasets import imdb(train_data, train_labels), (test_data, test_l…

UE5 Chaos破碎系统学习1

在UE5中&#xff0c;Chaos破碎系统被直接进行了整合&#xff0c;本篇文章就来讲讲chaos的基础使用。 1.基础破碎 1.首先选中需要进行破碎的模型&#xff0c;例如这里选择一个Box&#xff0c;然后切换至Fracture Mode&#xff08;破碎模式&#xff09;&#xff1a; 2.点击右侧…

JAVA实现打字练习软件

转眼已经学了一学期的java了&#xff0c;老师让我们根据所学知识点写一个打字练习软件的综合练习。一开始我也不是很有思路&#xff0c;我找了一下发现csdn上关于这个小项目的代码也不算很多&#xff0c;所以我最后自己在csdn查了一些资料&#xff0c;写了这么一个简略版本的打…

【C++】——list的介绍及模拟实现

文章目录 1. 前言2. list的介绍3. list的常用接口3.1 list的构造函数3.2 iterator的使用3.3 list的空间管理3.4 list的结点访问3.5 list的增删查改 4. list迭代器失效的问题5. list模拟实现6. list与vector的对比7. 结尾 1. 前言 我们之前已经学习了string和vector&#xff0c…

Remix IDE已支持Sui Move在线开发

网页版Remix IDE与WELLDONE Code插件结合&#xff0c;让您无需本地设置或安装即可开始构建Sui应用程序。 不熟悉Sui的构建者可能想在正式配置开发环境之前&#xff0c;浅尝一下构建Sui应用程序。Remix IDE与WELLDONE Code插件组合&#xff0c;即可帮助构建者实现从浏览器窗口开…

JavaScript函数的增强知识

函数属性和arguments以及剩余参数 函数属性name与length ◼ 我们知道JavaScript中函数也是一个对象&#xff0c;那么对象中就可以有属性和方法。 ◼ 属性name&#xff1a;一个函数的名词我们可以通过name来访问&#xff1b; // 自定义属性foo.message "Hello Foo"…

Nginx 之 Tomcat 负载均衡、动静分离

一.详细安装及操作实例&#xff08;Nginx 七层代理&#xff09; 首先至少准备三台服务器 Nginx 服务器&#xff1a;192.168.247.131:80 Tomcat服务器1&#xff1a;192.168.247.133:80 Tomcat服务器2&#xff1a;192.168.247.134:8080 192.168.247.134:80811.部署Nginx 负载均…

微信自动回复怎么设置呢?

友友们 你们是否有以下这些烦恼 1、每天要手动点击“添加”按钮多次以通过大量好友? 2、你是否经常需要在多个微信帐号之间来回切换&#xff1f; 3、你的回复速度慢&#xff0c;导致客户流失率高&#xff1f; 4、为了及时回复&#xff0c;你总是需要带着多部手机出门&…

二十一、C++11(中)

文章目录 一、左值&右值&#xff08;一&#xff09;基本概念1.左值是什么2.右值是什么 &#xff08;二&#xff09;左值引用和右值引用1.左值引用2.右值引用 二、右值引用使用场景和意义&#xff08;一&#xff09;引入&#xff08;二&#xff09;左值引用的使用场景&#…

Linux编译器(gcc/g++)调试器gdb项目自动化构建工具(make/Makefile)版本管理git

Linux编译器-gcc/g&&调试器gdb&&项目自动化构建工具-make/Makefile&&版本管理git &#x1f506;gcc/g的使用可执行文件的"生产"过程gcc如何完成预处理编译汇编链接 函数库函数库一般分为静态库和动态库两种静态C/C库的安装 gcc选项gcc选项记…

WPF 学习:如何照着MaterialDesign的Demo学习

文章目录 往期回顾对应视频资源如何照着wpf项目学习找到你想要抄的页面查找对应源码演示示例如何认清页面元素抄袭实战 项目地址总结 往期回顾 WPF Debug运行是 实时可视化树无效&#xff0c;无法查看代码 WPF MaterialDesign 初学项目实战&#xff08;0&#xff09;:github …