图解 Fluss(四):分布式协调 —— 选举、副本状态机与

图解 Fluss(四):分布式协调 ------ 选举、副本状态机与 Rebalance

阅读本文你将了解: 三个 Coordinator 如何选出唯一 Leader 且不脑裂、一个 Tablet 副本的状态迁移路径、节点宕机时数据为什么不会丢、以及扩容时 Fluss 如何在不中断服务的前提下搬移数据。

配套图表:seq-04-startup-leader-electionstate-01-replicastate-02-coordinatorseq-05-rebalance

难度:⭐⭐⭐⭐ | 适合人群:负责集群稳定性与容量规划的 SRE / 架构师


一、场景:大促前的三个运维动作

大促前一周,你要对 Fluss 集群做三件事:

动作一:冷启动整个集群

机房搬迁,所有节点关机再开机。开机顺序是什么?三个 Coordinator 同时启动,谁当 Leader?会不会出现两个 Leader?

动作二:应对节点宕机

大促期间一台 TabletServer 宕机了。它上面有 2000 个 Tablet,其中 300 个是 Leader。这些 Tablet 会发生什么?数据会丢吗?业务会中断多久?

动作三:紧急扩容

流量比预期高 3 倍,需要临时加 10 台 TabletServer。加了之后,数据会自动均衡过去吗?会不会因为大量数据迁移把网络打满,反而影响线上业务?

这三个动作,对应的正是本篇的四张图。


二、图 1:启动与 Leader 选举时序图

图里有两个 Coordinator 实例 A 和 B,完整演示了选举过程。

2.1 阶段 1:Standby 模式启动

复制代码
A -> A  : initCoordinatorStandby() 启动 RPC/元数据/调度器
A -> ZK : registerCoordinatorServer() (临时节点)
ZK --> A : 注册成功

第 1 篇讲过,Standby 阶段只起基础设施。关键点是 registerCoordinatorServer() 创建的是临时节点(ephemeral)

复制代码
/fluss/coordinator/servers/
├── server_0000000001   ← A 注册,ephemeral
├── server_0000000002   ← B 注册,ephemeral
└── server_0000000003   ← C 注册,ephemeral

为什么必须是临时节点? 因为 ZK 的临时节点在会话断开时会自动删除。Coordinator A 宕机 → 会话超时 → 节点自动消失 → 集群立刻感知到"A 不在了"。如果用持久节点,还需要额外的心跳检测机制。

2.2 阶段 2:Leader 选举

这是整张图最精妙的部分。

复制代码
A -> ZK : createEphemeralSequential(/coordinator_election/candidate_)
ZK --> A : 节点路径 candidate_001

B -> ZK : createEphemeralSequential(/coordinator_election/candidate_)
ZK --> B : 节点路径 candidate_002

A -> ZK : getChildren(选举路径)
ZK --> A : [candidate_001, candidate_002]
A -> A  : 序号 001 最小 → 自己是 Leader

B -> ZK : getChildren(选举路径)
ZK --> B : [candidate_001, candidate_002]
B -> B  : 序号 002 非最小 → Watch candidate_001

三个关键设计:

ephemeral_sequential(临时顺序节点)

ZK 会自动在节点名后追加一个单调递增的序号:

复制代码
/coordinator_election/
├── candidate_0000000001   ← A
├── candidate_0000000002   ← B
└── candidate_0000000003   ← C

"序号最小者为 Leader" 是一个无需协调的确定性规则------每个节点拿到 getChildren() 的结果后,自己就能判断出谁是 Leader,不需要额外的投票轮次。

同时因为是 ephemeral 节点,Leader 宕机后它的候选节点自动消失,下一个序号的节点自然成为新的最小者。

② Watch 前一个节点(避免羊群效应)

注意图里 B 的行为:

复制代码
B -> B : 序号 002 非最小 → Watch candidate_001

B watch 的是 candidate_001(它的前一个节点),而不是所有节点,也不是 Leader 节点。

这个细节叫"链式 watch",它解决的是羊群效应(Herd Effect)

复制代码
错误的做法:所有非 Leader 节点都 watch Leader 节点
  → Leader 宕机时,ZK 要向 N-1 个节点同时发送通知
  → N-1 个节点同时被唤醒,同时发起 getChildren() 查询
  → 瞬间产生 N-1 倍的 ZK 读请求 → ZK 被打爆

正确的做法(Fluss 采用):每个节点只 watch 它前面那一个
  → Leader (001) 宕机时,ZK 只需要通知 002
  → 002 成为新 Leader
  → 如果 002 也宕机,通知 003,以此类推
  → 通知复杂度 O(1),而不是 O(N)

这个模式和 Zookeeper 官方的分布式锁实现(Curator 的 LeaderLatch)是一致的。

③ ZK Fence(防脑裂)

复制代码
A -> ZK : fenceBecomeCoordinatorLeader() (epoch +1, 防脑裂)
ZK --> A : ZkEpoch(newEpoch)

只有成为 Leader 之后,才会执行 fenceBecomeCoordinatorLeader(),把 ZK 上记录的 epoch 加一。

图下方的 note 总结了这三点:

复制代码
ZooKeeper 选举关键设计:
1. ephemeral_sequential 节点自动处理崩溃退出
2. Watch 前一个节点 (非所有节点) → 避免羊群效应
3. fenceBecomeCoordinatorLeader 用 epoch 防脑裂

2.3 阶段 3:Leader 初始化

复制代码
A -> A  : initCoordinatorLeader() 创建 EventProcessor/AutoPartition/ChannelManager
A -> ZK : registerCoordinatorLeader() (临时节点)
A -> ZK : createDefaultDatabase("fluss")
A -> TS : 开始下发 Tablet 分配指令

注意 registerCoordinatorLeader() 创建的是另一个临时节点:

复制代码
/fluss/coordinator/leader → {serverId: "server_1", epoch: 42, address: "..."}

这个节点的作用是让TabletServer 和客户端能找到 Leader。TabletServer 启动时 watch 这个节点,Leader 变更时立刻感知并重新注册。

2.4 回答"动作一"

机房搬迁后冷启动:

复制代码
1. 先启动 ZooKeeper 集群(3 或 5 节点)   ← 必须最先
2. 再启动 3 个 CoordinatorServer          ← 自动选主,谁先注册谁序号小
3. 最后启动 N 个 TabletServer             ← 向 Coordinator 注册,等待分配

会不会出现两个 Leader? 不会,因为:

  • 顺序节点的序号由 ZK 保证全局唯一且单调
  • "序号最小者为 Leader"是确定性规则
  • 即使发生网络分区,旧 Leader 的 epoch 已过期,它下发的指令会被 TabletServer 拒绝

三、图 2:Coordinator 状态机

时序图看的是"一次成功的选举过程",状态图看的是"所有可能的生命周期路径"。

3.1 四个状态

复制代码
[*] --> Standby : 进程启动
Standby --> Electing : startElectLeaderAsync()
Electing --> Leader : 选举成功 (序号最小 + ZK Fence)
Electing --> Standby : 选举失败 (等待前节点删除)
Leader --> Standby : 失去 Leader (会话超时/主动释放)
Standby --> Down : closeAsync()
Leader --> Down : closeAsync()
Down --> [*] : 资源清理完成

注意 Electing 是一个显式的中间状态。这一点很重要------很多人的直觉是"启动 → 竞选 → 成功就 Leader",但实际上竞选可能失败,失败后要回到 Standby 继续等待。

3.2 每个状态持有什么资源

图里两个 note 精确列出了差异:

Standby 保留(所有节点都有):

复制代码
- ZooKeeper 连接
- RpcServer (健康检查)
- MetadataManager (只读)
- DynamicConfigManager (监听配置)

Leader 特有(只有一个节点有):

复制代码
- CoordinatorEventProcessor (单线程事件循环)
- CoordinatorChannelManager (连接 TabletServer)
- AutoPartitionManager (自动分区)
- RpcClient (主动连接)

这个资源划分是第 1 篇"两阶段启动"的直接体现initCoordinatorStandby() 建前者,initCoordinatorLeader() 建后者。

3.3 状态迁移时的资源操作

迁移 操作
Standby → Electing 创建顺序节点、getChildren、判断序号
Electing → Leader fence 递增 epoch → initCoordinatorLeader() 建 Leader 资源 → 注册 leader 节点
Electing → Standby watch 前一个节点,进入等待
Leader → Standby cleanupCoordinatorLeader() 按逆序清理 Leader 资源 → 重新参与选举

图里的 note 特别强调:

复制代码
失去 Leader 时按逆序清理这些资源

为什么要"逆序"? 因为资源之间有依赖:

复制代码
创建顺序:RpcClient → ChannelManager → AutoPartitionManager → EventProcessor
销毁顺序:EventProcessor → AutoPartitionManager → ChannelManager → RpcClient

如果先关掉 RpcClient,正在处理事件的 EventProcessor 就会因为连接断开而抛异常。逆序销毁保证上层消费者先停,下层依赖后关

3.4 代码骨架

java 复制代码
private void electCoordinatorLeaderAsync() {
    coordinatorLeaderElection.startElectLeaderAsync(
        // 成为 Leader 的回调
        () -> {
            try {
                ZkEpoch epoch = zkClient.fenceBecomeCoordinatorLeader();
                if (epoch == null) {
                    // fence 失败,说明有更新的 epoch,放弃
                    return;
                }
                initCoordinatorLeader();
                zkClient.registerCoordinatorLeader();
                zkClient.createDefaultDatabase(DEFAULT_DATABASE);
            } catch (Exception e) {
                // 初始化失败,主动释放 Leader 身份,重新选举
                cleanupCoordinatorLeader();
            }
        },
        // 失去 Leader 的回调
        () -> cleanupCoordinatorLeader()
    );
}

四、图 3:Replica 副本状态机

Coordinator 管元数据,真正的数据可靠性由 Replica 状态机保证。

4.1 五个状态

复制代码
[*] --> NewReplica : Tablet 创建

NewReplica      : 刚分配,尚未加载;LogTablet/KvTablet 未打开
OnlineReplica   : 已加载数据,可服务;等待 Leader/Follower 角色
LeaderReplica   : 处理读写请求;管理 ISR 列表;推进 High Watermark
FollowerReplica : 从 Leader 拉取日志;上报同步进度;等待晋升为 Leader
OfflineReplica  : 暂时不可用;可能因故障/网络分区

4.2 迁移路径

复制代码
NewReplica      --> OnlineReplica   : 加载完成 (becomeOnline)
OnlineReplica   --> LeaderReplica   : becomeLeader (Leader 选举成功)
OnlineReplica   --> FollowerReplica : becomeFollower (跟随 Leader)

LeaderReplica   --> FollowerReplica : 失去 Leader (被新的 Leader 取代)
FollowerReplica --> LeaderReplica   : 晋升 (原 Leader 故障)

LeaderReplica   --> OfflineReplica  : 故障 / 网络分区
FollowerReplica --> OfflineReplica  : 故障 / 同步超时

OfflineReplica  --> OnlineReplica   : 恢复 (重新加载)
OnlineReplica   --> [*]             : Tablet 删除

注意 NewReplicaOnlineReplica 的分离 :副本分配(Coordinator 决定)和副本加载(TabletServer 执行)是异步的两步。Tablet 被分配到某台机器,不代表它立刻能服务------要等 LogTablet 打开、索引 mmap、RocksDB 打开完成。

4.3 Leader 与 Follower 的职责

图里的两个 note:

复制代码
Leader 职责:
- 接收客户端写入 (append)
- 管理 ISR (加入/移除 Follower)
- 推进 HW = min(所有 ISR 的 LEO)

Follower 职责:
- ReplicaFetcher 拉取日志
- 验证 Leader Epoch
- 落后太多时通过快照追平

"验证 Leader Epoch"这一条值得展开。

这是防止"日志分叉(log divergence)"的关键机制。考虑这个场景:

复制代码
1. Leader A (epoch=5) 写入 offset 100-110,还没同步给 Follower
2. A 宕机
3. Follower B 晋升为新 Leader (epoch=6),它的日志只到 offset 100
4. B 从 offset 100 开始写入新数据:offset 100-105(内容不同!)
5. A 重启,它的日志有 offset 100-110
6. A 作为 Follower 向 B 拉取数据 ------ 如果不检查,A 会保留自己的 100-110,
   导致同一个 offset 在两个副本上内容不同 → 日志分叉

解决办法是 Leader Epoch 校验

复制代码
A 作为 Follower 向 B 请求:我要从 offset 100 开始拉,我的 epoch 是 5
B 查询 epoch 5 对应的最后 offset 是 100,返回 OK
A 发现自己 offset 100 之后的数据(101-110)不在有效范围内 → 截断
A 从 B 重新拉取 epoch 6 的数据

这个机制和 Kafka 的 LeaderEpoch 完全一致(KIP-101 / KIP-279)。

4.4 ISR 的动态调整

Leader 会持续监控每个 Follower 的同步进度:

java 复制代码
// 伪代码:Leader 侧的 ISR 管理
void maybeShrinkIsr() {
    long leaderLagMs = ...;   // 配置:replica.lag.time.max.ms(默认 30s)
    for (Replica follower : assignedReplicas) {
        if (now - follower.lastCaughtUpTimeMs > leaderLagMs) {
            // Follower 落后超过 30 秒 → 移出 ISR
            isr.remove(follower);
        }
    }
}

void maybeExpandIsr() {
    for (Replica follower : assignedReplicas) {
        if (follower.logEndOffset >= highWatermark && !isr.contains(follower)) {
            // Follower 追上 HW → 重新加入 ISR
            isr.add(follower);
        }
    }
}

配置 replica.lag.time.max.ms 的权衡:

设小(如 10s) 设大(如 60s)
慢副本快速被剔除,acks=all 不受拖累 副本容忍抖动,不会频繁进出 ISR
风险:网络偶发抖动导致 ISR 频繁收缩 风险:真的慢副本拖慢 acks=all 的响应

生产建议:默认 30s,网络环境差的机房可放宽到 60s。

4.5 回答"动作二"

一台 TabletServer 宕机,上面 2000 个 Tablet(300 个 Leader):

复制代码
T+0s     ZK 会话超时(默认 18s),节点被摘除
         ↓
T+18s    Coordinator 感知,标记这些 Tablet 的副本为 OfflineReplica
         ↓
T+18s    对每个失去 Leader 的 Tablet:
         从 ISR 中选一个 Follower 晋升为 LeaderReplica
         (优先选 LEO 最大的,保证数据最新)
         ↓
T+19s    新 Leader 开始接收读写
         ISR 收缩(少了宕机那台的副本)
         ↓
T+??     如果宕机节点恢复:
         → NewReplica → OnlineReplica → FollowerReplica
         → 追平 LEO → 重新加入 ISR

数据会丢吗? 只要写入时用的是 acks=allmin.insync.replicas >= 2,就不会丢------因为 acks=all 保证数据至少写进了所有 ISR 副本,而 ISR 里的副本即使不是 Leader,也持有完整数据。

业务中断多久? 主要取决于 ZK 会话超时时间:

properties 复制代码
# 缩短故障发现时间(但会增加误判风险)
zookeeper.session-timeout: 10s
replica.lag.time.max.ms: 30s

调优提醒 :把 zookeeper.session-timeout 设得太小(如 3s),一次 Full GC 就可能导致误判节点宕机,触发不必要的 Leader 切换。生产环境建议不低于 10s。


五、图 4:Rebalance 渐进式迁移

5.1 触发阶段

复制代码
Coord -> Rebal : checkAndTriggerRebalance()
Rebal -> Rebal : 检测负载不均衡 (标准差 > 阈值) 或新节点加入
Rebal -> Rebal : generateRebalancePlan() 计算最优 Tablet 分布
Rebal -> Rebal : 生成迁移计划 (最小迁移成本)
Rebal --> Coord : RebalancePlan

两种触发条件:

  1. 新节点加入:集群扩容时自动触发
  2. 负载不均衡:各 TabletServer 的 Tablet 数量标准差超过阈值
properties 复制代码
# 触发阈值配置
tablet-server.rebalance.trigger.threshold: 0.1    # 标准差比例超过 10% 触发
tablet-server.rebalance.check.interval: 5min

"最小迁移成本"是什么? Rebalance 规划是一个带约束的优化问题

复制代码
目标:minimize(迁移的 Tablet 数 × 每个 Tablet 的数据量)
约束:1. 每个 Tablet 的副本不能集中在同一台机器
     2. 迁移后各节点的负载标准差 < 阈值
     3. 单个节点同时进行的迁移数 < 上限

5.2 执行阶段:渐进式,分批

图里最关键的结构是这个 loop

复制代码
loop 每批迁移 (20% Tablet)
  ...
end

为什么是渐进式而不是一次性全搬?

一次性迁移的问题:

复制代码
假设要迁移 10000 个 Tablet,总计 10TB 数据:
- 网络带宽被打满 → 线上业务的读写延迟飙升
- 磁盘 IO 被打满 → 正常读写受影响
- 万一中途出错,回滚困难

渐进式的好处:

复制代码
每批只迁移 20%:
- 网络和磁盘压力可控
- 每批完成后检查集群稳定性,异常就停止
- 支持中途回滚(只回滚当前批次)

5.3 单个 Tablet 迁移的三步

图中每个 loop 内部有三个子阶段:

① 副本同步

复制代码
Src -> Dst : 复制 Tablet-X 日志 (作为 Follower)
Dst -> Src : 追平 Leader (LEO 追上)
Dst -> ZK  : 请求加入 ISR
ZK --> Dst : ISR 更新成功

注意:迁移的第一步是"在目标机器上增加一个 Follower 副本",而不是"直接搬数据"。 这个顺序保证了任何时刻都有完整的副本可用。

② Leader 切换

复制代码
Coord -> ZK  : 更新 Tablet-X Leader = Server-3
ZK --> Coord : Leader 元数据更新
Coord -> Dst : 晋升为 Leader
Dst --> Coord : 晋升完成

因为 Dst 已经追平了 LEO 并在 ISR 里,这次切换是"干净"的------不丢数据,业务侧只会感受到毫秒级的抖动(和上面"节点宕机"的切换是同一个机制,但更快,因为是主动切换)。

③ 清理旧副本

复制代码
Coord -> Src : 删除 Tablet-X 旧副本
Src --> Coord : 删除完成

注意清理发生在最后,这保证:如果第 ② 步失败,旧副本还在,可以回退。

5.4 图下方 note 的总结

复制代码
渐进式迁移三步:
1. Follower 同步 → 追上 Leader
2. 加入 ISR → Leader 切换
3. 删除旧副本
分批执行避免集群压力, 支持中途回滚

5.5 回答"动作三"

加 10 台 TabletServer 后:

复制代码
T+0min    新节点注册到 Coordinator
T+5min    checkAndTriggerRebalance 检测到负载不均衡
          生成迁移计划(从老节点搬一部分 Tablet 到新节点)
T+5min    开始第 1 批(20%)
          - 每个被迁的 Tablet 先在新节点建 Follower
          - 追平后切 Leader
          - 删老副本
T+??      第 1 批完成,检查集群稳定性
          继续第 2 批 ...
T+??      全部完成,集群负载均衡

会打满网络吗? 可以通过限流控制:

properties 复制代码
# 限制单个 TabletServer 用于副本同步的带宽
tablet-server.replica.fetch.max.bytes: 10MB
tablet-server.rebalance.max.concurrent.migrations: 5     # 单节点并发迁移数
tablet-server.rebalance.batch.ratio: 0.2                 # 每批比例

六、动手验证

6.1 观察选举过程

bash 复制代码
# 启动 Coordinator,观察日志
tail -f logs/coordinator-server.log | grep -E "Standby|Elect|Leader"

# 预期看到
# [main] initCoordinatorStandby - Starting coordinator server in standby mode
# [main] CoordinatorLeaderElection - Created candidate node: candidate_0000000001
# [main] CoordinatorServer - Became coordinator leader with epoch 1
# [main] initCoordinatorLeader - Starting coordinator leader services

6.2 手动触发 Leader 切换

bash 复制代码
# 找到当前 Leader
zkCli.sh get /fluss/coordinator/leader

# kill 掉 Leader 进程
kill -9 <leader_pid>

# 观察谁接任(应该在 10-20 秒内完成)
zkCli.sh get /fluss/coordinator/leader

6.3 观察 Rebalance

bash 复制代码
# 查看当前 Tablet 分布
curl http://coordinator:9124/metrics | grep tablet_distribution

# 手动触发 Rebalance(如果有管理接口)
curl -X POST http://coordinator:9124/rebalance

# 观察迁移进度
curl http://coordinator:9124/metrics | grep -E "rebalance|migrat"

6.4 观察 ISR 变化

bash 复制代码
curl http://tablet-server:9125/metrics | grep isr

关注指标:

复制代码
fluss_replica_isr_count          # 每个 Tablet 的 ISR 大小
fluss_replica_under_replicated   # 副本数不足的 Tablet 数(应该为 0)
fluss_replica_leader_count       # 每台机器上的 Leader 数(应该均衡)

七、生产实践要点

7.1 部署建议

项目 建议 原因
Coordinator 数量 3 或 5(奇数) ZK 选举需要多数派
ZK 集群 独立部署,3 或 5 节点 混部会因 IO 竞争导致误判
ZK session timeout 10 - 18s 太小易误判,太大故障恢复慢
TabletServer 数量 ≥ 3 保证副本能分散
副本数 3 2 副本在滚动重启时会降级

7.2 关键配置

properties 复制代码
# coordinator-server.yaml
zookeeper.session-timeout: 15s
zookeeper.connection-timeout: 10s
coordinator.rebalance.check-interval: 5min
coordinator.rebalance.trigger-threshold: 0.1

# tablet-server.yaml
replica.lag.time.max.ms: 30000
replica.fetch.max.bytes: 10MB
replica.fetch.min.bytes: 1KB
replica.fetch.wait.max.ms: 500

# 客户端(重要!)
client.request.acks: all
tablet-server.min.insync.replicas: 2

7.3 滚动重启的正确顺序

升级集群时,按这个顺序能避免不必要的 Leader 切换:

复制代码
1. 先滚动重启 TabletServer(一次一台)
   - 等该节点的 Tablet 全部回到 OnlineReplica 且 ISR 恢复
   - 再重启下一台
2. 最后重启 Coordinator
   - 先重启 Standby 节点
   - 最后重启 Leader(会触发一次 Leader 切换)

千万不要先重启 Coordinator Leader------这会导致所有 TabletServer 重新注册,产生大量元数据变更事件。

7.4 容量规划中的副本开销

复制代码
有效容量 = 裸容量 / 副本数 / 空间放大系数

举例:10 台机器,每台 2TB SSD
  裸容量 = 20TB
  副本数 = 3
  RocksDB 空间放大 ≈ 1.5(PK 表)
  有效容量 ≈ 20 / 3 / 1.5 ≈ 4.4TB(PK 表)
           ≈ 20 / 3       ≈ 6.7TB(Log 表)

规划时还要留出 30% 余量给 compaction 和 Rebalance 的临时空间。


八、排障手册

现象 可能原因 排查方向
集群一直无 Leader ZK 不可达或选举路径被污染 zkCli.sh ls /fluss/coordinator/election;检查是否有残留的 candidate_ 节点
Leader 频繁切换 GC 停顿 / 网络抖动 / ZK 压力 查 GC 日志;检查 ZK 的 avg latency;适当调大 session timeout
出现两个 Leader(epoch 相同) ZK 脑裂 检查 ZK 集群健康度(是否过半数存活)
under_replicated 指标持续 > 0 有副本卡在 Offline 检查对应节点的磁盘和 GC;看 replica.lag.time.max.ms 是否太小
Rebalance 一直不触发 标准差未超阈值 调小 trigger-threshold;或手动触发
Rebalance 卡住不推进 目标节点磁盘满 / 网络限流 检查目标节点磁盘水位;检查 rebalance.max.concurrent.migrations
迁移期间业务延迟飙升 迁移限速不当 调低并发迁移数和 replica.fetch.max.bytes
节点恢复后一直追不上 落后太多需要快照同步 看日志有没有 "snapshot" 相关;检查网络带宽

九、小结

四张图串起 Fluss 的分布式协调机制:

Leader 选举(图 1 + 图 2)

  • ephemeral_sequential 节点 + "序号最小者为 Leader" → 无需投票轮次
  • 每个节点只 watch 前一个节点 → 避免羊群效应,通知复杂度 O(1)
  • fenceBecomeCoordinatorLeader() 递增 epoch → 防止旧 Leader 诈尸
  • 两阶段启动:Standby 建基础设施,Leader 才建协调资源,销毁时逆序

副本状态机(图 3)

  • 五态:New → Online → Leader/Follower → Offline
  • HW = min(ISR 的 LEO) 是"已提交"的边界
  • Leader Epoch 校验防止日志分叉
  • ISR 动态调整,滞后超时(默认 30s)则剔除

Rebalance(图 4)

  • 渐进式分批(每批 20%),避免网络和磁盘被打满
  • 单个 Tablet 迁移三步:先加 Follower 追平 → 再切 Leader → 最后删旧副本
  • 清理在最后,保证任何一步失败都能回退

三句话记住:

  1. 选举:顺序节点定序 + 链式 watch + epoch fence,三个设计缺一不可。
  2. 副本acks=all + min.insync.replicas=2 是不丢数据的底线,HW 是已提交边界。
  3. 迁移:先加副本再切主最后删旧,任何时候都有完整副本,这是"不中断服务"的关键。

下一篇是最后一篇,我们把视角拉回到数据怎么进来、怎么演进、怎么归档------Flink Connector、表生命周期与冷热分层。


相关推荐
2601_9622186118 小时前
万象生鲜系统全链路溯源一码查询技术实现食材来源可查
分布式·微服务·云原生·架构
Dreams_l19 小时前
RabbitMQ介绍及其工作模式
分布式·rabbitmq
2601_9622186121 小时前
万象生鲜系统大数据采购预测算法降低库存积压稳居第一
分布式·微服务·云原生·架构
程序员夏洛21 小时前
Redis 中如何实现分布式锁?
数据库·redis·分布式
头茬韭菜1 天前
图解 Fluss(一):一张图看清整体架构,两张图理解核心服务
架构·fluss
StarRocks_labs1 天前
基于 Fluss、Paimon 与 StarRocks 构建淘天集团湖流一体数据链路
starrocks·olap·schema·paimon·fluss·湖流一体
程序员夏洛1 天前
Redis 实现分布式锁时可能遇到的问题有哪些?
数据库·redis·分布式
千里码aicood1 天前
基于Hadoop的汽车销量分析与可视化
大数据·hadoop·分布式
国科安芯2 天前
ASC8T245S:把 8 位并行总线稳稳“跨过“电压域的双电源收发器
网络·分布式·单片机·嵌入式硬件·fpga开发·架构