当前位置: 首页 > news >正文

Kafka原理剖析之「Topic创建」

一、前言

Kafka提供了高性能的读写,而这些读写操作均是操作在Topic上的,Topic的创建就尤为关键,其中涉及分区分配策略、状态流转等,而Topic的新建语句非常简单

bash kafka-topics.sh \
--bootstrap-server localhost:9092 \ // 需要写入endpoints
--create --topic topicA				// 要创建的topic名称
--partitions 10 					// 当前要创建的topic分区数
--replication-factor 2				// 副本因子,即每个TP创建多少个副本

因此Topic的创建可能并不像表明上操作的那么简单,这节我们就阐述一下Topic新建的细节

以下论述基于Kafka 2.8.2版本

二、整体流程

Topic新建分2部分,分别是

  1. 用户调用对应的API,然后由Controller指定分区分配策略,并将其持久化至Zookeeper中
  2. Controller负责监听Zookeeper的回调函数拿到元数据变更后,触发状态机并真正执行副本分配

用户发起一个Topic新建的请求,Controller收到请求后,开始制定分区分配方案,继而将分配方案持久化到Zookeeper中,然后就向用户返回结果

而在Controller中专门监听Zookeeper节点变化的线程(当然这个线程与创建Topic的线程是异步的),当发现有变更后,将会异步触发状态机进行状态流转,后续会将对应的Broker设置为Leader或Follower

三、Topic分区分配方案

在模块一中,主要的流程是3部分:

  1. 用户向Controller发起新增Topic请求
  2. Controller收到请求后,开始制定该Topic的分区分配策略
  3. Controller将制定好的策略持久化至Zookeeper中

而上述描述中,流程1、3都是相对好理解的,我们着重要说的是流程2,即分区分配策略。Kafka分区制定方案核心逻辑放在 scala/kafka/admin/AdminUtils.scala 中,分为无机架、有机架两种,我们核心看一下无机架的策略

无机架策略中,又分为Leader Replica及Follow Replica两种

3.1、Leader Partition

而关于Leader及Follower的分配策略统一在方法kafka.admin.AdminUtils#assignReplicasToBrokersRackUnaware中,此方法只有20多行,我们简单来看一下

private def assignReplicasToBrokersRackUnaware(nPartitions: Int,  // 目标topic的分区总数replicationFactor: Int,	// topic副本因子brokerList: Seq[Int],	// broker列表fixedStartIndex: Int,	// 默认情况传-1startPartitionId: Int  /* 默认情况传-1 */): Map[Int, Seq[Int]] = {val ret = mutable.Map[Int, Seq[Int]]()val brokerArray = brokerList.toArray// leader针对broker列表的开始index,默认会随机选取val startIndex = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerArray.length)// 默认为0,从0开始var currentPartitionId = math.max(0, startPartitionId)// 这个值主要是为分配Follower Partition而用var nextReplicaShift = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerArray.length)// 这里开始对partition进行循环遍历for (_ <- 0 until nPartitions) {// 这个判断逻辑,影响follower partitionif (currentPartitionId > 0 && (currentPartitionId % brokerArray.length == 0))nextReplicaShift += 1// 当前partition的第一个replica,也就是leader// 由于startIndex是随机生成的,因此firstReplicaIndex也是从broker list中随机取一个val firstReplicaIndex = (currentPartitionId + startIndex) % brokerArray.length// 存储了当前partition的所有replica的数组val replicaBuffer = mutable.ArrayBuffer(brokerArray(firstReplicaIndex))for (j <- 0 until replicationFactor - 1)replicaBuffer += brokerArray(replicaIndex(firstReplicaIndex, nextReplicaShift, j, brokerArray.length))ret.put(currentPartitionId, replicaBuffer)currentPartitionId += 1}ret
}

由此可见,Topic Leader Replica的分配策略是相对简单的,我们再简单概括一下它的流程

  1. 从Broker List中随机选取一个Broker,作为 Partition 0 的 Leader
  2. 之后开始遍历Broker List,依次创建Partition 1、Partition 2、Partition 3....
  3. 如果遍历到了Broker List末尾,那么重定向到0,继续向后遍历

假定我们有5个Broker,编号从1000开始,分别是1000、1001、1002、1003、1004,假定partition 0随机选举的broker是1000,那么最终的分配策略将会是如下:

Broker

1000

1001

1002

1003

1004

Leader Partition

0

1

2

3

4

5

6

7

8

9

而假定partition 0随机选举的broker是1002,那么最终的分配策略将会是如下:

Broker

1000

1001

1002

1003

1004

Leader Partition

3

4

0

1

2

8

9

5

6

7

这样做的目的是将Partition尽可能地打乱,将Partition Leader分配到不同的Broker上,避免数据热点

然而这个方案也并不是完美的,它只是会将当前创建的Topic Partition Leader打散,并没有考虑其他Topic Partition的分配情况,假定我们现在创建了5个Topic,均是单分区的,而正好它们都落在Broker 1000上,下一次我们创建新Topic的时,它的Partition 0依旧可能落在Broker 1000上,造成数据热点。不过因为是随机创建,因此当Topic足够多的情况时,还是能保证相对离散

3.2、Follower Partition

Leader Replica已经确定下来,接下来就是要制定Follower的分配方案,Follower的分配方案至少要满足以下2点要求

  • Follower要随机打散在不同的Broker上,主要是做高可用保证,当Leader Broker不可用时,Follower要能顶上
  • Follower的分配还不能太随机,因为如果真的全部随机分配的话,可能出现某个Broker比其他Broker的replica要多,而这个是可以避免的

Follower Replica的分配逻辑除了上述说的kafka.admin.AdminUtils#assignReplicasToBrokersRackUnaware方法外,很重要的一个方法是kafka.admin.AdminUtils#replicaIndex

private def replicaIndex(firstReplicaIndex: Int, 	// 第一个replica的index,也就是leader indexsecondReplicaShift: Int, 	// 随机shift,范围是[0, brokerList.length),每隔brokerList.length,将+1replicaIndex: Int, 	// 当前follower副本编号,从0开始nBrokers: Int): Int = {  // broker数量val shift = 1 + (secondReplicaShift + replicaIndex) % (nBrokers - 1)(firstReplicaIndex + shift) % nBrokers
}

其实这个方法只有2行,不过这2行代码相当晦涩,要理解它不太容易,而且在2.8.2版本中没有对其的注释,我特意翻看了当前社区的最新版本3.9.0-SNAPSHOT,依旧没有针对这个方法的注释。不过我们还是需要花点精力去理解它的

第一行

val shift = 1 + (secondReplicaShift + replicaIndex) % (nBrokers - 1)

这行代码的作用是生成一个随机值shift,因此shift的范围是 0 <= shift < nBrokers,而随着replicaIndex的增加,shift也会相应增加,当然这样做的目的是为第二行代码做铺垫

当然shift的值,只会与secondReplicaShift、replicaIndex相关,与partition无关

第二行

(firstReplicaIndex + shift) % nBrokers

这样代码就保证了生成的follower index不会与Leader index重复,并且所有的follower index是向前递增的

总结一下分配的规则:

  1. 随机从Broker list中选择一个作为第一个follower的起始位置(由变量secondReplicaShift控制)
  2. 后续的follower均基于步骤1的起始位置,依次向后+1
  3. follower的位置确保不会与Leader冲突,如果冲突则向后顺延一位(由 (firstReplicaIndex + shift) % nBrokers 进行控制)
  4. 并非当前Topic的所有的partition均采用同一步调,一旦(PartitionNum%BrokerNum == 0),secondReplicaShift将会+1,导致第一个follower的起始位置+1,这样就更加离散

我们看一个具体case:

Broker

1000

1001

1002

1003

1004

Leader

0

1

2

3

4

5

6

7

8

9

Follower 1

1

2

3

4

0

9

5

6

7

8

Follower 2

4

0

1

2

3

8

9

5

6

7

  • Partition 1:Leader在1001上,而2个Follower分别在1000、1002上。很明显,Follower是从1000开始往后遍历寻找的,因此2个Follower的分布本来应该是1000、1001,但1001正好是Leader,因此往后顺移,最终Follower的分布也就是【1000、1002】
    • 此处注意:为什么“Follower是从1000开始往后遍历”? 这个就与kafka.admin.AdminUtils#replicaIndex方法中的shift变量有关,而shift则是由随机变量secondReplicaShift而定的,因此“1000开始往后遍历”是本次随机运行后的一个结果,如果再跑一次程序,可能结果就不一致了
  • Partition 3:再看分区3,Leader在1003上,Follower是从1002开始的,因此Follower的分布也就是【1002、1004】
  • Partition 7:因为从partition 5开始,超过了broker的总数,因此变量secondReplicaShift++,导致Follower的起始index也+1,因此Follower的分布是【1003、1004】

为什么要费尽九牛二虎之力,做这么复杂的方案设定呢?直接将Leader Broker后面的N个Broker作为Follower不可以吗?其实自然是可以的,不过可能带来一些问题,比如如果Leader宕机后,这些Leader Partition都会飘到某1个或某几个Broker上,这样可能带来一些热点隐患,导致存活的Broker不能均摊这些流量

3.3、手动制定策略

当然上述是Kafka帮助我们自动制定分区分配方案,另外我们可以手动制定策略:

bash kafka-topics.sh \
--bootstrap-server localhost:9092 \
--create --topic topicA \
--replica-assignment 1000,1000,1000,1000,1000

按照上述的命令创建Topic,我们会新建一个名称为“topicA”的主题,它有5个分区,全部都创建在ID为1000的Broker上

另外Kafka还支持机架(rack)优先的分区分配方案,即尽量将某个partition的replica均匀地打散至N个rack中,这样确保某个rack不可用后,不影响这个partition整体对外的服务能力。本文不再对这种case进行展开

四、状态机

在分区分配方案制定完毕后,Controller便将此方案进行编码,转换为二进制的byte[],进而持久化到ZooKeeper的路径为/topics/topicXXX(其中topicXXX就是topic名称)的path内,而后便向用户返回创建成功的提示;然而真正创建Topic的逻辑并没有结束,Controller会异步执行后续创建Topic的操作,源码中逻辑写的相对比较绕,不过不外乎做了以下两件事儿:

  1. 更新元数据并通知给所有Brokers
  2. 向各个Broker传播ISR,并对应执行Make Leader、Make Follower操作

而实现上述操作则是通过两个状态机:

  • PartitionStateMachine.scala 分区状态机
  • ReplicaStateMachine.scala 副本状态机

Controll收到ZK异步通知的入口为 kafka.controller.KafkaController#processTopicChange

4.1、分区状态机

即一个partition的状态,对应的申明类为kafka.controller.PartitionState,共有4种状态:

  • NewPartition 新建状态,其实只会在Controll中停留很短的时间,继而转换为OnlinePartition
  • OnlinePartition 在线状态,只有处于在线状态的partition才能对外提供服务
  • OfflinePartition 下线状态,比如Topic删除操作
  • NonExistentPartition 初始化状态,如果新建Topic,partition默认则为此状态

转换关系如下

本文只讨论新建Topic时,状态转换的过程,因此只涉及

  • NonExistentPartition -> NewPartition
  • NewPartition -> OnlinePartition

4.2、副本状态机

所谓副本状态机,对应的申明类为kafka.controller.ReplicaState,共有7种状态:NewReplica、OnlineReplica、OfflineReplica、ReplicaDeletionStarted、ReplicaDeletionSuccessful、ReplicaDeletionIneligible、NonExistentReplica。在Topic新建的流程中,我们只会涉及其中的3种:NewReplica、OnlineReplica、NonExistentReplica,且副本状态机在新建流程中发挥的空间有限,不是本文的重点,读者对其有个大致概念即可

4.3、状态流转

首先要确认一点,Kafka的Controller是单线程的,所有的事件均是串行执行,以下所有的操作也均是串行执行

在真正执行状态流转前,需要执行2个前置步骤

  • 生产Topic ID。为新建的Topic生产唯一的TopicID,具体实现方法位置在kafka.zk.KafkaZkClient#setTopicIds内,其实就是简单调用org.apache.kafka.common.Uuid#randomUuid来生成一个随机串
  • 读取分区分配策略。接着从zk(存储路径为/brokers/topics/topicName)中读取这个Topic的分区分配策略,然后将分区分配策略放进缓存中,缓存的位置为kafka.controller.ControllerContext#partitionAssignments

上述两个步骤其实没啥好说的,只是为状态流转做一些前置铺垫。接下来就要进入主方法的逻辑中了,即kafka.controller.KafkaController#onNewPartitionCreation,可简单看一下此方法,主要执行4部分内容

    1. partition状态机将状态设置为NewPartition
    2. replica状态机降状态置为NewReplica
    3. partition状态机将状态设置为OnlinePartition
    4. replica状态机降状态置为OnlineReplica
// kafka.controller.KafkaController#onNewPartitionCreation
private def onNewPartitionCreation(newPartitions: Set[TopicPartition]): Unit = {info(s"New partition creation callback for ${newPartitions.mkString(",")}")partitionStateMachine.handleStateChanges(newPartitions.toSeq, NewPartition)replicaStateMachine.handleStateChanges(controllerContext.replicasForPartition(newPartitions).toSeq, NewReplica)partitionStateMachine.handleStateChanges(newPartitions.toSeq, OnlinePartition, Some(OfflinePartitionLeaderElectionStrategy(false)))replicaStateMachine.handleStateChanges(controllerContext.replicasForPartition(newPartitions).toSeq, OnlineReplica)
}

4.3.1、Partition状态机NewPartition

partition状态机将状态设置为NewPartition。这一步就是维护kafka.controller.ControllerContext#partitionStates内存变量,将对应partition的状态设置为NewPartition,其他什么都不做

4.3.2、Replica状态机NewReplica

replica状态机降状态置为NewReplica。这一步是维护kafka.controller.ControllerContext#replicaStates内存变量,将replica状态设置为NewReplica

4.3.3、Partition状态机OnlinePartition

这一步也是整个状态机流转中的核心部分,共分为以下5大步:

  1. 初始化Leader、ISR等信息,并将这些信息暂存至zk中
    1. 创建topic-partition在zk中的路径,path为/brokers/topics/topicName/partitions
    2. 为每个partition创建路径,path为/brokers/topics/topicName/partitions/xxx,例如
      1. /brokers/topics/topicName/partitions/0
      2. /brokers/topics/topicName/partitions/1
      3. /brokers/topics/topicName/partitions/2
    1. 将Leader及ISR的信息持久化下来,path为/brokers/topics/topicName/partitions/0/state
  1. 而后将Leader、ISR等已经持久化到zk的信息放入缓存kafka.controller.ControllerContext#partitionLeadershipInfo
  2. 因为Leader、ISR这些元数据发生了变化,因此将这些信息记录下来,放在内存结构kafka.controller.AbstractControllerBrokerRequestBatch#leaderAndIsrRequestMap中,表明这些信息是需要同步给对应的Broker的
  3. 维护kafka.controller.ControllerContext#partitionStates内存变量,将状态设置为OnlinePartition
  4. 调用接口ApiKeys.LEADER_AND_ISR,向对应的Broker发送数据,当Broker接收到这个请求后,便会执行MakeLeader/MakeFollower相关操作

4.3.4、Replica状态机OnlineReplica

replica状态机降状态置为OnlineReplica。维护kafka.controller.ControllerContext#replicaStates内存变量,将状态设置为OnlineReplica

至此,一个 Kafka Topic 才算是真正被创建出来

相关文章:

Kafka原理剖析之「Topic创建」

一、前言 Kafka提供了高性能的读写&#xff0c;而这些读写操作均是操作在Topic上的&#xff0c;Topic的创建就尤为关键&#xff0c;其中涉及分区分配策略、状态流转等&#xff0c;而Topic的新建语句非常简单 bash kafka-topics.sh \ --bootstrap-server localhost:9092 \ // …...

Java 高级学习路线概要~

前言&#xff1a;恭喜你已经掌握了 Java 的基础知识&#xff01;现在&#xff0c;让我们踏上 Java 高级学习之旅&#xff0c;探索更强大的编程技巧和技术。学习前记得不要忘了巩固和加强基础的学习哦&#xff0c;高级学习也是建立在基础的学习之上。 1. 集合框架进阶 Map 接口…...

浏览器插件快速开启/关闭IDM接管下载

假设你已经为浏览器安装了IDM扩展&#xff0c;那么按下图的点击顺序&#xff0c;可以快速开启或关闭IDM的下载接管&#xff0c;而不必在IDM软件的设置->选项中&#xff0c;临时作调整。...

初识c++:入门基础

打字不易&#xff0c;留个赞再走吧~~ 目录 一.第一个c程序二.命名空间 namespace三.C输⼊&输出四.缺省参数 C兼容C语⾔绝⼤多数的语法&#xff0c;所以C语⾔实现的hello world依旧可以运⾏&#xff0c;C中需要把定义⽂件 代码后缀改为.cpp 一.第一个c程序 做好准备我们来写…...

Java Exception 异常相关总结

1.简介 在Java中&#xff0c;当代码运行有问题时会抛出异常&#xff0c;主要分为两类&#xff1a; 1.可以通过try...catch来捕获解决的&#xff0c;不影响后续执行的RuntimeException。 2.不可以通过代码解决的Exception。 为了提高代码的健壮性&#xff0c;我们会选择去捕…...

HighCharts图表自动化简介

什么是分析数据? 在任何应用程序中捕获并以图形或图表形式显示的分析数据是任何产品或系统的关键部分,因为它提供了对实时数据的洞察。 验证此类分析数据非常重要,因为不准确的数据可能会在报告中产生问题,并可能影响应用程序/系统的其他相关领域。 什么是HighChart? …...

使用LDAP登录GitLab

使用LDAP登录GitLab gitlab.rb 配置如下 gitlab_rails[ldap_enabled] true #gitlab_rails[prevent_ldap_sign_in] false###! **remember to close this block with EOS below** gitlab_rails[ldap_servers] YAML.load <<-EOSmain:label: LDAPhost: 172.16.10.180port:…...

【2024】前端学习笔记5-表单标签使用

表单是网页提供的一种交互式操作手段,主要用于采集用户输入的信息。 学习笔记 1.表单框架:form标签1.1.action属性:目标指向1.2.method属性:提交方式1.3.id属性:唯一标识1.4.placeholder属性:提示文字2.input标签2.1.text类型:基本文本输入2.2.password类型:密码输入2.…...

数据结构--二叉树(C语言实现,超详细!!!)

文章目录 二叉树的概念代码实现二叉树的定义创建一棵树并初始化组装二叉树前序遍历中序遍历后序遍历计算树的结点个数求二叉树第K层的结点个数求二叉树高度查找X所在的结点查找指定节点在不在完整代码 二叉树的概念 二叉树&#xff08;Binary Tree&#xff09;是数据结构中一种…...

【将字符串变为空的编辑距离】

题目描述 求由s串变成t串的编辑距离 在s串的开头/末尾添加一个字符&#xff0c;花费p 在s串的开头/末尾添加一个s串的子串&#xff0c;花费q 每次作都是基于当前的s串 s串初始为空 分析 等价于将一个字符串变为空串的过程 第一层按照长度遍历&#xff08;如果按照下标i,j遍…...

卡特兰数的推理

卡特兰数&#xff08;Catalan number&#xff09;&#xff0c;又称卡塔兰数、明安图数&#xff0c;是组合数学中一种常出现于各种计数问题中的数列。它以比利时数学家欧仁查理卡特兰的名字命名&#xff0c;但值得注意的是&#xff0c;这一数列的首次发现可以追溯到1730年&#…...

高精度治具加工的重要性和优势

在现代工业制造中&#xff0c;高精度治具加工扮演着举足轻重的角色。它不仅关乎产品制造的精度与质量&#xff0c;还直接影响到生产效率和成本控制。因此&#xff0c;时利和将深入探讨高精度治具加工的重要性和优势&#xff0c;对于提升工业制造水平具有重要意义。 高精度治具加…...

新版IDEA提示@Autowired不建议字段注入

随着项目的复杂度的增加&#xff0c;我们通常会在一个业务类中注入其他过多的业务类。从而使当前的业务层扩充成一个大而全的功能模块。那么就容易出现一下问题 字段注入会让依赖关系变得不那么明显&#xff0c;因为你无法通过构造函数看到所有的依赖项。使用构造函数时&#…...

adb的安装和使用 以及安装Frida 16.0.10+雷电模拟器

.NET兼职社区 .NET兼职社区 .NET兼职社区 1.下载adb Windows版本&#xff1a;https://dl.google.com/android/repository/platform-tools-latest-windows.zip 2.配置adb环境变量 按键windowsr打开运行&#xff0c;输入sysdm.cpl&#xff0c;回车。 高级》环境变量》系统变量》…...

解决移动端1px 边框优化的8个方法

前言 您是否注意到 1px 边框在移动设备上有时会显得比预期的要粗&#xff1f;这种不一致源于移动屏幕的像素密度不同。 在 Web 开发中&#xff0c;我们使用 CSS 来设置页面样式。但是&#xff0c;CSS 中的 1px 并不总是转换为设备上的物理 1px。这种差异就是我们的“1px 边框…...

频带宽度固定,如何突破数据速率的瓶颈?

目录 目录 引言 信道 频带宽度 信噪比 信噪比的重要性 影响信噪比的因素 码元 码元的特点&#xff1a; 码元与比特的关系&#xff1a; 码元的作用&#xff1a; 码元的类型&#xff1a; Question 类比解释&#xff1a; 技术解释&#xff1a; 引言 在现代通信系统中…...

Linux网络编程 --- 高级IO

前言 IO Input&&Output read && write 1、在应用层read && write的时候&#xff0c;本质把数据从用户层写给OS --- 本质就是拷贝函数 2、IO 等待 拷贝。 等的是&#xff1a;要进行拷贝&#xff0c;必须先判断读写事件成立。读写事件缓冲区空间满…...

Python中给定一个数组a = [2,3,9,1,0],找出其中最大的一个数,并打印出来 求解?

Python有内置的max函数可以取最大值&#xff1a; max([2,3,9,1,0])也可以使用sorted先排序&#xff0c;再索引取出最大值&#xff1a; sorted([2,3,9,1,0])[-1]如果不用内置函数&#xff0c;自己排序算法来找出最大值&#xff0c;也有很多选择。 比如冒泡排序、循环排序、交…...

系统优化工具 | PC Cleaner v9.7.0.3 绿色版

PC Cleaner是一款功能强大的电脑清理和优化工具&#xff0c;旨在通过清理系统垃圾文件、解除恶意软件和优化系统性能来提高计算机的运行效率。该软件提供了多种功能&#xff0c;可以帮助用户维护和提升计算机的整体表现。 PC Cleaner 支持 Windows 7 及以上操作系统&#xff0…...

JavaSE、JavaEE 与 JavaWeb 的详解与区别

一、JavaSE(Java Standard Edition)——标准版 1. 什么是JavaSE JavaSE,全称Java Standard Edition,译为Java标准版,是Java平台的基础,也是开发者最常使用的Java版本。JavaSE包含了编程中最基础的核心库,如Java的基本语法、面向对象编程、集合框架、多线程、网络编程、…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...