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

Apache BookKeeper 一致性协议解析

导语

Apache Pulsar 是一个多租户、高性能的服务间消息传输解决方案,支持多租户、低延时、读写分离、跨地域复制(GEO replication)、快速扩容、灵活容错等特性。Pulsar 存储层依托于 BookKeeper 组件,所以本文简单探讨一下 BookKeeper(下文简称 BK) 的一致性协议是如何实现的。

背景

Pulsar相对于 Kafka 根本的区别在于数据一致性协议,这也是为什么 Pulsar 可以做到两副本就能保障高可用、高可靠,在磁盘使用方面更均衡,也不会存在单分区容量上限,同时在扩缩容、故障屏蔽等日常运维方面更加灵活和方便。

一致性协议简介

我们常见的一致性协议,比如 Raft、Kafka、ZAB 等,都是服务端集成协议(协议控制和数据存储绑定),简单来说一致性协议由服务端存储节点来执行。数据流向通常是客户端写数据到 Leader 节点,其他节点再通过推或拉的方式从 Leader 获取数据。

在这里插入图片描述

而 BK 的一致性协议控制和存储是分开的,协议控制是在客户端执行,可以称之为外部一致性协议,或者客户端一致性协议。数据流向为客户端向多台存储节点同时写入数据,存储节点之间基本不通信。

在这里插入图片描述

由于一致性协议主要是在客户端执行,本文聚焦于 BK 客户端的实现。所以,外部一致性协议需要解决的问题就简化成,给定 N 个 KV 存储(Bookie 高性能、低可靠)和一个分布式协调系统(下文以 ZK 为例, 低性能,高可靠),如何实现高可用、高可靠、低时延的读写服务。这里引入 ZK 集群的原因是 Bookie 节点会动态增减,需要有注册中心能让客户端拉取存活 Bookie 的 IPPort,同时客户端不具备存储能力,协议控制必要的元信息信息也需要存储到外部。在详细了解 BK 一致性协议之前, 我们先简单介绍一下BK 提供的基础能力。

BookKeeper 基本能力

协议作者设计 BK 初衷是提供分布式日志段(Ledger)而不是无界日志(如 Raft、Kafka)。所以 BK 提供是主要能力就是创建删除 Ledger 以及 Ledger 的读写操作。代码片段如下:

public interface BookKeeper extends AutoCloseable {CreateBuilder newCreateLedgerOp(); // 创建新 LedgerOpenBuilder newOpenLedgerOp(); // 打开已存在LedgerDeleteBuilder newDeleteLedgerOp(); // 删除 Ledger}
public interface ReadHandle extends Handle {CompletableFuture<LedgerEntries> readAsync(long firstEntry, long lastEntry); // 从 ledger 里读数据}public interface WriteHandle extends ReadHandle, ForceableHandle {CompletableFuture<Long> appendAsync(ByteBuf data); // 往ledger 写数据
}

而 MQ 场景需要提供无界数据流,所以 Pulsar 为每一个分区维护一组 Ledger,每个 Ledger 只会写一定量数据(条数 | 大小 | 时间限制),写满就会创建新的 Ledger 来继续写入,只有最新的 Ledger 可以写入,历史的 Ledger 只能读取。再根据用户配置的数据保存策略逐步删除历史 Ledger。Pulsar 内分区 Legder 组成如下:

在这里插入图片描述

Pulsar 内分区的元数据如下:

{"lastLedgerCreatedTimestamp": "2024-06-06T17:03:54.666+08:00", "waitingCursorsCount": 0,"pendingAddEntriesCount": 2,"lastConfirmedEntry": "11946613:185308","state": "LedgerOpened","ledgers": [{"ledgerId": 11945498,"entries": 194480,"size": 420564873,"offloaded": false,"underReplicated": false  
}, {"ledgerId": 11946057,"entries": 194817,"size": 420583702,"offloaded": false,"underReplicated": false  
}, {"ledgerId": 11946613,"entries": 0, // 未关闭的ledger,还没有条数"size": 0, // 未关闭的ledger,还没有大小 "offloaded": false,"underReplicated": false
}]
}

创建 Ledger

BK 以 Ledger 维度对外提供服务,第一步就是创建 Ledger。一致性协议高可用是由数据的多副本存储来实现的,所以创建 Ledger 时需要明确指定该 Ledger 的数据存储在几台 Bookie(E),每条数据写几份(Qw),需要等到多少台 Bookie 确认收到数据后才认为数据写入成功(Qa)。代码片段如下:

public interface CreateBuilder extends OpBuilder<WriteHandle> {// E: 接收该 Ledger 数据的节点数 默认值3CreateBuilder withEnsembleSize(int ensembleSize);/*** Qw: 每条数据需要写几个节点, 默认值2* 如果 E > Qw, 数据会条带化均匀写入到 Bookie 节点,按照默认配置,* 写入顺序为: 消息0=0,1; 消息1=1,2; 消息3=2,0*/CreateBuilder withWriteQuorumSize(int writeQuorumSize);// Qa: 需要等待几台节点确认写入成功,默认值2CreateBuilder withAckQuorumSize(int ackQuorumSize);

ps: 配置条带化写入(E > Qw)可以让多个节点分摊该 Ledger 的读写压力,但是由于数据会更分散且不连续,会影响服务端读写优化(顺序写、预读、批读等), 所以生产环境建议配置 E=Qw。而 Qa < Qw 可以让客户端在避免等待慢节点返回从而降低写入时延,但是优化效果有限,同时增加了存储成本,可以配合客户端熔断以及服务端指标来剔除慢节点。简单来说,建议生产环境配置 E=Qw=Qa。

在创建 Ledger 时,客户端会从 ZK 中拉取全部 Bookie,根据 EnsemblePlacementPolicy 放置策略挑选节点,默认策略会随机挑选出 E 个数的节点作为初始存储节点以保证存储节点使用均衡。同时 BK 提供丰富的放置策略满足用户实现跨机房、地区容灾的需求。

在这里插入图片描述

现在我们已经有了 Ledger 基本元数据,包含 Ledger 配置信息以及初始的 Bookie 列表,由于 Ledger 需要全局唯一,所以还需要使用 ZK 获取一个全局唯一的ID,最后把全部元数据写入到 ZK 集群即可(创建过程无需与 Bookie 节点交互)。代码如下:

在这里插入图片描述

BK 中 Ledger 元数据信息如下:

LedgerMetadata {  formatVersion = 3, ensembleSize = 3, writeQuorumSize = 3, ackQuorumSize = 3, state = CLOSED, length = 461178392, lastEntryId = 203045, digestType = CRC32C, password = base64: , ensembles = {0 = [127.0 .0 .1: 3181, 127.0 .0 .2: 3181, 127.0 .0 .3: 3181]  }, customMetadata = {    component = base64: bWFYWdlZC1sZWRnZXI = ,    pulsar / managed - ledger = base64: cHVibGjL2RlZmF1bHQvcGVyc2lzdGVudC9kd2RfMjEwODQtcGFydGl0aW9uLTA = ,    application = base64: cHVsc2Fy  }
}

数据写入

Ledger 已经创建好了,现在可以往里面写数据了。由于 Bookie 是个 KV 存储,数据需要全局唯一的 Key, Ledger Id 已经是全局唯一了,并且只有创建这个 Ledger 的客户端才能往里面写数据,所以数据的 Key 为 LedgerId + EntryId(客户端单调自增即可)。

在这里插入图片描述

每次写入都从 E 个Bookie 中顺序挑选出 Qw 个节点(writeSet)并行发送数据。

在这里插入图片描述
在这里插入图片描述

可以看到客户端并行写入 Bookie 节点,Bookie 间也不需要做数据同步,所以 Pulsar 即使加了一层 Broker 层,但是写入时延还是能做到和 Kafka 基本一致(2RTT)。

正常返回

发送数据后,如果收到 Qa 个数成功响应, 就可以认为这条数据写入成功了。同时这条数据也可以被读取。

在这里插入图片描述
在这里插入图片描述

LAC(LastAddConfirmed)

由于 EntryId 单调递增,数据按顺序写入,当一个位置的数据成功写入到 Qa 个存储节点后,就意味着该条数据以及之前的所有数据都可以被消费走,这个位置就是可读位置(不考虑事务),由于已写入多副本,也意味着这些数据可以在部分存储节点故障下存活,从而保证读一致性。不同协议中有不同的叫法。在 BK 中,这个位置称为 LAC(LastAddConfirmed),Kafka 中称为 HW(High Watermark**),在 Raft 中称为 Commitindex。

下面代码可以看到,成功写入一条数据后会立即更新可读位置。

在这里插入图片描述

LAC 是一致性协议至关重要的信息,正常情况下,LAC 只需要维护在内存中,写入成功后更新。读取时使用 LAC 来限制读取位置即可。如果当前 Ledger 写满了,正常关闭,需要把 LAC 也保存在 ZK 的元数据中。这样重新打开 Ledger 用于读取时,才能加载 LAC 回内存中,用来正确限制读取位置,同时 LAC 值也能标识 Ledger 中存储的数据条数。

而异常情况下,故障会直接导致内存中的 LAC 丢失,这是不可接受的。最简单粗暴的方式是每次更新 LAC 都直接同步到 ZK 中,但显然 ZK 性能无法满足高频率写入,所以 BK 的做法是在每条数据都携带 LAC 信息一起写入到 Bookie 中。

在这里插入图片描述

如下图,由于最新的 LAC 需要等下一轮的消息写入才会一起存储到 Bookie,所以 Bookie 中存储的 LAC 与实际的 LAC 相比是存在一定滞后。

在这里插入图片描述

上图可看到初始时客户端和 Bookie 中的 LAC 都为-1,在一轮写入后,Bookie 中的 LAC 就会滞后于客户端了,好在这个问题并不会影响到一致性协议的正确性,下文中会提到。

故障处理

因为需要容忍一定数据的节点故障,所以一致性协议复杂的部分都在故障处理逻辑。接下来我们先看写入失败场景。

写入失败

Kafka、Raft 等集成一致性协议,部分存储节点异常处理相对简单,如果是 Leader 节点故障,切换到其他节点进行读写即可。如果是 Follower 节点异常,通常不需要做任何操作,只需要等节点恢复后,从 Leader 节点同步数据补齐差异。由于节点是带存储的,所以可以容忍较长时间的节点故障。而 BK 是在客户端实现的一致性协议,客户端不带存储,没写成功的数据需要缓存在内存里,显然可缓存的数据是非常有限的,同时没有写成功 LAC 是不能推进的,整个写入也就停止了。所以 BK 在写入失败时,最好是方式就是挑选新的 Bookie 节点重新写入,创建 Ledger 时已经指定了初始的 Bookie 列表,后续的 Bookie 列表变更都称为 EnsembleChange。

EnsembleChange

如下图所示,写入失败时会替换新节点重新写入,同时也会把新元数据更新到 ZK。每次 EnsembleChange,Ensembles 中就会新增一个 Fragment,起始位置为当前 LAC+1。

在这里插入图片描述

写入存储节点失败简单可以分两种情况,如果还没有收到Qa 个数成功响应前,收到了错误响应(比如超时等),会立即执行 EnsembleChange。如果已经收到了 Qa 个数成功响应(更新 LAC),后续的错误响应只会记录下失败 Bookie 节点,在下一次写入时再触发 EnsembleChange。如果是不可恢复的异常,会直接返回写入失败,不会做 EnsembleChange。

在这里插入图片描述

EnsembleChange 和初始创建 Ledger 一样,需要先选择新的 Bookie 节点列表,替换掉失败的 Bookie 节点列表,然后把替换后的元数据更新回 ZK,代码如下:

在这里插入图片描述

更新完元数据后,重写数据到新节点,或者根据条件重新触发 EnsembleChange,代码如下:

在这里插入图片描述

简而言之,在写入 Bookie 异常时,BK 客户端都会尝试切换 Bookie 节点重写数据。如果遇到无法恢复的状态码,就会直接往外层抛出异常。上层使用方比如 Pulsar 侧接收到这个异常后,会正常关闭当前 Ledger,然后创建新的 Ledger 继续写入。

客户端故障

如果存储 Bookie 节点故障,客户端可以切换其他 Bookie 继续完成写入。接下来我们看看如果正在写入的客户端故障了,应该怎么处理。在 Pulsar 架构里,BK 客户端是 Pulsar Broker 内的一个组件,当 Pulsar Broker 故障后,分区的 Leader 会切换到新 Broker 继续服务。由于 BK 客户端故障时没有来得及正确关闭 Ledger。当前 Ledger 数据是不可读的,因为 LAC 信息已经丢失。所以新 Broker 主要任务就是要恢复 LAC 信息,然后正确关闭 Ledger,这样 Ledger 内数据就可以正确读取。最后再创建新的 Ledger 来恢复写入。由于恢复过程对应分区无法写入,所以要求恢复过程越快越好,不可存在长时间阻塞。

Fence Bookie

在恢复之前,由于老 Broker 有可能是短暂假死(比如长时间 gc、网络隔离等),后续可能还会持续向当前 Ledger 写入数据导致脑裂。所以恢复的第一步就是要通知对应 Bookie 节点,后续禁止往这个 ledger 里面写任何数据,这个过程称为 Fence。过程相对简单,就是向所有 Essemble 中的 Bookie 节点发送请求让其禁止该 Ledger 的后续写入。如下图所示:

在这里插入图片描述

由于当前 Bookie 节点不能保证全部存活,同时需要满足快速恢复,所以需要考虑客户端至少收到多少个成功响应,才能认为 Fence 操作执行成功。这里可以反过来想一下,客户端每次需要写入 Qw 个节点,然后收到 Qa 个成功响应就能认为写入成功,我们只需要让其凑不足 Qa 个成功响应就好了,也就是我们 Fence 掉的节点数只要大于 Qw - Qa,那写入客户端就一定凑不足 Qa 个成功响应,我们暂且称这个数量为 Qf。例子如下:

Qw = 5, Qa = 3 ==> Qf = 5 - 3 + 1 = 3
Qw = 3, Qa = 2 ==> Qf = 3 - 2 + 1 = 2 
Qw = 3, Qa = 3 ==> Qf = 3 - 3 + 1 = 1  // Qw=Qa 场景需要fence的节点数最少,恢复最快

由于每次写入都要从 E 个 Bookie 中挑选出 Qw 个节点来条带化写入,所以还需要保证任意一组 Qw,我们都 Fence 掉了对应的 Qf 个节点。例子如下:

假设 E=3,Qw=Qa=2,  Bookies列表为0,1,2, Qf=2-2+1=1
由于会条带化写入,每次写入挑选两台节点,全部组合为:
0,1  // 0,1节点至少要成功fence 掉一台
1,2  // 1,2节点至少要成功fence 掉一台     
2,0  // 2,0节点至少要成功fence 掉一台
易得 0,1,2 三台节点节点至少要成功fence 掉两台,
同理可证如果按上文中推荐配置 E=Qw=Qa, 任何情况下都只需要fence掉任意1台节点即可,fence 节点数量最少,恢复最快。

实际 BK 中实现的代码逻辑是统计任意 writeSet 未成功响应数大于等于 Qa 都认为未成功(功能一致),代码如下:

在这里插入图片描述

恢复 LAC

恢复主要的任务就是恢复 LAC 信息,上文中已经介绍 LAC 信息位于每条数据中存储在 Bookie 里面。接下来看看恢复整个过程。

修改元数据状态

第一步: 把 Ledger 状态改成 Recovery 状态。

在这里插入图片描述

读取初始 LAC

第二步: 读取 Bookie 中的 LAC,下图代码中可以看到 Fence Bookie 操作其实与查询最后 LAC 为同一个请求,只是携带了 Fence 标识。

在这里插入图片描述

取最大的 Bookie LAC 值返回。

在这里插入图片描述

初始 LAC = max(zkLac, bookieLac)作为初始 LAC。

在这里插入图片描述

恢复真实 LAC

由于 Bookie 中存储的 LAC 有滞后性,也就是真实 LAC(故障前内存中的LAC)往往大于目前从 Bookie 查询到的 LAC 最大值。所以我们还需要恢复真实的 LAC,在找到真实的 LAC之前,我们先确定一个基本的原则。最终恢复的 LAC 可以大于真实 LAC,但是绝对不能小于真实 LAC(会导致数据丢失)。比如一条数据已经成功写入到 Qa 个节点中,但是客户端还没来得及接受到 Qa 个成功响应(不更新 LAC)就故障了,那么恢复时把这条数据 EntryID 更新到 LAC 也是合理的。换句话说,当数据成功写入到了 Qa 节点中的那一刻,真实 LAC 就应该立即更新,只不过客户端内存中的 LAC 也存在一定的滞后(需等收到响应)。所以恢复过程可以以当前读取到的 LAC 作为起始 LAC, 依次往后面查询下一条数据(LAC + 1),如果这条数据已经存在到 Qa 个节点中,那这条数据就是可恢复的,向前推进 LAC 并查询下一条数据。直到某条数据存在的节点数少于 Qa , 就可以认定这条数据是不可恢复的。那当前的 LAC 就是真实的 LAC, 恢复过程结束。以上方案判断某条数据是否可恢复是根据收到的存在响应大于等于 Qa,所以对于每条数据都需要查询全部节点。BK 客户端实际上使用另一种更加快速的方式来判断数据是否可恢复。数据是否可恢复判断方式如下:

  • 串行发送读请求
  • 如果收到存在响应,认定数据可恢复,推进 LAC, 继续恢复下一条数据。
  • 其余响应依次读取下一个 Bookie 存储节点
  • 如果已经有 Qf (Fence 章节中定义)个节点明确返回不存在该数据,表明该数据不可能存在于 Qa 个节点上,后续的数据也不用查询了,恢复结束,当前 LAC 为最终需要恢复的 LAC。

下面使用伪代码描述如果判断单条数据是否可恢复。

int notFoundCount = 0;
for(Bookie bookie : writeSet) {   result = bookie.query(lac + 1);    switch( result) {       case : "found"           return "数据可恢复"; // 可恢复,推进LAC 读取下一条数据       case : "not found"if( ++ notFoundCount >= Qf) {                // 终止恢复,当前 LAC 作为最终 LAC                return "数据无法恢复";               };           break;          case : "unkonwn" // 连接不上、超时等   }
}
throw Exception("多节点未知,终止恢复") // 无法恢复,等待重试

举例如下:

Qw=3, Qa = 2, Qf = 3 - 2 + 1 = 2
向bookie0, bookie1, bookie2 同时发出读取请求1. 收到 bookie0 或 bookie1 或 bookie2 存在响应,可恢复
2. bookie0 否定回答,bookie1 否定回答, bookie2 未知回答,不可恢复
3. bookie0 否定回答,bookie1 未知回答, bookie2 未知回答,恢复失败,需要重试

需要特别注意,由于 LAC 非常重要,如果最终恢复的 LAC 小于实际 LAC,就会发生日志截断,相当于这段数据就丢失了。所以我们恢复过程中,需要明确收到 Qf 个Bookie 节点返回数据不存在,而未知返回(节点暂时不可用,超时等)不能等同于数据不存在。

以上方案为了快速恢复,只需要任意一台节点返回数据存在,就认为可恢复,从而推进 LAC。假设这条数据实际上存在份数不足 Qa,就不能容忍后续对应的存储节点故障了。所以会把这条数据覆盖写入全部的存储节点(写成功后自然 LAC 会更新),写入失败(未收到 Qa 个成功)会导致恢复失败。Bookie 是 KV 存储,支持幂等覆盖写。

代码如下:

开始执行恢复 LAC。

在这里插入图片描述

发起读请求。

在这里插入图片描述

返回失败后,向下一个节点发送读请求,如果收到 Qf 个不存在响应,恢复终止。

在这里插入图片描述

读取成功(包含失败重试其他节点): 回写这条数据回 Bookie(LAC自然推进), 触发下一条数据恢复 读取失败(Qf 个节点明确返回数据不存在): 当前 LAC 就为需要恢复的真正 LAC,恢复成功。

在这里插入图片描述

消费

消费逻辑相对简单,当前 Ledger 在写入时,只有当前写入的客户端可以读取数据,使用内存中的 LAC 防止读取越线。其他客户端只能读取已关闭的 Ledger,首先从 ZK 中获取元数据(包含 LAC),然后正常向对应 Bookie 发起请求即可。失败后尝试下一个 Bookie 节点,直到成功或者所有节点都尝试过。恢复 LAC 的读取过程和正常读取数据使用同一套逻辑,且相对简单,这里就不做代码分析。

可以看到 BK 客户端读取消息是按单条消息来读取的,会造成请求数较多。高版本 BK 已经做了一定优化,客户端提供了批读能力,可以和服务端一次交互就读到多条消息。这里有个前提条件,就是数据不能条带化写入,因为条带化写入会让数据分散到多台节点,单台节点内数据不连续,所以生产环境还是建议配置 E=Qw=Qa 。

结语

本文主要从客户端视角分析 BK 一致性协议设计理念以及实现原理,包含 Leger 创建、数据的写入、读取以及客户端服务端故障恢复等内容。可以看到 BK 的一致性协议还是有一些有趣的地方,并且实实在在的解决了一些问题,也能理解到 Pulsar 的存算分离并不是简单的加一层无状态代理层来实现的。

相关文章:

Apache BookKeeper 一致性协议解析

导语 Apache Pulsar 是一个多租户、高性能的服务间消息传输解决方案&#xff0c;支持多租户、低延时、读写分离、跨地域复制&#xff08;GEO replication&#xff09;、快速扩容、灵活容错等特性。Pulsar 存储层依托于 BookKeeper 组件&#xff0c;所以本文简单探讨一下 BookK…...

Solana的账户模型

Solana的账户模型与其他区块链平台&#xff08;如以太坊&#xff09;有所不同&#xff0c;其设计旨在提高性能和扩展性。以下是Solana账户模型的主要特点和工作原理&#xff1a; Solana账户模型概述 账户类型&#xff1a; 普通账户&#xff08;User Accounts&#xff09;&…...

iPython与Matplotlib:数据可视化的秘籍

iPython与Matplotlib&#xff1a;数据可视化的秘籍 前言 欢迎来到"iPython与Matplotlib&#xff1a;数据可视化的秘籍"教程&#xff01;无论你是数据可视化新手还是希望提升技能的专业人士&#xff0c;这里都是你开始的地方。让我们开始这段数据可视化之旅吧&#…...

做一只勤劳的小蜜蜂

机缘 成为创作者的初心&#xff0c;对我而言&#xff0c;是一个融合了个人兴趣、职业成长以及对知识传播热爱的复杂而纯粹的情感交织。回顾这段旅程的起点&#xff0c;几个核心驱动力始终引领着我前行&#xff1a; 1、记录与反思&#xff1a;在职业生涯的早期&#xff0c;我遇…...

如何处理 PostgreSQL 中死锁的情况?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 如何处理 PostgreSQL 中死锁的情况&#xff1f;一、认识死锁二、死锁的症状三、死锁的检测四、预防死锁…...

新版本 idea 创建不了 spring boot 2 【没有jkd8选项】

创建新项目 将地址换成如下 https://start.aliyun.com/...

linux系统和windows系统如何同步时间,服务器时间变动怎么同步

一、Linux系统时间同步 1. 使用NTP&#xff08;网络时间协议&#xff09; NTP是最常用的Linux系统时间同步方式。NTP通过连接到外部时间服务器&#xff08;如原子钟或GPS接收器&#xff09;来获取高精度的时间信息&#xff0c;并校准本地系统时间。 步骤&#xff1a; 安装N…...

Mac M1安装配置Hadoop+Flink SQL环境

Flink 1.18.1 Hadoop 3.4.0 一、准备工作 系统&#xff1a;Mac M1 (MacOS Sonoma 14.3.1) JDK&#xff1a;jdk1.8.0_381 &#xff08;注意&#xff1a;尽量一定要用JDK8&#xff0c;少用高版本&#xff09; Scala&#xff1a;2.12 JDK安装在本机的/opt/jdk1.8.0_381.jdk/C…...

【所谓生活】马太效应

简介 马太效应又称马太定律或两级分化现象。该效应描述的是在社会生活中&#xff0c;强者因为优势而获得更多机会&#xff0c;而弱者因劣势而失去机会&#xff0c;最终导致强者愈强、弱者愈弱的现象。这一概念最早由美国社会学家罗伯特莫顿于1968年提出&#xff0c;其名字来源…...

品牌进行电商数据采集的流程

品牌在进行数据分析与渠道管控时&#xff0c;均离不开电商数据的有力支撑&#xff0c;故而数据采集的质量举足轻重。电商数据采集首先要确保准确率&#xff0c;其次要保障覆盖率&#xff0c;即页面上呈现的商品信息必须采集完整&#xff0c;否则难以得出精确的数据分析成果&…...

面试问题:React基本概念,和所遇到的CPU和IO问题

在官方文档里面可以看见React基本设计概念&#xff0c;React是用 JavaScrip构建快速响应的大型Web应用程序的首选方式&#xff0c;但是快速响应用一定的是依赖&#xff0c;CPU的性能和IO的约束。 首先CPU性能原因&#xff1a;大部分浏览器的刷新频率为60HZ&#xff0c;及16.6ms…...

FOG Project 文件名命令注入漏洞复现(CVE-2024-39914)

0x01 产品简介 FOG是一个开源的计算机镜像解决方案,旨在帮助管理员轻松地部署、维护和克隆大量计算机。FOG Project 提供了一套功能强大的工具,使用户能够快速部署操作系统、软件和配置设置到多台计算机上,从而节省时间和精力。该项目支持基于网络的 PXE 启动、镜像创建和还…...

JavaScript 表单

JavaScript 表单 JavaScript 是一种广泛应用于网页开发的编程语言,它能够让网页变得更加动态和交互式。在网页设计中,表单是一个重要的组成部分,它允许用户输入数据并将其提交到服务器。JavaScript 可以用来增强表单的功能,提供更好的用户体验。本文将详细介绍如何使用 Ja…...

python程序设定定时任务

在 Windows 系统上,您可以使用任务计划程序(Task Scheduler)来设置定时任务,执行 Python 文件。以下是具体步骤: 步骤 1:准备 Python 文件 假设有一个名为 script.py 的 Python 脚本。确保它可以在命令行中正确运行。 步骤2:找到Python可执行文件的位置 知道Python可…...

win10 查看 jks 的公钥

1.使用 keytool 导出jks文件的 crt 文件 先查询别名 keytool -list -keystore oauth2.jks -storepass [你的密钥库密码] 导出crt 文件 keytool -exportcert -alias oauth2 -keystore oauth2.jks -file 777.crt 2.查看公钥 打开PowerShell # 设置.crt文件的路径 $ce…...

蓝牙模块在智能体育设备中的创新应用

随着科技的飞速发展&#xff0c;智能体育设备已经成为现代体育训练和健身的重要组成部分。蓝牙模块作为智能体育设备中的核心技术之一&#xff0c;其创新应用不仅提升了设备的智能化水平&#xff0c;也为运动员和健身爱好者带来了前所未有的便利和体验。本文将探讨蓝牙模块在智…...

智能家居和智能家电有什么区别?

智能家居和智能家电在定义、涵盖范围、功能特点以及系统集成度等方面存在显著区别。 一、定义 智能家居&#xff1a;智能家居是指通过物联网技术、人工智能技术等先进技术&#xff0c;将家居设备与互联网连接起来&#xff0c;实现智能化控制和管理的一种新型生活方式。它不仅…...

SpringBoot3 + Vue3 学习 Day 1

springboot 基础 和 注册接口的开发 学习视频基础SpringBoot 概述快速启动配置文件基本使用① application.properties② application.yml &#xff08;更好&#xff09; yml 配置信息的书写和获取yml 配置信息书写与获取 1 - Valueyml 配置信息书写与获取 2 - ConfigurationPr…...

如何使用在线工具将手机相册中的图片转换为JPG格式

我们经常在手机相册中保存大量的图片&#xff0c;无论是家庭聚会的照片还是旅行的瞬间&#xff0c;每一幅图像都承载着珍贵的记忆。然而&#xff0c;有时候我们会遇到图片格式不兼容的问题&#xff0c;尤其是在需要将图片分享到特定平台或编辑时。 例如&#xff0c;某些社交平台…...

C#医学影像管理系统源码(VS2013)

目录 一、概述 二、系统功能 系统维护 工作站 三、功能介绍 影像采集 统计模块 专业阅片 采集诊断报告 报告管理 一、概述 医学影像存储与传输系统&#xff08;PACS&#xff09;是一种集成了影像存储、传输、管理和诊断功能的系统。它基于数字化成像技术、计算机技术和…...

Qt Creator 项目Console 项目踩坑日记

最近在做QT的项目&#xff0c;但是一直是带界面&#xff08;QT Widgets&#xff09;程序&#xff0c;前几天收到个需求&#xff0c;是要做个socket服务端的桌面程序&#xff0c;界面有没有都成&#xff0c;然后就想着接着用 QT Widgets 搞&#xff0c;结果辛辛苦苦把socket服…...

[MAUI 项目实战] 笔记App(一):程序设计

文章目录 前言框架定义核心类项目地址 系列文章&#xff1a; [MAUI 项目实战] 笔记App&#xff08;一&#xff09;&#xff1a;程序设计 [MAUI 项目实战] 笔记App&#xff08;二&#xff09;&#xff1a;数据库设计 前言 有人说现在记事类app这么多&#xff0c;市场这么卷&a…...

VisualRules-Web案例展示(一)

VisualRules单机版以其卓越的功能深受用户喜爱。现在&#xff0c;我们进一步推出了VisualRules-Web在线版本&#xff0c;让您无需安装任何软件&#xff0c;即可在任何浏览器中轻松体验VisualRules的强大功能。无论是数据分析、规则管理还是自动化决策&#xff0c;VisualRules-W…...

使用Docker 实现 MySQL 循环复制(三)

系列文章 使用Docker 实现 MySQL 循环复制&#xff08;一&#xff09; 使用Docker 实现 MySQL 循环复制&#xff08;二&#xff09; 目录 系列文章1. 在主机上安装MySQL客户端2. 配置循环复制拓扑2.1 进入容器2.2 创建复制用户并授予复制权限2.3 复位二进制日志2.4 配置环形复…...

Spring如何管理Mapper

目录 一、背景二、猜测三、源码查看步骤1、创建MapperScannerConfigurer.java2、MapperScan注解3、MapperScannerRegistrar执行registerBeanDefinitions方法4、MapperScannerConfigurer执行postProcessBeanDefinitionRegistry方法5、执行doscan6、设置beanClass7、使用jdk生成代…...

NFS存储、API资源对象StorageClass、Ceph存储-搭建ceph集群和Ceph存储-在k8s里使用ceph(2024-07-16)

一、NFS存储 注意&#xff1a;在做本章节示例时&#xff0c;需要拿单独一台机器来部署NFS&#xff0c;具体步骤略。NFS作为常用的网络文件系统&#xff0c;在多机之间共享文件的场景下用途广泛&#xff0c;毕竟NFS配置方 便&#xff0c;而且稳定可靠。NFS同样也有一些缺点&…...

「Vue组件化」封装i18n国际化

前言 在Vue应用中实现国际化(i18n),通常需要使用一些专门的库,比如vue-i18n。本文主要介绍自定义封装i18n,支持Vue、uniapp等版本。 设计思路 一、预期效果 二、核心代码 2.1 i18n.xlsx文件准备 2.2 脚本执行 根目录main.js根目录locali18n.xlsxnode main.jsmain.js 文件…...

【Git远程操作】理解分布式管理 | 创建远程仓库

目录 1.理解分布式管理 多人协作开发 2.创建远程仓库 2.1仓库名&路径 2.2初始化仓库&设置模板 1.理解分布式管理 目前我们学习的所有内容都是在本地来完成的。&#xff08;add /commit /版本撤销回退/分支管理&#xff09; Git是一个分布式 的版本控制系统。 分支…...

OJ-0718

示例1 input 3 1 1 1 output 0示例2 input 3 3 7 10 output 1示例3 input 4 3 7 10 16 output 3题解1&#xff1a;List {public static void main(String[] args) {Scanner in new Scanner(System.in);int num in.nextInt();List<Integer> list new ArrayList<…...

python抓包 -- 用wireshark抓包、解析--scapy、PyShark

一、 安装scapy模块 1、打开DOS命令行 python -m pip install scapy2、sniff()函数 功能&#xff1a;数据嗅探 参数&#xff1a; iface: 指定在哪个网络接口上抓包 count: 表示要捕获数据包的数量。默认为0(不限制数量) filter: 流量的过滤规则。使用的是 BPF 的语法 prn:…...

网站免费网站免费片黄入口蜜桃观看射破屁屁/天津seo诊断技术

R-CNN&#xff0c;Fast R-CNN&#xff0c;Faster R-CNN这些是深度学习目标检测的鼻祖。看各种博客分析&#xff0c;东看看西看看&#xff0c;不系统。这里准备系统的记录一下深度学习目标检测的发展史。这里大部分摘录其他博客。参考链接见下。 R-CNN&#xff0c;Fast R-CNN&am…...

威海千淼网站建设/互联网营销师有什么用

题目&#xff1a;&#xff08;最小操作次数使数组元素相等&#xff09;给你一个长度为 n 的整数数组&#xff0c;每次操作将会使 n - 1 个元素增加 1 。返回让数组所有元素相等的最小操作次数。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;3 解释&…...

做网站cdn加速有什么用/推广优化排名

#include<iostream> #include<Windows.h>//Sleep调用 using namespace std; int main() {std::cout << "\n正在输出记录数据...... ";for( int i0; i < 100; i ) // 打印百分比 {std::cout.width(3);//i的输出为3位宽std::cout << i…...

wordpress识图搜索代码/百度seo原理

1&#xff0c;java.lang.NoSuchMethodException: com.opensymphony.xwork2.ActionSupport.getNetEventList()转载于:https://www.cnblogs.com/qingmaple/p/4058827.html...

中国站长站/泰州seo外包公司

使用Python原生IDLE IDLE是Python软件包自带的一个集成开发环境&#xff0c;点击开始-->Python安装包-->IDLE。启动 IDLE 时&#xff0c;会显示>>>&#xff0c;可以在>>>后面输入代码。在 Python Shell 输入代码回车后会立即执行&#xff0c;并直接在下…...

拍卖网站建设公司/百度销售是做什么

些括哪面试体细节包的具&#xff0c;用计用水杯尊重名片握手交换接递前台。酸性中性碱性&#xff0c;算机奥1为(焊条药皮成分。管理释)子弧(名词解等离切割。能正伤方现焊陷类型和形状部缺探伤缝内法是大小的探确发&#xff0c;.射线C波B超声.磁粉。时的锈钢些主要问题铁素体不…...