二、Kafka生产与消费全流程

news2024/11/18 21:50:01

Kafka生产与消费全流程

Kafka是一款消息中间件,消息中间件本质就是收消息与发消息,所以这节课我们会从一条消息开始生产出发,去了解生产端的运行流程,然后简单的了解一下broker的存储流程,最后这条消息是如何被消费者消费掉的。其中最核心的有以下内容。

1、Kafka客户端是如何去设计一个非常优秀的生产级的保证高吞吐的一个缓冲机制

2、消费端的原理:每个消费组的群主如何选择,消费组的群组协调器如何选择,分区分配的方法,分布式消费的实现机制,拉取消息的原理,offset提交的原理。

Kafka一条消息发送和消费的流程(非集群)

image.png

简单入门

我们这里使用Kafka内置的客户端API开发kafka应用程序。因为我们是Java程序员,所以这里我们使用Maven,使用较新的版本

  <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>3.3.1</version>
  </dependency>

生产者

先创建一个主题,推荐在消息发送时创建对应的主题。当然就算没有创建主题,Kafka也能自动创建。

auto.create.topics.enable

是否允许自动创建主题。如果设为true,那么produce(生产者往主题写消息),consume(消费者从主题读消息)或者fetch metadata(任意客户端向主题发送元数据请求时)一个不存在的主题时,就会自动创建。缺省为true。

num.partitions

每个新建主题的分区个数(分区个数只能增加,不能减少 )。这个参数默认值是1(最新版本)

必选属性

创建生产者对象时有三个属性必须指定。

bootstrap.servers

该属性指定broker的地址清单,地址的格式为host:port。

清单里不需要包含所有的broker地址,生产者会从给定的broker里查询其他broker的信息。不过最少提供2个broker的信息(用逗号分隔,比如:127.0.0.1:9092,192.168.0.13:9092),一旦其中一个宕机,生产者仍能连接到集群上。

key.serializer

生产者接口允许使用参数化类型,可以把Java对象作为键和值传broker,但是broker希望收到的消息的键和值都是字节数组,所以,必须提供将对象序列化成字节数组的序列化器。

key.serializer必须设置为实现org.apache.kafka.common.serialization.Serializer的接口类

Kafka的客户端默认提供了ByteArraySerializer,IntegerSerializer,StringSerializer,也可以实现自定义的序列化器。

value.serializer

同 key.serializer。

三种发送方式

我们通过生成者的send方法进行发送。send方法会返回一个包含RecordMetadata的Future对象。RecordMetadata里包含了目标主题,分区信息和消息的偏移量。

发送并忘记

忽略send方法的返回值,不做任何处理。大多数情况下,消息会正常到达,而且生产者会自动重试,但有时会丢失消息。

package com.msb.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

/**
 * 类说明:kafak生产者
 */
public class HelloKafkaProducer {

    public static void main(String[] args) {
        // 设置属性
        Properties properties = new Properties();
        // 指定连接的kafka服务器的地址
        properties.put("bootstrap.servers","127.0.0.1:9092");
        // 设置String的序列化
        properties.put("key.serializer", StringSerializer.class);
        properties.put("value.serializer", StringSerializer.class);

        // 构建kafka生产者对象
        KafkaProducer<String,String> producer  = new KafkaProducer<String, String>(properties);
        try {
            ProducerRecord<String,String> record;
            try {
                // 构建消息
                record = new ProducerRecord<String,String>("msb", "teacher","lijin");
                // 发送消息
                producer.send(record);
                System.out.println("message is sent.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } finally {
            // 释放连接
            producer.close();
        }
    }


}

同步发送

获得send方法返回的Future对象,在合适的时候调用Future的get方法。参见代码。

package com.msb.producer;

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.Future;

/**
 * 类说明:发送消息--同步模式
 */
public class SynProducer {

    public static void main(String[] args) {
        // 设置属性
        Properties properties = new Properties();
        // 指定连接的kafka服务器的地址
        properties.put("bootstrap.servers","127.0.0.1:9092");
        // 设置String的序列化
        properties.put("key.serializer", StringSerializer.class);
        properties.put("value.serializer", StringSerializer.class);

        // 构建kafka生产者对象
        KafkaProducer<String,String> producer  = new KafkaProducer<String, String>(properties);
        try {
            ProducerRecord<String,String> record;
            try {
                // 构建消息
                record = new ProducerRecord<String,String>("msb", "teacher2333","lijin");
                // 发送消息
                Future<RecordMetadata> future =producer.send(record);
                RecordMetadata recordMetadata = future.get();
                if(null!=recordMetadata){
                    System.out.println("offset:"+recordMetadata.offset()+","
                            +"partition:"+recordMetadata.partition());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } finally {
            // 释放连接
            producer.close();
        }
    }




}

异步发送

实现接口org.apache.kafka.clients.producer.Callback,然后将实现类的实例作为参数传递给send方法。

package com.msb.producer;

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.Future;

/**
 * 类说明:发送消息--异步模式
 */
public class AsynProducer {

    public static void main(String[] args) {
        // 设置属性
        Properties properties = new Properties();
        // 指定连接的kafka服务器的地址
        properties.put("bootstrap.servers","127.0.0.1:9092");
        // 设置String的序列化
        properties.put("key.serializer", StringSerializer.class);
        properties.put("value.serializer", StringSerializer.class);

        // 构建kafka生产者对象
        KafkaProducer<String,String> producer  = new KafkaProducer<String, String>(properties);

        try {
            ProducerRecord<String,String> record;
            try {
                // 构建消息
                record = new ProducerRecord<String,String>("msb", "teacher","lijin");
                // 发送消息
                producer.send(record, new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                        if (e == null){
                            // 没有异常,输出信息到控制台
                            System.out.println("offset:"+recordMetadata.offset()+"," +"partition:"+recordMetadata.partition());
                        } else {
                            // 出现异常打印
                            e.printStackTrace();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        } finally {
            // 释放连接
            producer.close();
        }
    }




}

消费者

消费者的含义,同一般消息中间件中消费者的概念。在高并发的情况下,生产者产生消息的速度是远大于消费者消费的速度,单个消费者很可能会负担不起,此时有必要对消费者进行横向伸缩,于是我们可以使用多个消费者从同一个主题读取消息,对消息进行分流。

必选属性

创建消费者对象时一般有四个属性必须指定。

bootstrap.servers、value.Deserializer key.Deserializer 含义同生产者

可选属性

group.id 并非完全必需,它指定了消费者属于哪一个群组,但是创建不属于任何一个群组的消费者并没有问题。不过绝大部分情况我们都会使用群组消费。

消费者群组

Kafka里消费者从属于消费者群组,一个群组里的消费者订阅的都是同一个主题,每个消费者接收主题一部分分区的消息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6u5kl5Z-1686625500438)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\msohtmlclip1\01\clip_image002.jpg)]image.png

如上图,主题T有4个分区,群组中只有一个消费者,则该消费者将收到主题T1全部4个分区的消息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VfAoDiZ9-1686625500439)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\msohtmlclip1\01\clip_image004.jpg)]image.png

如上图,在群组中增加一个消费者2,那么每个消费者将分别从两个分区接收消息,上图中就表现为消费者1接收分区1和分区3的消息,消费者2接收分区2和分区4的消息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykOsMslo-1686625500440)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\msohtmlclip1\01\clip_image006.jpg)]image.png

如上图,在群组中有4个消费者,那么每个消费者将分别从1个分区接收消息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxKxlI7z-1686625500441)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\msohtmlclip1\01\clip_image008.jpg)]image.png

但是,当我们增加更多的消费者,超过了主题的分区数量,就会有一部分的消费者被闲置,不会接收到任何消息。

往消费者群组里增加消费者是进行横向伸缩能力的主要方式。所以我们有必要为主题设定合适规模的分区,在负载均衡的时候可以加入更多的消费者。但是要记住,一个群组里消费者数量超过了主题的分区数量,多出来的消费者是没有用处的。

序列化

创建生产者对象必须指定序列化器,默认的序列化器并不能满足我们所有的场景。我们完全可以自定义序列化器。只要实现org.apache.kafka.common.serialization.Serializer接口即可。

image.png

自定义序列化

代码见:

image.png

代码中使用到了自定义序列化。

image.png

id的长度4个字节,字符串的长度描述4个字节, 字符串本身的长度nameSize个字节

image.png

自定义序列化容易导致程序的脆弱性。举例,在我们上面的实现里,我们有多种类型的消费者,每个消费者对实体字段都有各自的需求,比如,有的将字段变更为long型,有的会增加字段,这样会出现新旧消息的兼容性问题。特别是在系统升级的时候,经常会出现一部分系统升级,其余系统被迫跟着升级的情况。

解决这个问题,可以考虑使用自带格式描述以及语言无关的序列化框架。比如Protobuf,Kafka官方推荐的Apache Avro

分区

因为在Kafka中一个topic可以有多个partition,所以当一个生产发送消息,这条消息应该发送到哪个partition,这个过程就叫做分区。

当然,我们在新建消息的时候,我们可以指定partition,只要指定partition,那么分区器的策略则失效。

image.png

系统分区器

在我们的代码中可以看到,生产者参数中是可以选择分区器的。

image.png

image.png

DefaultPartitioner 默认分区策略

全路径类名:org.apache.kafka.clients.producer.internals.DefaultPartitioner

  • 如果消息中指定了分区,则使用它
  • 如果未指定分区但存在key,则根据序列化key使用murmur2哈希算法对分区数取模。
  • 如果不存在分区或key,则会使用粘性分区策略

采用默认分区的方式,键的主要用途有两个:

一,用来决定消息被写往主题的哪个分区,拥有相同键的消息将被写往同一个分区。

二,还可以作为消息的附加消息。

RoundRobinPartitioner 分区策略

全路径类名:org.apache.kafka.clients.producer.internals.RoundRobinPartitioner

  • 如果消息中指定了分区,则使用它
  • 将消息平均的分配到每个分区中。

即key为null,那么这个时候一般也会采用RoundRobinPartitioner

UniformStickyPartitioner 纯粹的粘性分区策略

全路径类名:org.apache.kafka.clients.producer.internals.UniformStickyPartitioner

他跟DefaultPartitioner 分区策略的唯一区别就是。

DefaultPartitionerd 如果有key的话,那么它是按照key来决定分区的,这个时候并不会使用粘性分区
UniformStickyPartitioner 是不管你有没有key, 统一都用粘性分区来分配

另外关于粘性分区策略

从客户端最新的版本上来看(3.3.1),有两个序列化器已经进入 弃用阶段。

这个客户端在3.1.0都还不是这样。关于粘性分区策略

image.png

如果感兴趣可以看下这篇文章

https://bbs.huaweicloud.com/blogs/348729?utm_source=oschina&utm_medium=bbs-ex&utm_campaign=other&utm_content=content

自定义分区器

我们完全可以去实现Partitioner接口,去实现有一个自定义的分区器

package com.msb.selfpartition;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.utils.Utils;

import java.util.List;
import java.util.Map;

/**
 * 类说明:自定义分区器,以value值进行分区
 */
public class SelfPartitioner implements Partitioner {
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);
        int num = partitionInfos.size();
        int parId = Utils.toPositive(Utils.murmur2(valueBytes)) % num;//来自DefaultPartitioner的处理
        return parId;
    }

    public void close() {
        //do nothing
    }

    public void configure(Map<String, ?> configs) {
        //do nothing
    }

}

image.png

生产缓冲机制

客户端发送消息给kafka服务器的时候、消息会先写入一个内存缓冲中,然后直到多条消息组成了一个Batch,才会一次网络通信把Batch发送过去。主要有以下参数:

buffer.memory

设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息。如果数据产生速度大于向broker发送的速度,导致生产者空间不足,producer会阻塞或者抛出异常。缺省33554432 (32M)

buffer.memory: 所有缓存消息的总体大小超过这个数值后,就会触发把消息发往服务器。此时会忽略batch.size和linger.ms的限制。
buffer.memory的默认数值是32 MB,对于单个 Producer 来说,可以保证足够的性能。 需要注意的是,如果您在同一个JVM中启动多个 Producer,那么每个 Producer 都有可能占用 32 MB缓存空间,此时便有可能触发 OOM。

batch.size

当多个消息被发送同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。当批次内存被填满后,批次里的所有消息会被发送出去。但是生产者不一定都会等到批次被填满才发送,半满甚至只包含一个消息的批次也有可能被发送(linger.ms控制)。缺省16384(16k) ,如果一条消息超过了批次的大小,会写不进去。

linger.ms

指定了生产者在发送批次前等待更多消息加入批次的时间。它和batch.size以先到者为先。也就是说,一旦我们获得消息的数量够batch.size的数量了,他将会立即发送而不顾这项设置,然而如果我们获得消息字节数比batch.size设置要小的多,我们需要“linger”特定的时间以获取更多的消息。这个设置默认为0,即没有延迟。设定linger.ms=5,例如,将会减少请求数目,但是同时会增加5ms的延迟,但也会提升消息的吞吐量。

为何要设计缓冲机制

1、减少IO的开销(单个 ->批次)但是这种情况基本上也只是linger.ms配置>0的情况下才会有,因为默认inger.ms=0的,所以基本上有消息进来了就发送了,跟单条发送是差不多!!

2、减少Kafka中Java客户端的GC。

比如缓冲池大小是32MB。然后把32MB划分为N多个内存块,比如说一个内存块是16KB(batch.size),这样的话这个缓冲池里就会有很多的内存块。

你需要创建一个新的Batch,就从缓冲池里取一个16KB的内存块就可以了,然后这个Batch就不断的写入消息

下次别人再要构建一个Batch的时候,再次使用缓冲池里的内存块就好了。这样就可以利用有限的内存,对他不停的反复重复的利用。因为如果你的Batch使用完了以后是把内存块还回到缓冲池中去,那么就不涉及到垃圾回收了。

image.png

消费者偏移量提交

一般情况下,我们调用poll方法的时候,broker返回的是生产者写入Kafka同时kafka的消费者提交偏移量,这样可以确保消费者消息消费不丢失也不重复,所以一般情况下Kafka提供的原生的消费者是安全的,但是事情会这么完美吗?

自动提交

最简单的提交方式是让消费者自动提交偏移量。 如果enable.auto.commit被设为 true,消费者会自动把从poll()方法接收到的最大偏移量提交上去。提交时间间隔由auto.commit.interval.ms控制,默认值是5s。

自动提交是在轮询里进行的,消费者每次在进行轮询时会检査是否该提交偏移量了,如果是,那么就会提交从上一次轮询返回的偏移量。

不过,在使用这种简便的方式之前,需要知道它将会带来怎样的结果。

假设我们仍然使用默认的5s提交时间间隔, 在最近一次提交之后的3s发生了再均衡,再均衡之后,消费者从最后一次提交的偏移量位置开始读取消息。这个时候偏移量已经落后了3s,所以在这3s内到达的消息会被重复处理。可以通过修改提交时间间隔来更频繁地提交偏移量, 减小可能出现重复消息的时间窗, 不过这种情况是无法完全避免的。

在使用自动提交时,每次调用轮询方法都会把上一次调用返回的最大偏移量提交上去,它并不知道具体哪些消息已经被处理了,所以在再次调用之前最好确保所有当前调用返回的消息都已经处理完毕(enable.auto.comnit被设为 true时,在调用 close()方法之前也会进行自动提交)。一般情况下不会有什么问题,不过在处理异常或提前退出轮询时要格外小心。

消费者的配置参数

auto.offset.reset

earliest
当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
latest
当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据

只要group.Id不变,不管auto.offset.reset 设置成什么值,都从上一次的消费结束的地方开始消费。

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

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

相关文章

JVM笔记(一)

走进JVM JVM相对于Java应用层的学习难度更大&#xff0c;**开篇推荐掌握的预备知识&#xff1a;**C/C(关键)、微机原理与接口技术、计算机组成原理、操作系统、数据结构与算法、编译原理&#xff08;不推荐刚学完JavaSE的同学学习&#xff09;&#xff0c;如果没有掌握推荐的一…

javac 无效的目标发行版: xx

一、检查系统JDK版本 java --version 如果不符合&#xff0c;重新配置系统环境。 二、检查IDEA设置 1、项目结构->项目->SDK和语言级别 2、 项目结构->模块->设置每一个模块的语言级别。 3、java编译器&#xff0c;模块预言级别。 三、检查pom文件 <!--Licens…

qt学习——基本使用、对象树、按钮、信号与槽

初识qt **qt****qt命名规范以及相关快捷键的使用****QPushButton****对象树****点击按钮关闭窗口****信号和槽****标准的信号和槽****自定义信号和槽****带参数的自定义信号和槽传参以及函数的二义性问题** qt qt命名规范以及相关快捷键的使用 优点:Qt相对于C&#xff0c;有一…

华为OD机试真题 JavaScript 实现【狼羊过河】【2022Q4 100分】,附详细解题思路

一、题目描述 一农夫带着m只羊&#xff0c;n只狼过河&#xff0c;农夫有一条可载x只狼/羊的船&#xff1b;农夫在时或者羊的数量大于狼时&#xff0c;狼不会攻击羊&#xff1b; 农夫在不损失羊的情况下&#xff0c;运输几次可以完成运输&#xff1f; 返程不计入次数。 二、…

Vue中如何进行表单联动与级联选择?

Vue中如何进行表单联动与级联选择&#xff1f; 表单联动和级联选择是Vue.js中常见的功能。表单联动是指在一个表单中&#xff0c;当某一个输入框的值发生变化时&#xff0c;其他输入框的值也会随之改变。级联选择是指在一个选择框中&#xff0c;当选择一个选项时&#xff0c;另…

领域建模之数据模型设计方法论 | 京东云技术团队

本文通过实际业务需求场景建模案例&#xff0c;为读者提供一种业务模型向数据模型设计的方法论&#xff0c;用于指导实际开发中如何进行业务模型向数据模型转化抽象&#xff0c;并对设计的数据模型可用性、扩展性提供了建议性思考。通过文章&#xff0c;读者可以收获到业务模型…

linux 漏洞升级、初次安装 mysql

文章目录 适用安装遇到问题 适用 1、为解决Mysql漏洞而需要离线升级mysql版本&#xff1b; 2、初次安装Mysql也可以参考本文&#xff1b; 安装 提示&#xff1a;首次安装可跳过第二步、第三步 一、查看CentOS7系统自带mariadb # 查看系统自带的Mariadb [rootiZ2ze3hm3gyjy…

最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念&#xff0c;体现了看待线程同步的不同角度&#xff0c;在Java和数据库中都有此概念对应的实际应用。 1.乐观锁 顾名思义&#xff0c;就是很乐观&#xff0c;每次去拿数据的时候都认为别人不会修改&#xff0c;所以不会上…

英语中-后置定语

一般说来&#xff0c;形容词放在所修饰名词的前面。单个的现在分词、过去分词以及动名词作定语&#xff0c;都是放在所修饰名词&#xff08;或代词&#xff09;的前面。这些称为前置定语。 例如&#xff1a; a red flower/ an interesting story&#xff0c;这里red, interes…

SaaS人力资源管理系统的Bug

SaaS人力资源管理系统的Bug Bug1【18】 这里我是直接把代码复制过来的&#xff0c;然后就有一个空白 这是因为它的代码有问题&#xff0c;原本的代码如下所示 <el-table-column fixed type"index" label"序号" width"50"></el-table-co…

ssm+java在线考试批改阅卷系统

本次学校在线考试系统的实现过程&#xff0c;它的开发使用B/S结构即浏览器和服务器结构框架&#xff0c;采用SSM框架技术&#xff0c;数据库使用了mysql数据库&#xff0c;页面设计采用了MVC框架&#xff0c;后端采用了SSM框架技术scrip等其他一些脚本语言&#xff0c;使用到在…

hash算法详解

散列算法&#xff08;Hash Algorithm&#xff09;&#xff0c;又称哈希算法&#xff0c;杂凑算法&#xff0c;是一种从任意文件中创造小的数字「指纹」的方法。与指纹一样&#xff0c;散列算法就是一种以较短的信息来保证文件唯一性的标志&#xff0c;这种标志与文件的每一个字…

Java中的动态链接VS操作系统动态链接

在操作系统OS中为了优化内存的使用会采用一种动态链接方式&#xff0c;一个文件想要在操作系统中运行必须经过编译、汇编译、链接、装载等步骤。可以参考Java程序是怎么跑起来的。本篇主要讲解Java栈帧中动态链接部分与操作系统的的动态链接的区别与联系 操纵系统为什么需要动态…

2023年欧洲科学院院士中的华人学者简介

近日&#xff0c;2023年欧洲科学院新当选欧洲科学院院士名单出炉&#xff0c;由于入选学者接受邀请及注册的时间进度不同&#xff0c;入选名单陆续公布&#xff08;截止目前已更新135位&#xff09;。本文知识人网小编仅介绍入选名单中华人学者的情况。 欧洲科学院(Academia Eu…

优化--分类树,我从2s优化到0.1s

1.前言 分类树查询功能&#xff0c;在各个业务系统中可以说随处可见&#xff0c;特别是在电商系统中。 但就是这样一个简单的分类树查询功能&#xff0c;我们却优化了5次。 到底是怎么回事呢&#xff1f; 2.背景 我们的网站使用了SpringBoot推荐的模板引擎&#xff1a;Thym…

TiDB x Catalyst丨秒级洞悉数据价值,TiDB 帮助“客户成功 SaaS 厂商”提升用户体验

导读 Catalyst 是一家总部位于纽约的 SaaS 创业公司&#xff0c;它提供了一个直观且灵活的客户成功平台&#xff08;Custom Success Platform&#xff09;&#xff0c;可帮助客户成功团队汇聚客户数据&#xff0c;洞悉客户健康状况&#xff0c;推动客户留存和业务增长。目前 C…

「网络编程」第一讲:初识网络_网络基础1

「前言」文章是关于网络编程方面的&#xff0c;今天内容大致是网络基础&#xff0c;讲解下面开始&#xff01; 「归属专栏」网络编程 「笔者」枫叶先生(fy) 「座右铭」前行路上修真我 「枫叶先生有点文青病」 「每篇一句」 青山不改&#xff0c;绿水长流 ——白居易 目录 一、…

chatgpt赋能python:Python怎么建服务器?

Python怎么建服务器&#xff1f; 作为一名具有10年Python编程经验的工程师&#xff0c;我深入研究了Python的一些高级特性&#xff0c;其中包括Python如何建立服务器的方法。Python是一个高级的编程语言&#xff0c;可以轻松创建服务器应用程序&#xff0c;并为您的网站提供高…

redis中的intset集合源码阅读探究;基于7.0+版本

1丶什么是intset Redis 中的 intset&#xff08;整数集合&#xff09;是一种高效的数据结构选择。Intset 具有紧凑的内存布局和快速的插入、删除和查找操作&#xff0c;适用于存储大量整数&#xff0c;并且能够节省内存空间。 2丶先说优点 内存布局&#xff1a;Intset 的内存…

vue-element-admin项目-Host key verification failed.-已解决

在网上下载的element-admin项目&#xff0c;install的时候一直报错Host key verification failed&#xff0c; 实测好用&#xff01;&#xff01;&#xff01;已解决 报错问题&#xff1a;上面写到主机密钥验证失败&#xff0c;无法从远程仓库拉取。说明我们需要生成一个新的密…