浅谈分布式存储之 Raft

芋道源码解读之多租户 租户(Tenant)是系统中的一个逻辑隔离的单元,代表一个独立使用系统的组织(如企业、高校等),在多租户系统中,不同租户共享相同的应用程序和基础设施,但各自拥有独立的数据、配置、组织架构及用户等。 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:shimingyah.github.io/2020

/03/浅谈分布式存储之raft/

25626529f49e1002d286e1204cd7e510.jpeg


分布式一致性是分布式系统中最基本的问题,用来保证分布式系统的高可靠。业界也有很多分布式一致性复制协议:Paxos、Zab、Viewstamped Replication、Raft 等。Raft 相比于其他共识算法简化了协议中的状态以及交互,更加清晰也更加容易理解实现。

1. Raft 概述

Raft 节点有 3 种角色:

  • Leader :处理客户端读写、复制 Log 给 Follower 等;

  • Candidate :竞选新的 Leader(由 Follower 超时转换得来);

  • Follower :不发送任何请求,完全被动响应 Leader、Candidate 的 RPC。

Raft 信息有 3 种 RPC:

  • RequestVote RPC :由 Candidate 发出,用于发送投票请求;

  • AppendEntries (Heartbeat) RPC :由 Leader 发出,用于 Leader 向 Followers 复制日志条目,也会用作 Heartbea(日志条目为空即为 Heartbeat);

  • InstallSnapshot RPC :由 Leader 发出,用于快照传输。虽然多数情况都是每个服务器独立创建快照,但是 Leader 有时候必须发送快照给一些落后太多的 Follower,这通常发生在 Leader 已经丢弃了下一条要发给该 Follower 的日志条目(Log 压缩时清除掉了的情况)。

1.1 Leader 选举

Raft 将时间划分为一个个的任期(Term),TermID 单调递增,每个 Term 最多只有一个 Leader。

3eb91cac7cf8111e695a669d40364338.png

Candidate 先将本地的 currentTerm++,然后向其他节点发送 RequestVote 请求。其他节点根据本地数据版本、长度和之前选主的结果判断应答成功与否。具体处理规则如下:

  • 如果 Time.Now() – lastLeaderUpdateTimestamp < electionTimeout,忽略请求;

  • 如果 req.term < currentTerm,忽略请求;

  • 如果 req.term > currentTerm,设置 currentTerm = req.term。如果是 Leader 和 Candidate 转为 Follower;

  • 如果 req.term == currentTerm,并且本地 voteFor 记录为空或者是与 vote 请求中 term 和 CandidateId 一致,req.lastLogIndex > lastLogIndex,即 Candidate 数据新于本地则同意选主请求;

  • 如果 req.term == currentTerm,如果本地 voteFor 记录非空或者是与 vote 请求中 term 一致 CandidateId 不一致,则拒绝选主请求;

  • 如果 req.term == currentTerm,如果 lastLogTerm > req.lastLogTerm,本地最后一条 Log 的 Term 大于请求中的 lastLogTerm,说明 candidate上数据比本地旧,拒绝选主请求。

currentTerm 只是用于忽略老的 Term 的 vote 请求,或者提升自己的 currentTerm,并不参与 Log 新旧的决策。Log 新旧的比较,是基于 lastLogTerm 和 lastLogIndex 进行比较,而不是基于 currentTerm 和 lastLogIndex 进行比较。

关于选举有两个很重要的随机超时时间:心跳超时、选举超时。

  • 心跳超时 :Leader 周期性的向 Follower 发送心跳(0.5ms – 20ms)。如果 Follower 在选举超时时间内没有收到心跳,则触发选举;

  • 选举超时 :如果存在两个或者多个节点选主,都没有拿到大多数节点的应答,需要重新选举。Raft 引入随机的选举超时时间(150ms – 300ms),避免选主活锁。

心跳超时要小于选举超时一个量级,Leader 才能够发送稳定的心跳消息来阻止 Follower 开始进入选举状态。可以设置:心跳超时=peers max RTT(round-trip time),选举超时=10 * 心跳超时。

1.2 Log 复制

大致流程是:更新操作通过 Leade r写入 Log,复制到多数节点,变为 Committed,再 Apply 业务状态机。

  1. Leader 首先要把这个指令追加到 log 中形成一个新的 entry;

  2. 然后通过 AppendEntries RPC 并行地把该 entry 发给其他 servers;

  3. 其他 server 如果发现没问题,复制成功后会给 Leader 一个表示成功的 ACK;

  4. Leader 收到大多数 ACK 后 Apply 该日志,返回客户端执行结果。

如果 Followers crash 或者丢包,Leader 会不断重试 AppendEntries RPC。

8a0a0143e2ad0b236a5071bfee3f4f75.png
  • Raft 要求所有的日志不允许出现空洞;

  • Raft 的日志都是顺序提交的,不允许乱序提交;

  • Leader 不会覆盖和删除自己的日志,只会 Append;

  • Follower 可能会截断自己的日志。存在脏数据的情况;

  • Committed 的日志最终肯定会被 Apply;

  • Snapshot 中的数据一定是 Applied,那么肯定是 Committed 的;

  • commitIndex、lastApplied 不会被所有节点持久化;

  • Leader 通过提交一条 Noop 日志来确定 commitIndex;

  • 每个节点重启之后,先加载上一个 Snapshot,再加入 RAFT 复制组。

789f3f770136f3a054170048196addd6.png

每个 log entry 都存储着一条用于状态机的指令,同时保存着从 Leader 收到该 entry 时的 Term,此外还有 index 指明自己在 Log 中的位置。可以被 Apply 的 entry 叫做 committed,一个 log entry 一旦复制给了大多数节点就成为 committed,committed 的 log 最终肯定会被 Apply。

如果当前待提交 entry 之前有未提交的 entry,即使是以前过时的 leader 创建的,只要满足已存储在大多数节点上就一次性按顺序都提交。

1.3 Log 恢复

Log Recovery 分为 currentTerm 修复和 prevTerm 修复。Log Recovery 就是要保证一定已经 Committed 的数据不会丢失,未 Committed 的数据转变为 Committed,但不会因为修复过程中断又重启而影响节点之间一致性。

currentTerm 修复主要是解决某些 Follower 节点重启加入集群,或者是新增 Follower 节点加入集群,Leader 需要向 Follower 节点传输漏掉的 Log Entry。如果 Follower 需要的 Log Entry 已经在 Leader上Log Compaction 清除掉了,Leader 需要将上一个 Snapshot 和其后的 Log Entry 传输给 Follower 节点。Leader-Alive 模式下,只要 Leader 将某一条 Log Entry 复制到多数节点上,Log Entry 就转变为 Committed。

prevTerm 修复主要是在保证Leader切换前后数据的一致性。通过上面 RAFT 的选主可以看出,每次选举出来的 Leader 一定包含已经 committed 的数据(抽屉原理,选举出来的 Leader 是多数中数据最新的,一定包含已经在多数节点上 commit 的数据)。新的 Leader 将会覆盖其他节点上不一致的数据。虽然新选举出来的 Leader 一定包括上一个 Term 的 Leader 已经 Committed 的 Log Entry,但是可能也包含上一个 Term 的 Leader 未 Committed 的 Log Entry。这部分 Log Entry 需要转变为 Committed,即通过 Noop。

Leader 为每个 Follower 维护一个 nextId,标识下一个要发送的 logIndex。Leader 通过回溯寻找 Follower 上最后一个 CommittedId,然后 Leader 发送其后的 LogEntry。

重新选取 Leader 之后,新的 Leader 没有之前内存中维护的 nextId,以本地 lastLogIndex+1 作为每个节点的 nextId。这样根据节点的 AppendEntries 应答可以调整 nextId:

local.nextIndex = max(min(local.nextIndex-1, resp.LastLogIndex+1), 1)

1.4 Log 压缩

在实际系统中,Log 会无限制增长,导致 Log 占用太多的磁盘空间,需要更长的启动时间来加载,将会导致系统不可用。需要对日志做压缩。

Snapshot 是 Log Compaction 的常用方法,将系统的全部状态写入一个 Snapshot 中,并持久化到一个可靠存储系统中,完成 Snapshot 之后这个点之前的 Log 就可以被删除了。

  • Leader、Follower 独立地创建快照;

  • Follower 与 Leader 差距过大,则 InstallSnapshot,Leader chunk 发送 Snapshot 给 Follower;

  • Snapshot 中的数据一定是 Applied,那么肯定是 Committed 的;

  • Log 达到一定大小、数量、超过一定时间可以做 Snapshot。;

  • 如果底层存储支持 COW,则可以使用 COW 做 Snapshot,减小对 Log Append 的影响。

1.5 成员变更

当 Raft 集群进行节点变更时,新加入的节点可能会因为需要花费很长时间同步 Log 而降低集群的可用性,导致集群无法 commit 新的请求。

假设原来集群有 3 个节点,可以容忍 3 - (3/2+1) = 11 个节点出错,这时由于机器维修、增加副本解决热点读等原因又新加入了一个节点,这时也是可以容忍 4 - (4/2+1) = 11 个节点出错,恰好原来的一个节点出错了,此时虽然可以 commit 但是得等到新的节点完全追上 Leader 的日志才可以,而新节点追上 Leader 日志花费的时间比较长,在这期间就没法 commit,会降低系统的可用性。

为了避免这个问题,引入了节点的 Learner 状态,当集群成员变更时,新加入的节点为 Learner 状态,Learner 状态的节点不算在 Quorum 节点内,不能参与投票;只有 Leader 确定这个 Learner 节点接收完了 Snapshot,可以正常同步 Log 了,才可能将其变成可以正常的节点。

1.6 安全性

  • Election Safety :一个 Term 下最多只有一个 Leader;

  • Leader Append-Only :Leader 不会覆盖或者是删除自己的 Entry,只会进行 Append;

  • Log Matching :如果两个 Log 拥有相同的 Term 和 Index,那么给定 Index 之前的 LogEntry 都是相同的;

  • Leader Completeness :如果一条 LogEntry 在某个 Term 下被 Commit 了,那么这条 LogEntry 必然存在于后面 Term 的 Leader 中;

  • State Machine Safety :如果一个节点已经 Apply 了一条 LogEntry 到状态机,那么其他节点不会向状态机中 Apply 相同 Index 下的不同的 LogEntry。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

2. 功能完善

2.1 预选举

预选举(Pre-Vote)主要避免了网络分区节点加入集群时,引起集群中断服务的问题。

Follower 在转变为 Candidate 之前,先与集群节点通信,获得集群 Leader 是否存活的信息。如果当前集群有 Leader 存活,Follower 就不会转变为 Candidate,也不会增加 Term,就不会引起 Leader StepDown,从而不会导致集群选主中断服务。

2.2 Leader 转移

Leader 转移可以把当前 Raft Group 中的 Leader 转换给另一个 Follower,可用于负载均衡、重启机器等。

在进行 transfer leadership 时,先 block 当前 Leader 的写入,然后使 Transferee 节点日志达到 Leader 的最新状态,进而发送 TimeoutNow 请求,触发 Transferee 节点立即选主。

但是不能无限制的 block Leader 的写入,会影响线上服务。通常可以为 transfer leadership 设置一个超时时间。超时之后如果发现 Transferee 节点 Term 没有发生变化,说明 Transferee 节点没有追上数据,没有选主成功,transfer leadership 就失败了。

2.3 网络分区

网络分区主要包含对称网络分区(Symmetric network partitioning)和非对称网络分区(Asymmetric network partitioning)。

对称网络分区

c34f00d3f79c1e183d46d23ee729f608.png

S1 为当前 Leader,网络分区造成 S2 和 S1、S3 心跳中断。S2 既不会被选成 Leader,也不会收到 Leader 的消息,而是会一直不断地发起选举。Term 会不断增大。为了避免网络恢复后,S2 发起选举导致正在工作的 Leader step-down,从而导致整个集群重新发起选举,可以使用 pre-vote 来阻止对称网络分区节点在重新加入时,会中断集群的问题。因为发生对称网络分区后的节点,pre-vote 不会成功,也就不会导致集群一段时间内无法正常提供服务的问题。

非对称网络分区

fe38f8631b26503e3f4535865533af86.png

S1、S2、S3 分别位于三个 IDC,其中 S1 和 S2 之间网络不通,其他之间可以联通。这样一旦 S1 或者是 S2 抢到了 Leader,另外一方在超时之后就会触发选主,例如 S1 为 Leader,S2 不断超时触发选主,S3 提升 Term 打断当前 Lease,从而拒绝 Leader 的更新。

可以增加一个 trick 的检查,每个 Follower 维护一个时间戳记录收到 Leader 上数据更新的时间,只有超过 ElectionTimeout 之后才允许接受 Vote 请求。这个类似 ZooKeeper 中只有 Candidate 才能发起和接受投票,就可以保证 S1 和 S3 能够一直维持稳定的 quorum 集合,S2 不能选主成功。

2.4 SetPeer

Raft 只能在多数节点存活的情况下才可以正常工作,在实际环境中可能会存在多数节点故障只存活一个节点的情况,这个时候需要提供服务并修复数据。因为已经不能达到多数,不能写入数据,也不能做正常的节点变更。Raft 库需要提供一个 SetPeer 的接口,设置每个节点的复制组节点列表,便于故障恢复。

假设只有一个节点 S1 存活的情况下,SetPeer 设置节点列表为 {S1},这样形成一个只有 S1 的节点列表,让 S1 继续提供读写服务,后续再调度其他节点进行 AddPeer。通过强制修改节点列表,可以实现最大可用模式。

2.5 Noop

在分布式系统中,对于一个请求都有三种返回结果:成功、失败、超时。

在 failover 时,新的 Leader 由于没有持久化 commitIndex,所以并不清楚当前日志的 commitIndex 在哪,也即不清楚 log entry 是 committed 还是 uncommitted 状态。通常在成为新 Leader 时提交一条空的 log entry 来提交之前所有的 entry。

RAFT 中增加了一个约束:对于之前 Term 的未 Committed 数据,修复到多数节点,且在新的 Term 下至少有一条新的 Log Entry 被复制或修复到多数节点之后,才能认为之前未 Committed 的 Log Entry 转为 Committed。即最大化 commit 原则:Leader 在当选后立即追加一条 Noop 并同步到多数节点,实现之前 Term uncommitted 的 entry 隐式 commit。

  • 保证 commit 的数据不会丢。

  • 保证不会读到 uncommitted 的数据。

2.6 MultiRaft

元数据相比数据来说整体数据量要小的多,通常单台机器就可以存储。我们也通常借助于 Etcd 等使用单个 Raft Group 来进行元数据的复制和管理。但是单个 Raft Group,存在以下两点弊端:

  • 集群的存储容量受限于单机存储容量(排除使用分布式存储);

  • 集群的性能受限于单机性能(读写都由 Leader 处理)。

对于集群元数据来说使用单个 Raft Group 是够了,但是如果想让 Raft 用于数据的复制,那么必须得使用 MultiRaft,也即有多个复制组,类似于 Ceph 的 PG,每个 PG、Raft Group 是一个复制组。

但是 Raft Group 的每个副本间都会建立链接来保持心跳,如果多个 Raft Group 里的副本都建立链接的话,那么物理节点上的链接数就太多了,需要复用物理节点的链接。如下图 cockroachdb multi raft 所示:

01e156e246a7b1eb3b8c52de7f383ec3.png

MultiRaft 还需要解决以下问题:

  • 负载均衡 :可以通过 Transfer Leadership 的功能保持每个物理节点上 Leader 个数大致相当;

  • 链接复用 :一个物理节点上的所有 Raft Group 复用链接。会有心跳合并、Lease 共用等;

  • 中心节点 :用来管理集群包括 MultiRaft,使用单个 Raft Group 做高可靠,类似 Ceph Mon。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

3. 性能优化

3.1 Batch

  • Batch 写入落盘 :对每一条 Log Entry 都进行 fsync 刷盘效率会比较低,可以在内存中缓存多个 Log Entry Batch 写入磁盘,提高吞吐量,类似于 Ceph FileStore 批量写 Journal;

  • Batch 网络发送 :Leader 也可以一次性收集多个 Log Entry,批量的发送给 Follower;

  • Batch Apply :批量的 Apply 已经 commit Log 到业务状态机。

Batch 并不会对请求做延迟来达到批量处理的目的,对单个请求的延迟没有影响。

3.2 PipeLine

Raft 依赖 Leader 来保持集群的数据一致性,数据的复制都是从 Leader 到 Follower。一个简单的写入流程如下,性能是完全不行的:

  1. Leader 收到 Client 请求;

  2. Leader 将数据 Append 到自己的 Log;

  3. Leader 将数据发送给其他的 Follower;

  4. Leader 等待 Follower ACK,大多数节点提交了 Log,则 Apply;

  5. Leader 返回 Client 结果;

  6. 重复步骤 1。

Leader 跟其他节点之间的 Log 同步是串行 Batch 的方式,如果单纯使用 Batch,每个Batch 发送之后 Leader 依旧需要等待该 Batch 同步完成之后才能继续发送下一个 Batch,这样会导致较长的延迟。可以通过 Leader 跟其他节点之间的 PipeLine 复制来改进,会有效降低延迟。

3.3 并行

顺序提交

将 Leader Append 持久化日志和向 Followers 发送日志并行处理。Leader 只需要在内存中保存未 Committed 的 Log Entry,在多数节点已经应答的情况下,无需等待 Leader 本地 IO 完成,直接将内存中的 Log Entry 直接 Apply 给状态机即可。

乱序提交

乱序提交要满足以下两点:

  • Log Entry 之间不存在覆盖写,则可以乱序 Commit、Apply;

  • Log Entry 之间存在覆盖写,不可以乱序,只能顺序 Commit、Apply。

上层不同的应用场景限制了提交的方式:

  • 对 IO 保序要求比较严格,那么只能使用顺序提交;

  • 对 IO 保序没有要求,可以 IO 乱序完成,那么可顺序提交、乱序提交都可以使用。

不同的分布式存储需要的提交方式:

  • 分布式数据库(乱序提交) :其上层可串行化的事物就可以保证数据一致性,可以容忍底层 IO 乱序完成的情况;

  • 分布式 KV 存储(乱序提交) :多个 KV 之间(排除上层应用语义)本身并无相关性,也不需要 IO 保序,可以容忍 IO 乱序;

  • 分布式对象存储(乱序提交) :本来就不保证同一对象的并发写入一致性,那么底层也就没必要顺序接收顺序完成 IO,天然容忍 IO 乱序;

  • 分布式块存储(顺序提交) :由于在块存储上可以构建不同的应用,而不同的应用对 IO 保序要求也不一样,所以为了通用性只能顺序提交;

  • 分布式文件存储(顺序提交) :由于可以基于文件存储(POSIX 等接口)构建不同的应用,而不同的应用对 IO 保序要求也不一样,所以为了通用性只能顺序提交,当然特定场景下可以乱序提交,比如 PolarFS 适用于数据库;

  • 分布式存储 :具体能否乱序提交最终依赖于应用语义能否容忍存储 IO 乱序完成。

简单分析

单个 Raft Group 只能顺序提交日志,多个 Raft Group 之间虽然可以做到并行提交日志,但是受限于上层应用(数据库等)的跨 Group 分布式事物,可能导致其他不相关的分布式事物不能并行提交,只能顺序提交。

上层应用比如数据库的分布式事物是跨 Group(A、B、C) 的,Group A 被阻塞了,分布式事务不能提交, 那么所有的参与者 Group(B、C) 就不能解锁,进而不能提交其他不相关的分布式事物,从而引发多个 Group 的链式反应。

Raft 不适用于多连接的高并发环境中。Leader 和 Follower 维持多条连接的情况在生产环境也很常见,单条连接是有序的,多条连接并不能保证有序,有可能发送次序靠后的 Log Entry 先于发送次序靠前的 Log Entry 达到 Follower。但是 Raft 规定 Follower 必须按次序接受 Log Entry,就意味着即使发送次序靠后的 Log Entry 已经写入磁盘了(实际上不能落盘得等之前的 Log Entry 达到)也必须等到前面所有缺失的 Log Entry 达到后才能返回。如果这些 Log Entry 是业务逻辑顺序无关的,那么等待之前未到达的 Log Entry 将会增加整体的延迟。

其实 Raft 的日志复制和 Ceph 基于 PG Log 的复制一样,都是顺序提交的,虽然可以通过 Batch、PipeLine 优化,但是在并发量大的情况下延迟和吞吐量仍然上不去。

具体 Raft 乱序提交的实现可参考:PolarFS: ParallelRaft

http://www.vldb.org/pvldb/vol11/p1849-cao.pdf

3.4 异步

我们知道被 committed 的日志肯定是可以被 Apply 的,在什么时候 Apply 都不会影响数据的一致性。所以在 Log Entry 被 committed 之后,可以异步的去 Apply 到业务状态机,这样就可以并行的 Append Log 和 Apply Log 了,提升系统的吞吐量。

其实就和 Ceph BlueStore 的 kv_sync_thread 和 kv_finalize_thread 一样,每个线程都有其队列。kv_sync_thread 去写入元数据到 RocksDB(请求到此已经成功),kv_finalize_thread 去异步的回调上层应用通知请求成功。

3.5 ReadIndex

Raft 的写入流程会走一遍 Raft,保证了数据的一致性。为了实现线性一致性读,读流程也可以走一遍 Raft,但是会产生磁盘 IO,性能不好。Leader 具有最新的数据,理论上 Leader 可以读取到最新的数据。但是在网络分区的情况下,无法确定当前的 Leader 是不是真的 Leader,有可能当前 Leader 与其他节点发生了网络分区,其他节点形成了一个 Group 选举了新的 Leader 并更新了一些数据,此时如果 Client 还从老的 Leader 读取数据,便会产生 Stale Read。

读流程走一遍 Raft、ReadIndex、Lease Read 都是用来实现线性一致性读,避免 Stale Read。

  1. 当收到读请求时,Leader 先检查自己是否在当前 Term commit 过 entry,没有否则直接返回;

  2. 然后,Leader 将自己当前的 commitIndex 记录到变量 ReadIndex 里面;

  3. 向 Follower 发起 Heartbeat,收到大多数 ACK 说明自己还是 Leader;

  4. Leader 等待 applyIndex >= ReadIndex,就可以提供线性一致性读;

  5. 返回给状态机,执行读操作返回结果给 Client。

线性一致性读 :在 T1 时刻写入的值,在 T1 时刻之后肯定可以读到。也即读的数据必须是读开始之后的某个值,不能是读开始之前的某个值。不要求返回最新的值,返回时间大于读开始的值就可以。

注意 :在新 Leader 刚刚选举出来 Noop 的 Entry 还没有提交成功之前,是不能够处理读请求的,可以处理写请求。也即需要步骤 1 来防止 Stale Read。

原因 :在新 Leader 刚刚选举出来 Noop 的 Entry 还没有提交成功之前,这时候的 commitIndex 并不能够保证是当前整个系统最新的 commitIndex。

考虑这个情况 :

  • w1->w2->w3->noop| commitIndex 在 w1;

  • w2、w3 对 w1 有更新;

  • 应该读的值是 w3。

因为 commitIndex 之后可能还有 Log Entry 对该值更新,只要 w1Apply 到业务状态机就可以满足 applyIndex >= ReadIndex,此时就可以返回 w1 的值。但是此时 w2、w3 还未 Apply 到业务状态机,就没法返回 w3,就会产生 Stale Read。必须等到 Noop 执行完才可以执行读,才可以避免 Stale Read。

3.6 Follower Read

如果是热点数据么可以通过提供 Follower Read 来减轻 Leader 的读压力,可用非常方便的通过 ReadIndex 实现。

  1. Follower 向 Leader 请求 ReadIndex;

  2. Leader 执行完 ReadIndex 章节的前 4 步(用来确定 Leader 是真正的 Leader);

  3. Leader 返回 commitIndex 给 Follower 作为 ReadIndex;

  4. Follower 等待 applyIndex >= ReadIndex,就可以提供线性一致性读;

  5. 返回给状态机,执行读操作返回结果给 Client。

3.7 Lease Read

Lease Read 相比 ReadIndex 更进一步,不仅省去了 Log  的磁盘开销,还省去了Heartbeat的网络开销,提升读的性能。

基本思路

Leader 获取一个比 election timeout 小的租期(Lease)。因为 Follower 至少在 election timeout 时间之后才会发送选举,那么在 Lease 内是不会进行 Leader 选举。就可以跳过 ReadIndex 心跳的环节,直接从 Leader 上读取。但是 Lease Read 的正确性是和时间挂钩的,如果时钟漂移比较严重,那么 Lease Read 就会产生问题。

  1. Leader 定时发送(心跳超时时间)Heartbeat 给 Follower, 并记录时间点 start;

  2. 如果大多数回应,那么新的 Lease 到期时间为 start + Lease(<election timeout);

  3. Leader 确认自己是 Leader 后,等待 applyIndex >= ReadIndex,就可以提供线性一致性读;

  4. 返回给状态机,执行读操作返回结果给 Client。

3.8 Double Write-Store

我们知道 Raft 把数据 Append 到自己的 Log 的同时发送请求给 Follower,多数回复 ACK 就认为 commit,就可以 Apply 到业务状态机了。如果业务状态机(分布式 KV、分布式对象存储等)也把数据持久化存储,那么数据便 Double Write-Store,集群中存在两份相同的数据。如果是三副本,那么就会有 6 份。

接下来主要思考元数据、数据做的一点点优化。

通常的一个优化方式就是先把数据写入 Journal(环形队列、大小固定、空间连续、使用 3D XPoint、NVME),然后再把数据写入内存即可返回,最后异步的把数据刷入 HDD(最好带有 NVME 缓存)。

元数据

元数据通常使用分布式 KV 存储,数据量比较小,Double Write-Store 影响不是很大,即使存储两份也不会浪费太多空间,而且以下改进也相比数据方面的改进更容易实现。

可以撸一个简单的 Append-Only 的单机存储引擎 WAL 来替代 RocksDB 作为 Raft Log 的存储引擎,Apply 业务状态机层的存储引擎可以使用 RocksDB,但是可以关闭 RocksDB 的 WAL,因为数据已经存储在 Append-Only 的 Raft Log 了,细节仍需考虑。

数据

这里的数据通常指非结构化数据:图片、文档、音视频等。非结构化数据通常使用分布式对象存储、块存储、文件存储等来存储,由于数据量比较大,Double Store 是不可接受的,大致有两种思路去优化:

  • Raft Log、User Data 分开存:Raft Log 只存 op-cmd,不存 data。类似于 Ceph 的 PG Log。

  • Raft Log、User Data 一起存:作为同一份数据来存储。Bitcask 模型 Append 操作天然更容易实现。

参考资源

  • raft paper:https://raft.github.io/raft.pdf

  • braft:https://github.com/baidu/braft/blob/master/docs/cn/raft_protocol.md

  • SOFAJRaft:https://juejin.im/post/5c88756a6fb9a049f9136c1a

  • etcd raft:https://github.com/etcd-io/etcd/tree/master/raft

  • 关于Paxos “幽灵复现”问题看法:https://zhuanlan.zhihu.com/p/40175038

  • PolarFS: ParallelRaft:http://www.vldb.org/pvldb/vol11/p1849-cao.pdf

  • BlueStore源码分析之事物状态机:IO保序:https://shimingyah.github.io/2019/11/BlueStore%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%E4%BA%8B%E7%89%A9%E7%8A%B6%E6%80%81%E6%9C%BA/#chapter3



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

05cc3d6fc11fa158b0463f5c7f27b81c.png

已在知识星球更新源码解析如下:

fa825d957cb434c5c8ba71e3baeb938e.jpeg

43ca7cbb1264fad4f5b382788ef5b5c6.jpeg

9e5efb6dc7d92390177680663304eb45.jpeg

139a81e20cfb7605639755d3f2ad1c70.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
浅谈分布式一致性:Raft 与 SOFAJRaft 简介:SOFAJRaft已开源 作者 | 家纯 来源 | 阿里技术公众号 一 分布式共识算法 (Consensus Algorithm) 1 如何理解分布式共识? 多个参与者针对某一件事达成完全一致:一件事,一个结论。 已达成一致的结论,不可推翻。 2 有哪些分布式共识算法? Paxos:被认为是分布式共识算法的根本,其他都是其变种,但是 paxos 论文中只给出了单个提案的过程,并没有给出复制状态机中需要的 multi-paxos 的相关细节的描述,实现 paxos 具有很高的工程复杂. 阅读详情

相关推荐

Claude AI实战指南:从注册到精通,突破手机验证的终极解决方案

本文提供了一份详尽的Claude AI实战指南,重点解决了国内用户注册过程中的手机号验证难题,并分享了使用技巧。文章详细介绍了如何通过虚拟手机号服务等方案完成注册,并深入解析了Opus、Sonnet、Haiku三大模型的特点与选择策略,帮助用户从注册到精通,高效利用这一安全、健谈的AI助手进行内容创作、编程开发与学习研究。

weixin_29092031的博客 1233

分布式锁-yudao之设计精妙之处

/ 获得需要通知的任务 List < PayNotifyTaskDO > tasks = payNotifyTaskCoreMapper . selectListByNotify();return 0;} // 遍历,逐个通知 CountDownLatch latch = new CountDownLatch(tasks . size());try {} }));// 等待完成 awaitExecuteNotify(latch);

weixin_48278764的博客 618

eDP/DP接口简介

目前,在视频传输协议以及接口领域,外部接口主要是DP与HDMI两分天下,当然之前出现过很多接口-VGA, DVI等等,最新平台基本以这两个为主;笔记本内部接口就基本上使用eDP((Embedded Display Port))作为标准接口来驱动液晶屏了。那么问题来了,eDP和DP,是完全兼容的么?eDP,DP,HDMI,这三种接口的显示器,可以插在同一个接口上使用么?这篇文章,就会从硬件和一部分协议的角度分析一下这三大协议。

weixin_50518899的博客 2万+

芋道 spring security oauth2 入门_OAuth2.0分布式系统环境搭建

介绍OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0的系统大致分由客户端,认证授权服务器以及资源服务器三部分组成。客户端如果想要访问资源服务器中的资源,就必须要持有认证授权服务器颁发的Token。认证流程如下图所示:这篇文章将通过一个具体的案例来展示如何搭建一个分布式...

weixin_39602280的博客 651

HBase在物流行业的应用:轨迹数据存储

想象一下,每天在全球范围内,数以亿计的物流包裹在各个城市、乡镇间穿梭。每一个包裹从发货人手中出发,经过仓储中心、转运站,最终到达收货人手中,这一路上都留下了它的“足迹”,也就是我们所说的轨迹数据。这些数据就像一个个拼图碎片,完整拼接起来后,能够清晰地呈现出整个物流网络的运行状况。例如,你在网上购买了一件心仪已久的商品,迫不及待地想知道它现在到哪儿了。物流平台实时更新的轨迹信息,让你可以准确了解包裹是在运输途中,还是已经到达附近的站点。

架构师的AI之路,分享AI应用开发架构的学习与实践。 966

指纹浏览器降维打击(下):Cloudflare Turnstile 与 hCaptcha 的底层交互逻辑

本文将深度拆解:Turnstile 与 hCaptcha 的 Iframe 沙箱通信机制,动态混淆 JS 与 PoW(工作量证明)的物理破解法则,以及如何通过 C++ 底层事件合成与环境基因强绑定,实现验证码的“无感过验”。

数据知道的博客 423

芋道 Spring Cloud Alibaba 分布式事务 Seata 入门:面试官,我来带你飞!

在微服务架构中,分布式事务是一个绕不开的话题。随着业务的复杂度增加,多个服务之间的数据一致性问题变得尤为关键。Seata 作为一款高性能的分布式事务解决方案,为 Spring Cloud Alibaba 提供了强大的支持。本文将从初学者的角度出发,深入浅出地讲解如何在 Spring Cloud Alibaba 中使用 Seata 实现分布式事务管理,并提供面试回答思路和答案,帮助你轻松应对校招面试。回答思路:定义:分布式事务是指在分布式系统中,跨越多个服务或数据库的事务。

shaozheng0503的博客 1238

RK3588, 绘制中文OSD,实现左右对齐

基于RK3588,实现中文OSD, 上下左右对齐。

J。 662

使用MyBatisPlus实现多租户功能

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT ...

芋艿V 1257

Windows11上pip安装IsaacSim5.1.0与IsaacLab

本文详细介绍了CUDA Toolkit 12.8.0、IsaacSim和IsaacLab的安装过程。首先从NVIDIA官网下载并安装CUDA Toolkit 12.8.0,然后通过miniforge创建conda环境(env_isaacsim)安装IsaacSim相关Python包,解决了Windows长路径问题。成功运行IsaacSim后,克隆IsaacLab仓库并完成安装验证,最后通过创建空场景测试环境配置。整个安装过程包含详细的命令行操作和问题解决方法,为机器人仿真开发环境搭建提供了完整指导。

拥抱具身智能,昇腾Ascend C 378

PS射击游戏射线检测优化:原理、工程与全链路高性能实现

射线池:减少GC,提升批量检测效率。分层检测:先粗后细,远距离射击高效判定。批量检测:并行处理,适合大量射线场景。空间结构:进一步加速大场景检测。下面继续深入射线检测优化,补充更多实用细节和进阶技巧,包括空间分区加速、射线检测多线程/异步化、射线合并与分帧、射线池通用模板、常见性能陷阱与调优建议等。对于极端性能需求,可自定义轻量级物理系统,仅检测关键对象(如玩家、子弹),跳过复杂物理引擎。结合AABB、OBB、球体等简单碰撞体,射线-包围体检测极快。空间分区+分层检测是大场景射线优化核心。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 434

浅谈分布式存储中的网络通信

作者 | 火尉子责编 | Carol封图 | 优快云 下载自视觉中国在各行各业数字化转型深入的当下,数据呈爆炸式增长。面对海量数据的存储需求,分布式存储显然在架构上有着天然优势,但在这...

优快云云计算 3413

解锁RK平台黑科技:DPDK加速与硬件卸载实战

本文探讨了在RK平台上通过DPDK加速与硬件卸载技术构建高性能网络架构的方法。RK平台凭借强大的硬件性能为高并发网络处理提供基础,而DPDK通过用户态驱动、轮询机制等技术绕过内核瓶颈,显著提升数据包处理效率。硬件卸载则将校验和计算等任务交由专用硬件,释放CPU资源。实践案例显示,该方案可使网络吞吐量提升至理论极限,延迟降至5ms以下,CPU利用率降低40%。文章详细介绍了配置步骤、优化方法和常见问题解决方案,并展望了该技术在5G、车联网等新兴领域的应用前景。

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 1281

golang 分布式事务_也浅谈下分布式存储要点

本篇文章谢绝转载,欢迎转发几年之前,曾不自量力的想要写一个兼容RDBMS和NoSQL的数据库,结果仅实现了一个Raft协议,写了一棵BTree,就放弃了。使用Golang写这个算是比较简单的了,但过程难以言诉,有点蚂蚁撼大树了。而个人,由于工作的关系,也已经有四五年没有和SQL打交道了。最近重拾,感慨良多。像MySQL这种RDBMS,天生是存在分布式缺陷的,在海量数据的今天,很容易就达到瓶颈。过去...

weixin_39970064的博客 765

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

ceph存储原理_浅谈分布式存储中的网络通信

作者 | 火尉子责编 | Carol封图 | 优快云 下载自视觉中国在各行各业数字化转型深入的当下,数据呈爆炸式增长。面对海量数据的存储需求,分布式存储显然在架构上有着天然优势,但在这波数据洪流之中也面临着性能上的全新挑战。由于分布式存储的工作原理是将各个存储节点使用网络互联的方式构建为集群,向外部提供更加可靠的高性能服务,因此可以说分布式存储本质上是一种网络存储,其性能在很大程度上受...

weixin_39946239的博客 779

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

ceph 存储 对比_浅谈分布式存储中的网络通信

作者 | 火尉子责编 | Carol封图 | 优快云 下载自视觉中国在各行各业数字化转型深入的当下,数据呈爆炸式增长。面对海量数据的存储需求,分布式存储显然在架构上有着天然优势,但在这波数据洪流之中也面临着性能上的全新挑战。由于分布式存储的工作原理是将各个存储节点使用网络互联的方式构建为集群,向外部提供更加可靠的高性能服务,因此可以说分布式存储本质上是一种网络存储,其性能在很大程度上受...

weixin_39827905的博客 302

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

也浅谈下分布式存储要点

本篇文章谢绝转载,欢迎转发 几年之前,曾不自量力的想要写一个兼容RDBMS和NoSQL的数据库,结果仅实现了一个Raft协议,写了一棵BTree,就放弃了。使用Golang写这个算是比较简单的了,但过程难以言诉,有点蚂蚁撼大树了。 而个人,由于工作的关系,也已经有四五年没有和SQL打交道了。最近重拾,感慨良多。 像MySQL这种RDBMS,天生是存在分布式缺陷的,在海量数据的今天,很容易就达到...

weixin_33804582的博客 296

《浅谈分布式存储系统构建:以 TiDB 双引擎架构演进为例的技术复盘》

obsidian。

985 硕士,高级职称,数据库技术爱好者。持有Oracle/MySQL/ PostgreSQL/OceanBase/OpenGauss/TiDB和达梦中级认证,涉猎OLTP、OLAP 与大数据领域。现专注国产分布式数据库知识分享和实战沉淀。 516

Java中高级核心知识全面解析(5),这4种高可用RocketMQ集群搭建方案

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 三、一致性协议/算法 早在1900年就诞生了著名的 Paxos经典算法 (Zookeeper就采用了Paxos算法的近亲兄弟Zab算 法),但由于Paxos算法非常

m0_60635084的博客 218

浅谈Nacos注册中心集群分布式架构设计

当Nacos不单单是服务于集团公司内部,而是PaaS化了,所需管理的服务实例,达到了几百万 ,甚至千万等等。那么依靠Nacos现在开源版本是很难支持这种场景的。我们该如何二开Nacos使其集群分布式化呢?

啊杰的博客 2532

Zeppelin:一个分布式KV存储平台之概述

过去的一年多的时间中,大部分的工作都围绕着Zeppelin这个项目展开,经历了Zeppelin的从无到有,再到逐步完善稳定。见证了Zeppelin的成长的同时,Zeppelin也见证了我的积累进步。对我而言,Zeppelin就像...

cizhuo6814的博客 363

一文读懂国产分布式数据库TiDB&Ocean Base原理

目录: 背景介绍 产品架构 数据分片原理 数据同步原理 ban

ALing_DBA的博客 8682

SaaS模式下,多租户系统数据隔离实现方案

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天 10:33更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT 源码解析作业调度中间件 E...

芋艿V 1407
上一篇: 一文轻松图解搞懂Elasticsearch原理!
下一篇: 软件架构的23个基本原则
公众号-芋道源码
博客等级 码龄10年 1634粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值