资讯详情

Apache Mesos 复制日志(Replicated Log)内部原理:Paxos 共识、Multi-Paxos 与自动复本恢复

📅 2026/10/13 1:51:29 | 华诺云谱 👁 阅读
Apache Mesos 复制日志(Replicated Log)内部原理:Paxos 共识、Multi-Paxos 与自动复本恢复
云原生集群管理容器编排任务调度【免费下载链接】mesosApache Mesos项目地址https://gitcode.com/gh_mirrors/meso/mesos点击查看免费下载本文基于 Apache Mesos 官方文档 replicated-log-internals.md并结合仓库源码深入展开。Mesos 复制日志是一个可复用的、具备容错能力的追加型日志库Mesos master 用它持久化集群注册表registry框架也可用它实现复制状态机模式。读完本文你将掌握复制日志的 Paxos 共识流程、Multi-Paxos 优化、本地读、截断与垃圾回收、自动复本恢复非投票复本、catch-up、自动初始化等内部机制并能在 src/log 与 include/mesos/log/log.hpp 中找到对应实现。什么是复制日志Replicated LogMesos 提供了一套库允许使用者创建复制的、容错的、追加型日志append-only log这套库被称为replicated log。它具有如下特性追加型存储日志中只能追加log entry日志条目每个条目可携带任意数据复制每个日志条目在系统中存在多份副本容错与高可用副本的存在既提供了故障容忍也提供了高可用性。Mesos master 使用这套库以复制、持久化的方式保存集群状态框架也可以使用它保存复制后的框架状态或实现经典的复制状态机模式。以运行在 Mesos 之上的 Apache Aurora 调度器为例可以看到典型的复制日志部署形态多个 Aurora 实例同时运行为了高可用其中一个被选举为 leader每个运行 Aurora 的主机上都有一个日志副本replicaAurora 通过一个薄库thin library即日志 API访问复制日志。通常只有 leader 会向日志追加数据。每个日志条目都会被复制并发送给系统中的所有副本。副本之间是强一致的——所有副本对每个日志条目的取值保持一致。正因为日志被复制当 Aurora 发生故障切换failover时它无需从远端主机拷贝日志本机副本即可恢复。典型使用场景Use Cases复制日志可以用来构建多种分布式应用Mesos 中就有两个现成的使用者Aurora用复制日志存储所有任务状态与作业配置Mesos master 的注册表registry通过复制日志存储集群中所有 agent 的信息。在 src/master/main.cpp 中可以看到 master 是如何接入复制日志的当--registryreplicated_log时master 会在工作目录--work_dir下创建replicated_log子目录存放日志文件并使用Log构造一个LogStorage作为注册表的持久化后端src/master/main.cpp#L352-L402。这印证了文档所述registry 复用复制日志的实现事实。用日志实现强一致的复制状态复制日志常用于让应用以强一致的方式管理复制状态。一种做法是把一次状态变更操作写进一条日志条目让分布式应用的所有实例对相同的初始状态如空状态达成一致复制日志保证每个应用实例按相同顺序观察到相同序列的日志条目只要状态变更操作的执行是确定性的所有实例就能始终彼此一致。如果某个实例崩溃它可以从初始状态开始按顺序重新应用所有已记录的变更从而重建当前版本的复制状态。当日志增长过大时应用可以写出快照snapshot然后删除快照之前的所有日志条目。Mesos 正是采用这种思路以复制日志为后端向使用者暴露分布式状态distributed state抽象。类似地复制日志还可用于构建复制状态机每条日志条目包含一条状态机命令由于副本强一致所有服务器会以相同顺序执行相同命令。实现原理基于 Paxos 的共识复制日志使用Paxos 共识算法确保所有副本对每条日志条目的取值达成一致参见 Paxos 相关论文。架构上可分为两类角色Coordinator协调者当用户要追加数据时系统创建一个日志写入者log writer写入者内部创建一个 coordinator。coordinator 联系所有副本并执行 Paxos 算法使所有副本对追加数据达成一致。coordinator 有时被称为proposer提议者。在源码中对应 Coordinator / CoordinatorProcessReplica副本每个副本维护一个日志条目数组数组下标即日志位置log position。每个日志条目由三部分组成用户写入的值关联的 Paxos 状态learned 位布尔值为 true 表示该条目的值已被达成共识。因此本实现中每个副本既是acceptor接受者又是learner学习者。副本在源码中对应 Replica / ReplicaProcess。这些角色在协议消息层面有清晰的定义见 src/messages/log.protoAction日志动作携带position、promised、performed、learned字段类型包括NOP、APPEND、TRUNCATE三种PromiseRequest/PromiseResponse、WriteRequest/WriteResponse、RecoverRequest/RecoverResponse、LearnedMessage定义了副本间的全部通信消息响应的type分为ACCEPT接受、REJECT拒绝即代码中的 NACK、IGNORED忽略例如副本不在 VOTING 状态时。副本的本地存储结构ReplicaProcess 是副本的运行时实现内部维护了begin日志的起始位置已学习到被截断的位置之后end日志的结束位置最后写入的位置holes日志中的洞空洞即从未写入过的位置区间unlearned尚未被学习未达成共识的位置集合metadata副本元数据当前状态 最近做出的隐式承诺的提案号。missing(position)判断某个位置是否缺失未学习或空洞read(from, to)读取区间内所有动作读取被截断的位置会返回错误读取尚未写入的位置返回空src/log/replica.cpp#L185-L282。底层持久化由Storage接口提供默认实现是LevelDBLevelDBStorage。对单个日志条目达成共识一轮 Paxos 可以让所有副本对单个日志条目的值达成共识包含两个阶段promise 阶段和write 阶段。需要说明的是本实现的术语与原始 Paxos 论文略有不同原论文的prepare与accept阶段在此分别称为promise与write阶段相应地prepare 请求响应称为 promise 请求响应accept 请求响应称为 write 请求响应。Promise 阶段对应 log::promise要在位置p追加值Xcoordinator 先向所有副本广播携带提案号n的 promise 请求要求副本承诺不会响应任何提案号低于n的请求包括 promise 请求与 write 请求。这里假定n高于此前使用过的任何提案号如何做到见下文唯一提案号小节。副本收到 promise 请求后检查自己的 Paxos 状态根据此前给出的承诺决定能否安全响应若能给出承诺通过提案号检查先将承诺提案号n持久化到磁盘然后回复 promise 响应若该副本此前已被写入即接受过某个 write 请求则必须把先前写入的值及那次 write 请求使用的提案号一并放进即将发出的 promise 响应中。这一点在 ReplicaProcess::promise 中有完整实现副本会返回该位置已有的 actionresponse.mutable_action()-MergeFrom(original)供 coordinator 判断。Write 阶段对应 log::writecoordinator 收到**法定人数quorum**的 promise 响应后先检查其中是否存在先前写入的值若发现存在先前的写入值追加操作不能继续——因为该日志条目很可能已经有值被达成共识了。这是 Paxos 的关键思想之一限制可以被写入的值以保证一致性。在实现中若 promise 阶段返回了已执行的 actionhas_actioncoordinator 会沿用该值见 FillProcess::checkPromisePhase 的说明若未发现先前写入的值coordinator 向所有副本广播携带值X与提案号n的 write 请求副本收到 write 请求后再次检查自己做出的承诺若 write 请求的提案号大于或等于其承诺过的提案号则回复 write 响应coordinator 收到法定人数的 write 响应后追加操作即成功。以上流程对应 consensus.hpp 中的注释promise 阶段有两个目的——其一向法定人数的副本索取不接受更低提案号写入的承诺其二寻找潜在已达成共识的值只有这些值可以在下一阶段被写入。写入成功后coordinator 还会广播learned 消息对应 log::learn通知所有副本该位置已达成共识这是一个优化而非必需的阶段。用 Multi-Paxos 优化追加延迟实现复制日志的一个朴素方案是为每个日志条目都运行完整的一轮 Paxospromise 阶段 write 阶段。但正如原始 Paxos 论文所述如果 leader 相对稳定可以使用Multi-Paxos消除大多数追加操作的 promise 阶段从而显著提升性能。为此本实现引入了一种新的 promise 请求——隐式 promise 请求implicit promise request它可被看作对**潜在无限一组日志条目**的批处理promise 请求广播一个隐式 promise 请求概念上等价于对每一个值尚未达成共识的日志条目广播 promise 请求如果 coordinator 广播的隐式 promise 请求被法定人数的副本接受该 coordinator不再需要为值尚未达成共识的日志条目运行 promise 阶段——因为 promise 阶段已经以批处理方式完成此时的 coordinator 被称为已当选elected即 leader它对复制日志拥有独占访问权当选的 coordinator 可能被降级demoted如果另一个 coordinator 以更高的提案号广播了隐式 promise 请求原 coordinator 就失去独占访问权。关键问题随之而来如何找出值尚未达成共识的日志条目本实现的方案非常简洁如果一个副本接受了隐式 promise 请求它会在响应中包含自己已知的最大日志位置。当选的 coordinator 只会在大于p的位置追加日志条目其中p大于这些响应中出现的所有日志位置。在代码中ImplicitPromiseProcess 收集响应并取highestEndPosition响应中出现过的最高位置返回CoordinatorProcess::elect 随后用index response.position()记录当前写入位置并将其作为后续追加的起点。Multi-Paxos 只有在 leader 稳定时才有更好的性能。复制日志本身不进行 leader 选举而是依赖使用者自行选择稳定的 leader——例如 Aurora 使用 ZooKeeper 选举 leader。在 Mesos master 中HA 模式下由 MasterContender / MasterDetector 完成领导权争夺当选的 master 再以 coordinator 身份独占写复制日志。本地读取Local Reads如前所述每个副本同时是 acceptor 和 learner。将副本视为 learner 使得无需与其他副本交互即可完成本地读取当一个日志条目的值达成共识后coordinator 会向所有副本广播learned消息副本收到 learned 消息后将该日志条目对应的 learned 位设置为 true表示该条目的值已被共识我们说一个日志条目被学习learned当且仅当其 learned 位被设置coordinator不必等待副本的确认log::learn 的返回值在消息广播完成时即就绪。执行读取时日志读取者log reader直接查询底层本地副本若对应日志条目已 learned直接向用户返回值否则需要运行完整的一轮 Paxos来发现已共识的值。本实现还保证与当选 coordinator 位于同一主机的副本总是让所有日志条目处于 learned 状态。实现方式是coordinator 当选后对所有未学习的位置运行完整 Paxos 轮次来填充见 CoordinatorProcess::checkPromisePhase 中的getMissingPositions与catchupMissingPositions。这样 coordinator 便能执行最新的本地读取。在代码层面LogReaderProcess::__read 会校验读取范围内的每个 action 都已performed且learned否则返回读取范围包含未决条目的错误并且只向用户返回APPEND类型的条目。通过垃圾回收减小日志体积当日志增长过大时应用可以选择截断truncate日志。截断的做法是追加一条特殊的日志条目其值就是用户希望截断到的日志位置。一旦这条特殊条目被 learned副本就可以真正截断日志。在实现中CoordinatorProcess::truncate(to)构造一条Action::TRUNCATE其to字段表示截断到该位置之前不包含该位置src/log/coordinator.cpp#L326-L343。副本持久化一条 learned 的 TRUNCATE 动作后会将[0, to)区间从holes和unlearned中移除更新begin max(begin, truncate().to())src/log/replica.cpp#L719-L750。此外日志条目的Nop消息还包含一个tombstone标志src/messages/log.proto#L57-L59用于标记某个位置已被截断——这为下文非领导 VOTING 副本 catch-up中的一致性保护提供了基础。唯一提案号Unique Proposal Number许多 Paxos 研究论文假定每个提案号在全局唯一且 coordinator 总能提出一个比系统中任何提案号都大的提案号。然而在分布式环境中实现这一点并不容易。有人建议把全局唯一的服务器 id 拼接进每个提案号但如何为每台服务器生成全局唯一 id 仍然是个难题。本实现不做上述假设方案是coordinator最初可以使用任意提案号在 promise 阶段如果某个副本知道一个比 coordinator 所用提案号更高的提案号它会把已知的最大提案号回传给 coordinatorcoordinator 将使用更高的提案号重试 promise 阶段。为避免活锁livelock例如两个 coordinator 相互竞争时本实现在每次重试前注入一个T 到 2T 之间的随机延迟。T的选择需要权衡一方面希望T 广播时间使某个 coordinator 通常先超时并获胜然后其他 coordinator 才苏醒另一方面希望T尽量小以缩短等待时间。目前实现使用T 100ms。这一思路借鉴自 Raft。代码位于 FillProcess::retrystatic const Duration T Milliseconds(100);延迟为T * (1.0 (double) os::random() / RAND_MAX)即随机落在[T, 2T)区间。类似地RecoverProcess::finished 在恢复协议重试时使用T Milliseconds(500)的随机退避以避免网络/磁盘 IO 饱和。自动复本恢复Automatic Replica Recovery上述算法有一个关键漏洞如果一个副本丢失了持久化状态例如磁盘故障或运维失误导致日志文件丢失简单地重启并重新加入副本组可能引发日志不一致。出现这种情况时传统做法是在所有主机上停止应用从 leader 所在主机拷贝日志文件然后重启应用。注意运维人员不能从任意副本拷贝日志文件——因为拷贝一个**未学习unlearned**的日志条目可能错误地为一个错误的值凑齐法定人数从而导致不一致。为避免这种需要人工干预的场景Mesos 复制日志内置了**自动恢复auto recovery**支持只要仍有法定人数的副本正常工作应用的用户就不会察觉任何差异。非投票副本Non-Voting Replicas实现自动恢复的关键洞察是丢失持久化状态的副本在重启后不应被允许响应 coordinator 的请求。否则它可能接受一个如果其先前的 Paxos 状态未丢失就不会接受的 promise/write 请求从而在日志中引入不一致。为此每个副本引入一个状态变量VOTING普通副本处于 VOTING 状态允许响应 coordinator 的请求EMPTY没有持久化状态的副本默认处于 EMPTY 状态不允许响应 coordinator 的任何请求。这两个状态与RECOVERING正在追赶中、STARTING日志已初始化一起定义在 src/messages/log.proto#L81-L91 的Metadata.Status枚举中默认状态为EMPTY。一个处于 EMPTY 状态的副本在满足以下两个条件时会被提升为 VOTING 状态恢复了足够数量的缺失日志条目使得即便其他副本发生故障剩余副本也能恢复所有已 learned 的日志条目它未来的响应不会破坏它曾经可能已丢失做出的任何承诺。副本协议的处理逻辑中可以看到这两个条件的体现ReplicaProcess::promise与ReplicaProcess::write在副本不是 VOTING 状态时会直接返回IGNORED响应src/log/replica.cpp#L374-L388、src/log/replica.cpp#L524-L539即不允许投票而 recover.hpp 的注释解释了条件 1 与条件 2 的语义。Catch-up追赶为满足上述两个条件副本需要执行catch-up来恢复丢失的状态——即运行 Paxos 轮次找出那些值已被共识的日志条目。问题是本地副本要追赶多少条目才足以满足上述两个条件研究发现追赶从位置begin到位置end的日志条目即足够其中begin是在一个法定人数的 VOTING 副本中所见到的最小位置end是在一个法定人数的 VOTING 副本中所见到的最大位置。正确性论证如下对大于end的位置e显然还没有值被共识。否则我们应该能在法定人数的副本中找到至少一个 VOTING 副本其 end 位置大于end。同理coordinator 也不应为位置e收集到足够的承诺。因此恢复中的副本响应针对该位置的请求是安全的对小于begin的位置b它应该已经被截断且截断操作应该已经被共识。因此允许恢复中的副本响应针对该位置的请求也是安全的。在代码中RecoverProcess::catchup 从恢复协议得到的begin/endVOTING 副本中的最低 begin 与最高 end构造IntervalSet并调用log::catchup执行追赶。追赶的核心循环在 CatchUpProcess对每个缺失位置调用log::fill运行完整 Paxos 轮次promise → write → learn进行填充若没有找到先前已共识的值就提议一个NOP动作。这也解释了为何日志中出现大量 NOP 条目——它们通常是追赶/选举时填充空洞产生的。自动初始化Auto Initialization由于不允许空副本EMPTY 状态的副本响应 coordinator 的请求就引出了**引导bootstrapping**问题——因为初始时每个副本都是空的。复制日志提供了两种选择选择一手动初始化。使用mesos-log工具在每个副本上显式初始化日志将副本状态设置为 VOTING但这要求应用在搭建时多做一步。选择二自动初始化。思路是如果一个 EMPTY 状态的副本发现所有副本都处于 EMPTY 状态就允许它立即变为 VOTING。其依据是所有副本都处于 EMPTY 状态只会发生在启动阶段。但如果灾难性故障导致所有副本都丢失了持久化状态这一假设可能不再成立——这正是允许保守用户禁用自动初始化的原因。自动初始化的一个实现难点在于如果使用单阶段协议允许副本直接从 EMPTY 状态转变为 VOTING 状态即使所有副本初始时都处于 EMPTY 状态也可能进入无法推进的僵局。例如假设 quorum 大小为 2所有副本初始均为 EMPTY。某个副本发现所有副本都是 EMPTY 后先将自己置为 VOTING。此后那个 VOTING 副本和其余 EMPTY 副本都无法继续推进。为解决此问题本实现采用两阶段协议在 EMPTY 与 VOTING 之间引入一个中间过渡状态 STARTINGEMPTY 状态的副本如果发现所有副本都处于EMPTY 或 STARTING状态可以转入 STARTINGSTARTING 状态的副本如果发现所有副本都处于STARTING 或 VOTING状态可以转入 VOTING。这样一来在上面的例子中所有副本会先全部进入 STARTING 状态之后任何副本才能转入 VOTING 状态。恢复协议的具体规则记录在 RecoverProtocolProcess 的注释中对应 B1~B4 四种分支B1若发现一个法定人数的副本处于 VOTING 状态无论本地副本当前状态如何本地副本进入 RECOVERING 状态随后执行 catch-upB2若本地副本处于 EMPTY 状态且所有副本都处于 EMPTY 或 STARTING 状态本地副本进入 STARTINGB3若本地副本处于 STARTING 状态且所有副本都处于 STARTING 或 VOTING 状态本地副本进入 VOTINGB4否则重跑恢复协议。其中所有副本即2 * quorum - 1个副本见 src/log/recover.cpp#L238 的 cluster size 计算。当自动初始化被禁用时若发现所有副本均处于 EMPTY代码会打印提醒信息提示用户在每个主机上执行mesos-log initialize --pathPATH手动初始化src/log/recover.cpp#L295-L308。非领导 VOTING 副本的 Catch-upMesos 1.5.0从Mesos 1.5.0起可以从非领导non-leading的 VOTING 日志副本进行最终一致eventually consistent的读取。这使得可以在非领导的框架副本上做额外工作例如把一部分读取从 leader 卸载到 standby通过保持由复制日志支撑的内存存储热hot缩短故障切换时间。要提供最终一致的读取副本需要执行catch-up恢复最新的日志状态方式与 EMPTY 副本恢复 中的做法类似。追赶之后恢复出来的位置就可以无惧空洞holes地进行回放。一个需要注意的边界情况在非领导副本追赶期间可能发生截断。如果副本已经恢复了begin和end位置之后截断才发生副本可能会试图填充被截断的位置这会导致日志回放时产生不一致的数据。为防御这一点实现使用了一个特殊的tombstone墓碑标志它向副本发出信号——该位置已被截断begin需要相应调整参见 src/messages/log.proto#L57-L59 中Nop.tombstone字段以及 src/log/replica.cpp#L735-L750 对 tombstone NOP 的处理。副本在追赶期间或之后并不会被阻止发生截断这意味着如果已恢复的位置在日志回放期间被截断用户可能需要重试 catch-up 过程。在 API 层面Log::Reader提供了 catchup() 方法对应 LogReaderProcess::catchup返回追赶范围的结束位置。复制日志的公共 API 与运维工具公共 APIinclude/mesos/log/log.hpp复制日志对外暴露在 include/mesos/log/log.hpp 中Log复制日志入口。两种构造方式Log(quorum, path, pids, autoInitialize, metricsPrefix)通过一组进程 PIDprocess::UPID集合与其他副本协调Log(quorum, path, servers, timeout, znode, auth, autoInitialize, metricsPrefix)通过ZooKeeper发现与协调副本对应ZooKeeperNetwork。Log::Position日志位置抽象提供identity()序列化方法与全套比较运算符Log::Entrypositiondata二元组Log::Reader提供read(from, to)、beginning()、ending()、catchup()Log::Writer提供start()发起选举、获得独占写权、append(data)、truncate(to)。若append/truncate返回None说明该 writer 已失去独占写权必须重新start()。Writer 的调用关系与文档中的写入者内部创建 coordinator一一对应LogWriterProcess::_start中每次start都会新建一个Coordinator并调用coordinator-elect()src/log/log.cpp#L542-L573。mesos-log 命令行工具src/log/main.cpp 注册了四个子命令mesos-log command [OPTIONS]initialize手动初始化日志把副本状态置为 VOTING参数--path、--timeout见 src/log/tool/initialize.hppread读取日志参数--path、--from、--to、--timeout见 src/log/tool/read.hppreplica启动一个副本服务器参数--quorum、--path、--servers、--znode、--initialize见 src/log/tool/replica.hppbenchmark性能基准测试见 src/log/tool/benchmark.hpp。与 Mesos master 的集成参数master 使用复制日志作为 registry 后端时相关参数定义在 src/master/flags.cpp--registry持久化策略可用选项为replicated_log与in_memory仅用于测试默认replicated_log--quorum使用replicated_log型 registry 时的副本法定人数必须设置为 master 的多数即quorum (number of masters)/2standalone非 HA模式下无需指定--work_dirmaster 工作目录复制日志文件存放于其下的replicated_log子目录--log_auto_initialize是否自动初始化 registry 所用的复制日志若为 false 则首次使用时必须手动初始化默认true对应 src/master/main.cpp#L391 中传给Log构造函数的flags.log_auto_initialize--registry_fetch_timeout默认 60 秒与--registry_store_timeout默认 20 秒从 registry 读取/写入数据的超时上限。在 ZooKeeper 模式下master 会把各副本的 PID 注册到 ZooKeeper 的log_replicasznode 下src/master/main.cpp#L384-L392ZooKeeperNetwork持续监听该 znode 以动态维护副本集合src/log/network.hpp#L109-L146。运行期指标复制日志暴露两个 pull-gauge 指标src/log/metrics.hpprecovered日志是否已完成恢复1 或 0ensemble_size副本网络replica set的规模。在 master 集成中这两个指标以registrar/为前缀注册src/master/main.cpp#L392。未来工作目前复制日志不支持动态调整法定人数大小即所谓的重配置reconfiguration。支持重配置将使得更轻松地添加、移动或替换副本主机。Mesos 计划在未来支持重配置。从代码注释也可以看到相关迹象Network本身支持动态增删 PIDadd/remove/setsrc/log/network.hpp#L68-L78但 Paxos 组的 quorum 仍是构造时固定的常量。源码阅读地图文档docs/replicated-log-internals.md本文的原始依据公共 APIinclude/mesos/log/log.hpp日志进程Log/Reader/Writersrc/log/log.cpp、src/log/log.hpp协调者coordinatorsrc/log/coordinator.cpp、src/log/coordinator.hpp共识协议promise/write/learn/fillsrc/log/consensus.cpp、src/log/consensus.hpp副本replicasrc/log/replica.cpp、src/log/replica.hpp恢复协议与自动初始化src/log/recover.cpp、src/log/recover.hpp追赶catch-upsrc/log/catchup.cpp、src/log/catchup.hpp副本发现Network / ZooKeeperNetworksrc/log/network.hpp持久化存储LevelDBsrc/log/leveldb.hpp协议消息定义src/messages/log.proto运维工具 mesos-logsrc/log/main.cpp、src/log/toolmaster 集成src/master/main.cpp、src/master/flags.cpp赞分享云原生集群管理容器编排任务调度【免费下载链接】mesosApache Mesos项目地址https://gitcode.com/gh_mirrors/meso/mesos点击查看免费下载相关推荐《Paxos Made Simple》深度解读从两阶段共识算法到 Multi-Paxos 状态机复制《Paxos Made Simple》深度解读从两阶段共识算法到 Multi Paxos 状态机复制 本文以 translations 仓库中的中文译文 pa文档教程知识库ngxtop日志解析错误恢复机制自动恢复解析状态ngxtop日志解析错误恢复机制自动恢复解析状态 ngxtop是一款强大的 实时Nginx服务器监控工具 能够实时解析Nginx访问日志并输出类似 top运维可观测性CLIApache Cassandra 轻量级事务LWT与 Paxos 共识算法深度解析Apache Cassandra 轻量级事务LWT与 Paxos 共识算法深度解析 导读 本文以 Apache Cassandra 官方仓库中的 Paxos数据库分布式数据库后端上一篇Falcon-OCR技术架构揭秘早期融合视觉语言模型的创新设计下一篇解决Langchain-Chatchat 0.3.0版本DOCX文件导入问题的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑