「布道师系列文章」小红书黄章衡:AutoMQ Serverless 基石-秒级分区迁移

news2025/1/19 20:24:08

作者|黄章衡,小红书消息引擎研发专家

01 引言

Apache Kafka 因存算一体化架构,分区迁移依赖大量数据同步的完成,以一个 100MB/s 流量的 Kafka 分区为例,运行一天产生的数据量约为 8.2T,如果此时需要将该分区迁移到其他 Broker,则需要对全量数据进行复制,即使对拥有 1 Gbps 带宽的节点,也需要小时级的时间来完成迁移,这使得 Apache Kafka 集群几乎不具备实时弹性能力。

而得益于 AutoMQ Kafka 的存算分离架构,在实际进行分区迁移时无需搬迁任何数据,这使得将分区迁移时间缩短至秒级成为了可能。
本篇文章将详细解析 AutoMQ 秒级迁移能力对应的原理和源码部分,并在最后讨论秒级迁移能力的应用场景。

02 AutoMQ 分区迁移流程概述

如下图,以分区 P1 从 Broker-0 迁移至 Broker-1 为例,流程分为六步:
ꔷ Step1 构建分区迁移命令: Controller(ReplicationControlManager:AlterPartitionReassign)当 Kraft Controller 收到分区迁移命令时,会构建出相应的 PartitionChangeRecord 并 commit 至 Kraft Log 层,将 Broker-0 从 leader replica 列表中删除,并将 Broker-1 加入 follower replica 列表中。
ꔷ Step2 Broker 同步分区变更: Broker(ElasticReplicaManager:AsyncApplyDelta)Broker-0 同步 Kraft Log 监听到 P1 分区变更,进入分区关闭流程。
ꔷ Step3 元数据持久化与分区 Stream 关闭: Broker (ElasticLog: Close) ElasticLog 为 LocalLog 基于 S3Stream 的实现。ElasticLog 会先持久化分区元数据至 Meta Stream(包括 LeaderEpoch、ProducerSnapshot、SegmentList、StreamIds etc..),随后将 Meta 和 Data Stream 全部 Close。
ꔷ Step4 数据上传与 Stream 关闭: Stream (S3Stream: Close) 每个 Stream 关闭时,若还存在未上传至对象存储的数据,则会触发强制上传,而在一个稳定运行的集群中,这部分数据往往不超过数百 MB,结合目前云厂商提供的突发网络带宽能力,这个过程一般仅需秒级即可完成。当 Stream 的数据上传完成后,即可安全的上报 Controller 关闭该 Stream 并从 Broker-0 删除分区 P1。
ꔷ Step5 主动重新触发选主: Controller (ReplicationControlManager:ElectLeader) P1 从 Broker 完成关闭后会主动触发一次选主,此时 Broker-1 作为唯一的 replica 晋升为 P1 的 leader,进入分区恢复流程。
ꔷ Step6 分区恢复与数据恢复: Broker (ElasticLog: Apply) 分区恢复时,会先上报 Controller 打开 P1 对应的 Meta Stream,根据 Meta Stream 从对象存储中拉取 P1 对应的元数据,从而恢复出 P1 相应的 checkpoint(Leader Epoch/SegmentList etc..),后根据 P1 的关闭状态(是否为 cleaned shutdown)进行对应的数据恢复。

03 AutoMQ 分区迁移流程源码解析

接下来我们详细解析分区迁移六步骤的源码,仍然以分区 P1 从 Broker-0 迁移至 Broker-1 为例:

注:AutoMQ 关闭分区前,需要先上报 Controller 关闭分区对应的所有 Stream 使其变为 Closed State,以便分区恢复时能够重新打开 Stream 使其变为 Opened State。这么做的目的是防止脑裂(也即两台 Broker 同时打开同一个 Stream),统一由 Controller 调控 Stream 的 State 和 Owner。 

Step1: Controller 构建分区迁移命令 当 Controller 收到 alterPartitionReassignments 指令时,会构建 PartitionChangeBuilder 将该 Partition 的 TargetISR、Replicas 设置为目标 [1],但不会直接选举 Leader 而是选择延后选举,以保障选举前分区对应的 Stream 已经正常关闭。

此外,流程中还设置了分区选主超时器,若一段时间内源Broker 没有成功触发选主,则会在 Controller 中主动触发选主。

ReplicationControlManager:changePartitionReassignmentV2 {

    PartitionChangeBuilder builder = new PartitionChangeBuilder(part,
        tp.topicId(),
        tp.partitionId(),
        // no leader election, isAcceptableLeader 直接返回 False,代表不选主
        brokerId -> false,
        featureControl.metadataVersion(),
        getTopicEffectiveMinIsr(topics.get(tp.topicId()).name.toString())
    );
    builder.setZkMigrationEnabled(clusterControl.zkRegistrationAllowed());
    builder.setEligibleLeaderReplicasEnabled(isElrEnabled());
    // 设置 ISR、Replicas 为 [target.replicas().get(0)]
    builder.setTargetNode(target.replicas().get(0));
    TopicControlInfo topicControlInfo = topics.get(tp.topicId());
    if (topicControlInfo == null) {
        log.warn("unknown topicId[{}]", tp.topicId());
    } else {
        // 选主超时器
        TopicPartition topicPartition = new TopicPartition(topicControlInfo.name, tp.partitionId());
        addPartitionToReElectTimeouts(topicPartition);
    }
    return builder.setDefaultDirProvider(clusterDescriber).build();
}

Step2: Broker 同步分区变更 Controller 更新 Partition 的 Replicas 后,Broker-0 同步 Kraft Log 监听到 P1 分区变更,该分区不再属于 Broker-0,因此进入分区关闭流程。

ElasticReplicaManager: asyncApplyDelta(delta: TopicsDelta, newImage: MetadataImage) {
    if (!localChanges.deletes.isEmpty) {
      val deletes = localChanges.deletes.asScala
        .map { tp =>
          val isCurrentLeader = Option(delta.image().getTopic(tp.topic()))
            .map(image => image.partitions().get(tp.partition()))
            .exists(partition => partition.leader == config.nodeId)
          val deleteRemoteLog = delta.topicWasDeleted(tp.topic()) && isCurrentLeader
          StopPartition(tp, deleteLocalLog = true, deleteRemoteLog = deleteRemoteLog)
        }
        .toSet
    
      def doPartitionDeletion(): Unit = {
        stateChangeLogger.info(s"Deleting ${deletes.size} partition(s).")
        deletes.foreach(stopPartition => {
          val opCf = doPartitionDeletionAsyncLocked(stopPartition)
          opCfList.add(opCf)
        })
      }
    
      doPartitionDeletion()
    }    
}

Step3: Broker 元数据持久化与分区 Stream 关闭 当 ReplicasManager 调用 StopPartition 后,会一层层调用至 ElasticLog.Close.ElasticLog 为 LocalLog 基于 S3Stream 的实现,分区的数据和元数据与 S3Stream 的对应关系如下:

  • 每个 Segment 被映射到 DataStream

  • Segment 的 TxnIndex 和 TimeIndex 为别被映射为 Txn Stream 和 Time Stream

  • 分区的元数据 (producerSnapshot、LeaderEpoch、Streamids、SegmentList ...)则以KV的形式映射为 Meta Stream

ElasticLog 会先持久化分区元数据至 Meta Stream,随后将 Meta 和 Data Stream 全部 Close:

ElasticLog close(): CompletableFuture[Void] = {
    // already flush in UnifiedLog#close, so it's safe to set cleaned shutdown.
    // 标志为 Clean Shutdown
    partitionMeta.setCleanedShutdown(true)
    partitionMeta.setStartOffset(logStartOffset)
    partitionMeta.setRecoverOffset(recoveryPoint)

    maybeHandleIOException(s"Error while closing $topicPartition in dir ${dir.getParent}") {
        // 持久化元数据
        CoreUtils.swallow(persistLogMeta(), this)
        CoreUtils.swallow(checkIfMemoryMappedBufferClosed(), this)
        CoreUtils.swallow(segments.close(), this)
        CoreUtils.swallow(persistPartitionMeta(), this)
    }
    info("log(except for streams) closed")
    // 关闭分区对应的所有 Streams
    closeStreams()
}

Step4: S3Strema 数据上传与关闭 每个 Stream 关闭时:

  1. 等待所有未完成的 request
  2. 若还存在未上传至对象存储的数据,则会触发强制上传,而在一个稳定运行的集群中,这部分数据往往不超过数百 MB,结合目前云厂商提供的突发网络带宽能力,这个过程一般仅需秒级即可完成
  3. 当 Stream 的数据上传完成后,即可安全的上报 Controller 关闭该 Stream
S3Stream:Close(){

    // await all pending append/fetch/trim request
    List<CompletableFuture<?>> pendingRequests = new ArrayList<>(pendingAppends);
    if (GlobalSwitch.STRICT) {
        pendingRequests.addAll(pendingFetches);
    }
    pendingRequests.add(lastPendingTrim);
    CompletableFuture<Void> awaitPendingRequestsCf = CompletableFuture.allOf(pendingRequests.toArray(new CompletableFuture[0]));
    CompletableFuture<Void> closeCf = new CompletableFuture<>();
    
    // Close0 函数触发强制上传和 Stream 关闭
    awaitPendingRequestsCf.whenComplete((nil, ex) -> propagate(exec(this::close0, LOGGER, "close"), closeCf));
    
}

private CompletableFuture<Void> close0() {
    return storage.forceUpload(streamId)
        .thenCompose(nil -> streamManager.closeStream(streamId, epoch));
}

Step5: Broker 主动重新触发选主 P1 从 Broker 完成关闭后会主动触发一次选主:

ElasticReplicaManager:StopPartitions(partitionsToStop: collection.Set[StopPartition]) {

    partitionsToStop.foreach { stopPartition =>
      val topicPartition = stopPartition.topicPartition
      if (stopPartition.deleteLocalLog) {
        getPartition(topicPartition) match {
          case hostedPartition: HostedPartition.Online =>
            if (allPartitions.remove(topicPartition, hostedPartition)) {
              maybeRemoveTopicMetrics(topicPartition.topic)
              // AutoMQ for Kafka inject start
              if (ElasticLogManager.enabled()) {
                // For elastic stream, partition leader alter is triggered by setting isr/replicas.
                // When broker is not response for the partition, we need to close the partition
                // instead of delete the partition.
                val start = System.currentTimeMillis()
                hostedPartition.partition.close().get()
                info(s"partition $topicPartition is closed, cost ${System.currentTimeMillis() - start} ms, trigger leader election")
                
                // 主动触发选主
                alterPartitionManager.tryElectLeader(topicPartition)
              } else {
                // Logs are not deleted here. They are deleted in a single batch later on.
                // This is done to avoid having to checkpoint for every deletions.
                hostedPartition.partition.delete()
              }
              // AutoMQ for Kafka inject end
            }
    
          case _ =>
        }
        partitionsToDelete += topicPartition
      }

}

Controller 中, Broker-1 作为唯一的 replica 晋升为 P1 的 leader,进入分区恢复流程

Step6: Broker 分区恢复与数据恢复 Broker 分区恢复时,会先上报 Controller 打开 P1 对应的 Meta Stream,根据 Meta Stream 从对象存储中拉取 P1 对应的元数据,从而恢复出 P1 相应的 checkpoint(Leader Epoch/SegmentList etc..),后根据 P1 的关闭状态(是否为 cleaned shutdown)进行对应的数据恢复。

代码部分对应 ElasticLog:Apply ꔷ 步骤一:Open Meta Stream

metaStream = if (metaNotExists) {
    val stream = createMetaStream(client, key, replicationFactor, leaderEpoch, logIdent = logIdent)
    info(s"${logIdent}created a new meta stream: stream_id=${stream.streamId()}")
    stream
} else {
    val metaStreamId = Unpooled.wrappedBuffer(value.get()).readLong()
    // open partition meta stream
    val stream = client.streamClient().openStream(metaStreamId, OpenStreamOptions.builder().epoch(leaderEpoch).build())
        .thenApply(stream => new MetaStream(stream, META_SCHEDULE_EXECUTOR, logIdent))
        .get()
    info(s"${logIdent}opened existing meta stream: stream_id=$metaStreamId")
    stream
}

ꔷ 步骤二:从 MetaStream 拉取 Partition MetaInfo、Producer Snapshot 等分区元信息

// load meta info for this partition
val partitionMetaOpt = metaMap.get(MetaStream.PARTITION_META_KEY).map(m => m.asInstanceOf[ElasticPartitionMeta])
if (partitionMetaOpt.isEmpty) {
    partitionMeta = new ElasticPartitionMeta(0, 0, 0)
    persistMeta(metaStream, MetaKeyValue.of(MetaStream.PARTITION_META_KEY, ElasticPartitionMeta.encode(partitionMeta)))
} else {
    partitionMeta = partitionMetaOpt.get
}
info(s"${logIdent}loaded partition meta: $partitionMeta")

//load producer snapshots for this partition
val producerSnapshotsMeta = metaMap.get(MetaStream.PRODUCER_SNAPSHOTS_META_KEY).map(m => m.asInstanceOf[ElasticPartitionProducerSnapshotsMeta]).getOrElse(new ElasticPartitionProducerSnapshotsMeta())
val snapshotsMap = new ConcurrentSkipListMap[java.lang.Long, ByteBuffer](producerSnapshotsMeta.getSnapshots)
if (!snapshotsMap.isEmpty) {
    info(s"${logIdent}loaded ${snapshotsMap.size} producer snapshots, offsets(filenames) are ${snapshotsMap.keySet()} ")
} else {
    info(s"${logIdent}loaded no producer snapshots")
}

// load leader epoch checkpoint
val leaderEpochCheckpointMetaOpt = metaMap.get(MetaStream.LEADER_EPOCH_CHECKPOINT_KEY).map(m => m.asInstanceOf[ElasticLeaderEpochCheckpointMeta])
val leaderEpochCheckpointMeta = if (leaderEpochCheckpointMetaOpt.isEmpty) {
    val newMeta = new ElasticLeaderEpochCheckpointMeta(LeaderEpochCheckpointFile.CURRENT_VERSION, List.empty[EpochEntry].asJava)
    // save right now.
    persistMeta(metaStream, MetaKeyValue.of(MetaStream.LEADER_EPOCH_CHECKPOINT_KEY, ByteBuffer.wrap(newMeta.encode())))
    newMeta
} else {
    leaderEpochCheckpointMetaOpt.get
}
info(s"${logIdent}loaded leader epoch checkpoint with ${leaderEpochCheckpointMeta.entries.size} entries")
if (!leaderEpochCheckpointMeta.entries.isEmpty) {
    val lastEntry = leaderEpochCheckpointMeta.entries.get(leaderEpochCheckpointMeta.entries.size - 1)
    info(s"${logIdent}last leaderEpoch entry is: $lastEntry")
}

ꔷ 步骤三:从 MetaStream 拉取 SegmentList 并恢复所有 Segment 状态:

val logMeta: ElasticLogMeta = metaMap.get(MetaStream.LOG_META_KEY).map(m => m.asInstanceOf[ElasticLogMeta]).getOrElse(new ElasticLogMeta())
logStreamManager = new ElasticLogStreamManager(logMeta.getStreamMap, client.streamClient(), replicationFactor, leaderEpoch)
val streamSliceManager = new ElasticStreamSliceManager(logStreamManager)

val logSegmentManager = new ElasticLogSegmentManager(metaStream, logStreamManager, logIdent)

// load LogSegments and recover log
val segments = new CachedLogSegments(topicPartition)
// 这里通过 ElasticLogLoader 恢复所有 elastic log segment 的状态
val offsets = new ElasticLogLoader(
    logMeta,
    segments,
    logSegmentManager,
    streamSliceManager,
    dir,
    topicPartition,
    config,
    time,
    hadCleanShutdown = partitionMeta.getCleanedShutdown,
    logStartOffsetCheckpoint = partitionMeta.getStartOffset,
    partitionMeta.getRecoverOffset,
    Optional.empty(),
    producerStateManager = producerStateManager,
    numRemainingSegments = numRemainingSegments,
    createAndSaveSegmentFunc = createAndSaveSegment(logSegmentManager, logIdent = logIdent)).load()
info(s"${logIdent}loaded log meta: $logMeta")

04 秒级分区迁移

1)高峰期快速扩容

Kafka 运维人员通常会根据历史经验准备 Kafka 集群容量,然而总会有一些非预期中的热门事件和活动导致集群流量陡增。这时候就需要快速的将集群扩容并重平衡分区,来应对突发流量。

在 Apache Kafka 中,由于存储和计算紧密耦合,集群扩容往往需要搬迁 Partition 数据,这个过程需要耗费大量的时间和资源,在高峰期则无法高效的完成扩容。

而在 AutoMQ 中,由于存储和计算分离,扩容过程则无需涉及数据的搬迁。这意味着在高峰期需要快速扩容时,AutoMQ 能够更加灵活地响应,减少了扩容过程的时间和对业务的影响。

AutoMQ 具备极强的弹性能力,能够在5分钟内完成支撑1GB流量的扩容流程:

2) Serverless 按需扩容

AutoMQ 架构的另一个优势在于其能够实现 Serverless 按需扩容。

在传统的架构中,扩容往往需要手动调整服务器的规模,或者预先分配一定的资源。然而,AutoMQ 的存算分离架构使得扩容过程变得更加灵活和自动化。由于存储和计算分离,可以结合容器 HPA、云厂商的弹性部署组,根据实际流量需求自动地调整计算资源,而无需考虑存储数据的搬迁问题。这使得系统能够更好地应对流量的波动,同时也降低了运维的复杂性和机器成本。

布道师计划

加入 AutoMQ 布道师,共创开源生态! 详情点击:2024 AutoMQ 布道师计划

*AutoMQ 在法律允许范围内,保留对本次活动的最终解释权

关于我们

我们是来自 Apache RocketMQ 和 Linux LVS 项目的核心团队,曾经见证并应对过消息队列基础设施在大型互联网公司和云计算公司的挑战。现在我们基于对象存储优先、存算分离、多云原生等技术理念,重新设计并实现了 Apache Kafka 和 Apache RocketMQ,带来高达 10 倍的成本优势和百倍的弹性效率提升。 🌟 GitHub 地址:https://github.com/AutoMQ/automq 💻 官网:https://www.automq.com 👀 B站:AutoMQ官方账号 🔍 视频号:AutoMQ

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

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

相关文章

【数据结构(邓俊辉)学习笔记】向量04——有序向量

文章目录 0.概述1.比较器2.有序性甄别3.唯一化3.1低效算法3.1.1实现3.1.2 复杂度3.1.3 改进思路3.2 高效算法3.2.1 实现3.2.2 复杂度 4.查找4.1统一接口4.2 语义定义4.3 二分查找4.3.1 原理4.3.2 实现4.3.3 复杂度4.3.4 查找长度4.3.5 不足 4.4 Fibonacci查找4.4.1 改进思路4.4…

AI-数学-高中-42导数的概念与意义

原作者视频&#xff1a;【导数】【一数辞典】1导数的概念与意义_哔哩哔哩_bilibili .a是加速度&#xff1b;

【Spring篇 | 补充】三级缓存解决循环依赖

文章目录 7.三级缓存解决循环依赖7.1何为循环依赖&#xff1f;7.2三级缓存解析7.3三级缓存解决循环依赖7.3.1实例化A7.3.2创建B的需求7.3.3实例化B7.3.4注入A到B7.3.5B创建完成7.3.6回溯至A7.3.7清理二级缓存 7.4为什么不能用二级缓存解决循环依赖&#xff1f; 7.三级缓存解决循…

【漏洞复现】通天星CMSV6车载监控平台ids SQL注入漏洞

漏洞描述&#xff1a; 通天星CMSV6车载定位监控平台拥有以位置服务、无线3G/4G视频传输、云存储服务为核心的研发团队&#xff0c;专注于为定位、无线视频终端产品提供平台服务&#xff0c;通天星CMSV6产品覆盖车载录像机、单兵录像机、网络监控摄像机、行驶记录仪等产品的视频…

微信小程序4~6章总结

目录 第四章 页面组件总结 4.1 组件的定义及属性 4.2 容器视图组件 4.2.1 view 4.2.2 scroll-view 4.2.3 swiper 4.3 基础内容组件 4.3.1 icon ​编辑 4.3.2 text 4.3.3 progress ​编辑 4.4 表单组件 4.4.1 button 4.4.2 radio 4.4.3 checkbox 4.4.4 switch …

网工学习云计算HCIE感受如何?

作为一名网工&#xff0c;我经常会在各种网络论坛里查询搜索一些网络技术资料&#xff0c;以及跟论坛里的网友交流讨论平时在工作、学习中遇到的问题、故障&#xff0c;因此也经常能在论坛的首页看到誉天的宣传信息。机缘巧合之下关注了誉天的B站号&#xff0c;自从关注了誉天的…

用 LMDeploy 高效部署 Llama-3-8B,1.8倍vLLM推理效率

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总…

嵌入式Linux driver开发实操(十八):Linux音频ALSA开发

应用程序程序员应该使用库API,而不是内核API。alsa库提供了内核API 100%的功能,但增加了可用性方面的主要改进,使应用程序代码更简单、更美观。未来的修复程序或兼容性代码可能会放在库代码中,而不是放在内核驱动程序中。 使用ALSA API和libasound进行简单的声音播放: /*…

Gartner发布攻击面管理创新洞察:CTEM、VA、EASM、CAASM、ASA、DRPS、BAS、VM等攻击面管理相关技术及关系

安全运营团队负责管理跨内部和外部数字资产的复杂攻击面。这项研究概述了攻击面评估空间&#xff0c;以帮助安全和风险管理领导者驾驭技术并改善其安全状况。 主要发现 随着本地和云中的技术环境变得越来越复杂和分散&#xff0c;组织必须管理不断增长的攻击面。 SaaS 应用程序…

wps/word中字体安装教程

问题&#xff1a;下载的字体怎么导入wps/word wps或word中没有相应字体&#xff0c;怎么导入。其实方法很简单。 Step 1&#xff1a;下载字体 首先&#xff0c;在网上搜索自己喜欢的字体&#xff0c;然后下载到本地。字体的格式通常是.ttf 下面是我网上找的字体&#xff08…

2024年度西安市创新联合体备案申报条件时间要求须知

一、申报条件 组建市级创新联合体需具备牵头单位、成员单位、组建协议、首席科学家等四个条件。 (一)牵头单位 1.牵头单位应为在西安市注册登记的省市产业链龙头骨干企业&#xff0c;重点支持市级重点产业链“链主”企业; 2.牵头单位一般为1家。 (二)成员单位 1.成员单位…

2024最新版JavaScript逆向爬虫教程-------基础篇之JavaScript密码学以及CryptoJS各种常用算法的实现

目录 一、密码学介绍1.1 为什么要学密码学?1.2 密码学里面学哪一些 二、字符编码三、位运算四、Hex 编码与 Base64 编码4.1 Hex 编码4.2 Base64 编码 五、消息摘要算法5.1 简介5.2 JS中的MD5、SHA、HMAC、SM3 六、对称加密算法6.1 介绍6.2 加密模式和填充方式6.3 CryptoJS 中D…

代理IP干货:如何正确使用防范风险?

在今天的数字时代&#xff0c;代理IP地址已成为互联网世界中不可或缺的一部分。无论您是寻求绕过地理限制、保护个人隐私还是执行网络任务&#xff0c;代理IP地址都发挥着关键作用。我们将为您探讨代理IP地址的重要性以及如何防范潜在的风险和威胁。 一、代理IP地址的潜在风险 …

CUDA编程技术概述

CUDA&#xff08;Compute Unified Device Architecture&#xff0c;统一计算设备架构&#xff09;是由英伟达&#xff08;NVIDIA&#xff09;公司推出的一种软硬件集成技术&#xff0c;是该公司对于GPGPU&#xff08;通用图形处理器计算&#xff09;的正式名称。透过这个技术&a…

微信小程序用户隐私协议保护指引自定义组件封装

这是一个微信小程序用户隐私协议保护指引自定义组件封装详细教程及代码。【建议收藏】 在做微信小程序有涉及表单提交&#xff0c;涉及用户信息收集时。提交代码会审核不过。 有需要了解到文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/framework/user-pr…

超分辨率遥感图像去云的扩散增强训练

GitHub - littlebeen/Cloud-removal-model-collection: A collection of the existing end-to-end cloud removal model readme 云恢复的扩散增强 基于ADM的超分辨率遥感图像去云扩散增强算法。 几种传统的CR模型可以参考https://github.com/littlebeen/Cloud-removal-model-co…

短链接推荐:一个可以监测用户行为的“营销神器”

客户对我的推广有兴趣吗&#xff1f;他喜欢我的产品吗&#xff1f;他打开了我的营销信息吗&#xff1f;这三个问题相信每一位推广者都遇到过。接下来&#xff0c;就将给大家介绍一位大聪明——它能帮你监测每一位用户的行为&#xff0c;让你分分秒秒掌握用户的心理&#xff01;…

深入了解Redis内存淘汰策略中的LRU算法应用

LRU算法简析 LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;算法是一种常见的内存淘汰策略&#xff0c;它根据数据的访问时间来决定哪些数据会被淘汰。LRU算法的核心思想是&#xff1a;最久未被访问的数据&#xff0c;被认为是最不常用的数据&#…

UE5 GAS开发P41-43 永久效果,去除永久效果,伤害区域,EnumClass,开始重叠与结束重叠事件

这一部分学习了怎么创建一个伤害性的地形(火焰地形,毒沼泽等都可以用这个方式创建) AuraEffectActor.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "GameplayEffect.h&q…

Linux驱动开发:掌握SPI通信机制

目录标题 1、SPI简介2、SPI通信机制3、Linux内核中的SPI支持4、SPI核心API5、SPI控制器驱动6、SPI设备驱动 7、编写SPI设备驱动8、调试SPI驱动 在Linux驱动开发中&#xff0c;串行外设接口(SPI)是一种常见的高速全双工通信协议&#xff0c;用于连接处理器和各种外设。本文将深入…