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

分布式2(限流算法、分布式一致性算法、Zookeeper )

目录

限流算法

 固定窗口计数器(Fixed Window Counter)

滑动窗口计数器(Sliding Window Counter)

漏桶算法(Leaky Bucket)

令牌桶算法(Token Bucket)

令牌桶与漏桶的对比

分布式限流实现

限流框架推荐

根据业务场景选择合适的限流算法:

评估算法特性

1. 性能指标

2. 关键特性对比

Ngixn 控制速率/控制并发连接数

网关令牌桶限流

分布式一致性算法

常见算法

1. Paxos 算法

2. Raft 算法

3. ZAB 协议(ZooKeeper Atomic Broadcast)

4. Paxos 变体(Multi-Paxos、Fast Paxos)

5. 拜占庭容错算法(PBFT、PoW)

应用场景与实践

1. 分布式协调服务

2. 分布式数据库

3. 区块链

选择建议

常见误区与挑战

总结

算法对比

Zookeeper

数据结构

特点

核心功能​​​​​​​

统一配置管理

命名服务

分布式锁

核心特点

典型场景

底层技术支撑

集群管理

架构设计

典型应用场景

关键技术细节

优缺点

最佳实践

替代方案

总结

 Dubbo


限流算法

限流是分布式系统中控制流量的重要手段,用于防止系统因过载而崩溃。以下是几种常见的限流算法及其原理、优缺点和实现方式:

 固定窗口计数器(Fixed Window Counter)

  • 原理:将时间划分为固定大小的窗口,每个窗口内维护一个计数器。当请求到达时,若计数器超过阈值则拒绝请求。
  • 优点:实现简单,空间复杂度低(O (1))。
  • 缺点:存在临界问题(如窗口切换时可能出现突发流量)。
  • 示例代码
    import java.util.concurrent.atomic.AtomicInteger;public class FixedWindowRateLimiter {private final int capacity;           // 窗口容量private final long windowSizeMillis;  // 窗口大小(毫秒)private AtomicInteger count = new AtomicInteger(0);private long windowStartMillis;public FixedWindowRateLimiter(int capacity, long windowSizeMillis) {this.capacity = capacity;this.windowSizeMillis = windowSizeMillis;this.windowStartMillis = System.currentTimeMillis();}public synchronized boolean allow() {long currentTimeMillis = System.currentTimeMillis();// 窗口已过期,重置计数器if (currentTimeMillis - windowStartMillis > windowSizeMillis) {windowStartMillis = currentTimeMillis;count.set(0);}// 未超过容量,允许请求if (count.get() < capacity) {count.incrementAndGet();return true;}return false;}
    }
    

滑动窗口计数器(Sliding Window Counter)

  • 原理:将固定窗口划分为更小的时间片段,每个片段维护独立计数器。统计时,将所有片段的计数器累加作为当前窗口的计数。
  • 优点:解决了固定窗口的临界问题,更平滑地控制流量。
  • 缺点:实现复杂度高,空间复杂度增加(O (n),n 为片段数量)。
  • 示例代码
    import java.util.concurrent.ConcurrentLinkedQueue;public class SlidingWindowRateLimiter {private final int capacity;          // 窗口容量private final long windowSizeMillis; // 窗口大小(毫秒)private final ConcurrentLinkedQueue<Long> queue = new ConcurrentLinkedQueue<>();public SlidingWindowRateLimiter(int capacity, long windowSizeMillis) {this.capacity = capacity;this.windowSizeMillis = windowSizeMillis;}public boolean allow() {long currentTimeMillis = System.currentTimeMillis();// 移除过期的请求记录while (!queue.isEmpty() && currentTimeMillis - queue.peek() > windowSizeMillis) {queue.poll();}// 未超过容量,允许请求并记录时间if (queue.size() < capacity) {queue.offer(currentTimeMillis);return true;}return false;}
    }

漏桶算法(Leaky Bucket)

  • 原理:请求如同水流进入桶中,桶以固定速率处理请求。若桶满则溢出(拒绝请求)。
  • 优点:平滑流量,保证输出速率稳定。
  • 缺点:无法应对突发流量,即使系统此时有处理能力。
  • 示例代码

    public class LeakyBucketRateLimiter {private final int capacity;          // 桶容量private final double rate;           // 漏水速率(每秒处理请求数)private int water;                   // 当前水量(请求数)private long lastLeakTimeMillis;     // 上次漏水时间public LeakyBucketRateLimiter(int capacity, double rate) {this.capacity = capacity;this.rate = rate;this.water = 0;this.lastLeakTimeMillis = System.currentTimeMillis();}public synchronized boolean allow() {long currentTimeMillis = System.currentTimeMillis();// 计算这段时间漏出的水量long elapsedMillis = currentTimeMillis - lastLeakTimeMillis;double leakedWater = elapsedMillis * rate / 1000.0;water = Math.max(0, (int) (water - leakedWater));lastLeakTimeMillis = currentTimeMillis;// 桶未满,允许请求if (water < capacity) {water++;return true;}return false;}
    }

令牌桶算法(Token Bucket)

  • 原理:系统以固定速率生成令牌放入桶中,每个请求需获取一个令牌才能被处理。桶满时令牌溢出。
  • 优点:既能限制平均速率,又能应对突发流量(桶内有令牌时)。
  • 缺点:实现较复杂,需维护令牌生成逻辑。
  • 示例代码
    import java.util.concurrent.atomic.AtomicLong;public class TokenBucketRateLimiter {private final long capacity;          // 桶容量(最大令牌数)private final long rate;              // 令牌生成速率(每秒生成令牌数)private AtomicLong tokens;            // 当前令牌数private AtomicLong lastRefillTime;    // 上次生成令牌时间public TokenBucketRateLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens = new AtomicLong(capacity);this.lastRefillTime = new AtomicLong(System.currentTimeMillis());}public boolean allow() {refill();  // 先补充令牌return tokens.getAndUpdate(t -> t - 1) >= 1;}private void refill() {long now = System.currentTimeMillis();long lastTime = lastRefillTime.get();// 计算这段时间应生成的令牌数long generatedTokens = (now - lastTime) * rate / 1000;if (generatedTokens > 0) {// 使用CAS确保线程安全long newTokens = Math.min(capacity, tokens.get() + generatedTokens);if (tokens.compareAndSet(tokens.get(), newTokens)) {lastRefillTime.set(now);}}}
    }

使用示例

public class RateLimiterExample {public static void main(String[] args) throws InterruptedException {// 固定窗口:每秒允许10个请求FixedWindowRateLimiter fixedWindow = new FixedWindowRateLimiter(10, 1000);// 令牌桶:每秒生成5个令牌,桶容量10TokenBucketRateLimiter tokenBucket = new TokenBucketRateLimiter(10, 5);// 测试固定窗口System.out.println("=== 固定窗口测试 ===");for (int i = 0; i < 20; i++) {System.out.println("Request " + i + ": " + fixedWindow.allow());Thread.sleep(100); // 每100ms发送一个请求}// 测试令牌桶System.out.println("\n=== 令牌桶测试 ===");for (int i = 0; i < 20; i++) {System.out.println("Request " + i + ": " + tokenBucket.allow());Thread.sleep(100);}}
}

令牌桶与漏桶的对比

特性令牌桶算法漏桶算法
突发流量允许(桶内有令牌时)不允许(严格固定速率)
输出速率可变(取决于令牌生成和消耗)固定(漏出速率恒定)
实现复杂度较高(需维护令牌生成)较低(只需记录水量)
适用场景允许突发但平均速率受控的场景需要严格平滑流量的场景

分布式限流实现

在分布式系统中,限流通常需要基于中心化存储(如 Redis)实现:

  • 基于 Redis 的令牌桶
    import redis.clients.jedis.Jedis;public class DistributedTokenBucket {private final Jedis jedis;private final String key;private final long capacity;private final long rate;public DistributedTokenBucket(Jedis jedis, String key, long capacity, long rate) {this.jedis = jedis;this.key = key;this.capacity = capacity;this.rate = rate;}public boolean allow() {// Lua脚本实现原子操作String luaScript = "local tokens_key = KEYS[1] " +"local timestamp_key = KEYS[2] " +"local capacity = tonumber(ARGV[1]) " +"local rate = tonumber(ARGV[2]) " +"local now = tonumber(ARGV[3]) " +"local last_tokens = tonumber(redis.call('get', tokens_key) or capacity) " +"local last_refreshed = tonumber(redis.call('get', timestamp_key) or 0) " +"local delta = math.max(0, now - last_refreshed) " +"local filled_tokens = math.min(capacity, last_tokens + (delta * rate / 1000)) " +"local allowed = filled_tokens >= 1 " +"local new_tokens = filled_tokens " +"if allowed then " +"  new_tokens = filled_tokens - 1 " +"end " +"redis.call('set', tokens_key, new_tokens) " +"redis.call('set', timestamp_key, now) " +"return allowed";return (Long) jedis.eval(luaScript, 2, key + ":tokens", key + ":timestamp", String.valueOf(capacity), String.valueOf(rate), String.valueOf(System.currentTimeMillis())) == 1;}
    }

限流框架推荐

  • Sentinel:阿里巴巴开源,支持流量控制、熔断降级,集成 Spring Cloud。(生产环境推荐)
  • 使用 Sentinel

    // 引入依赖
    <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>1.8.6</version>
    </dependency>// 示例代码
    public class SentinelExample {public static void main(String[] args) {// 定义资源Entry entry = null;try {// 限流规则:每秒最多20个请求initFlowRules();// 获取资源入口entry = SphU.entry("resourceName");// 执行业务逻辑System.out.println("Processing request...");} catch (BlockException e) {// 被限流时的处理System.out.println("Request blocked by Sentinel");} finally {if (entry != null) {entry.exit();}}}private static void initFlowRules() {List<FlowRule> rules = new ArrayList<>();FlowRule rule = new FlowRule();rule.setResource("resourceName");rule.setCount(20); // 限流阈值rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS模式rules.add(rule);FlowRuleManager.loadRules(rules);}
    }
  • Resilience4j:轻量级 Java 库,支持限流、熔断、重试等。
  • Guava RateLimiter:Google Guava 提供的本地...
  • Redis + Lua:自定义分布式限流,通过 Lua 脚本保证原子...

根据业务场景选择合适的限流算法:

  • 固定窗口:简单但临界问题明显。
  • 滑动窗口:解决临界问题,适合平滑限流。
  • 漏桶:严格控制速率,适合需要稳定输出的场景。
  • 令牌桶:允许突发流量,适合大多数场景。
算法优点缺点适用场景
固定窗口实现简单临界问题明显对精度要求不高的场景
滑动窗口解决临界问题实现复杂需要更精确限流的场景
漏桶严格平滑流量无法应对突发流量需要稳定输出速率的场景
令牌桶允许突发流量实现复杂大多数场景,尤其是有突发流量
分布式限流跨节点统一限流依赖外部存储微服务架构下的全局限流

评估算法特性

1. 性能指标
算法时间复杂度空间复杂度突发流量支持实现难度
固定窗口O(1)O(1)差(临界问题)
滑动窗口O(n)O(n)
漏桶O(1)O(1)不支持
令牌桶O(1)O(1)支持
2. 关键特性对比
需求推荐算法
简单实现固定窗口、本地令牌桶
严格速率控制漏桶
突发流量处理令牌桶、滑动窗口
分布式场景Redis + 令牌桶 / Lua 脚本
熔断降级一体化Sentinel、Resilience4j

限流实现方式:

①Tomcat:可以设置最大连接数(maxThreads) 单机

②Nginx 漏桶算法

控制速率(突发容量),让请求以固定速率处理请求,可以应对突发容量。

控制并发数,限制单个 IP和链接数并发链接总数。

③网关令牌桶算法

在springCloud gateway中支持局部过滤器RequestRateLimiter来做限流,可以根据ip或者路径进行限流,可以设置每秒填充的平均速率和令牌的总容量。

 ④自定义拦截器

计数器 (固定窗口、滑动窗口)

漏桶:固定速率漏出请求,多余请求等待或者抛出。

令牌桶:固定速率生成令牌,存入令牌桶,桶满后暂停生成。请求需要到令牌桶申请到令牌,才能被服务处理。

Ngixn 控制速率/控制并发连接数

网关令牌桶限流

分布式一致性算法

常见算法

1. Paxos 算法

  • 核心思想:通过多轮投票达成共识,分为提案(Prepare)和接受(Accept)两个阶段。
  • 角色:提议者(Proposer)、接受者(Acceptor)、学习者(Learner)。
  • 流程
    1. Prepare 阶段:Proposer 发送提案编号给 Acceptors。
    2. Accept 阶段:若多数 Acceptors 响应,Proposer 发送值给 Acceptors。
    3. Learn 阶段:多数 Acceptors 接受后,值被确认。
  • 优点:理论完备,被广泛研究。
  • 缺点:实现复杂,难以理解。

2. Raft 算法

  • 核心思想:通过选举 leader 简化共识过程,leader 负责日志复制。
  • 角色:Leader、Follower、Candidate( 英/ˈkændɪdət/候选人)。
  • 流程
    1. Leader 选举:超时未收到心跳的 Follower 变为 Candidate,发起选举。
    2. 日志复制:Leader 接收客户端请求,复制日志到 Follower。
    3. 安全机制:通过任期(Term)和日志匹配原则保证安全性。
  • 优点:易于理解,实现简单。
  • 缺点:不适合拜占庭故障场景。

3. ZAB 协议(ZooKeeper Atomic Broadcast)

  • 核心思想:ZooKeeper 使用的原子广播协议,类似 Raft 但更注重事务顺序。
  • 角色:Leader、Follower、Observer。
  • 流程
    1. 崩溃恢复:选举新 Leader,同步最新事务。
    2. 消息广播:Leader 接收事务,生成 ZXID,广播给 Follower。
  • 应用:ZooKeeper 分布式协调服务。

4. Paxos 变体(Multi-Paxos、Fast Paxos)

  • Multi-Paxos:优化 Paxos,减少通信开销,常用于分布式数据库(如 Spanner)。
  • Fast Paxos:允许在某些情况下跳过 Prepare 阶段,提高性能。

5. 拜占庭容错算法(PBFT、PoW)

  • PBFT(Practical Byzantine Fault Tolerance)
    • 在存在恶意节点(最多 1/3 故障)的情况下达成共识。
    • 流程:预准备(Pre-Prepare)、准备(Prepare)、提交(Commit)。
  • PoW(Proof of Work)
    • 比特币使用的共识机制,通过算力竞争达成共识。
    • 优点:抗攻击能力强;缺点:能耗高、效率低。

应用场景与实践

1. 分布式协调服务
  • ZooKeeper:基于 ZAB 协议,用于服务注册发现、配置管理。
  • etcd:基于 Raft 协议,用于 Kubernetes 的核心数据存储。
2. 分布式数据库
  • Spanner:Google 的全球分布式数据库,使用 Multi-Paxos。
  • CockroachDB:开源分布式 SQL 数据库,使用 Raft。
3. 区块链
  • 比特币:基于 PoW 共识,处理拜占庭故障。
  • 联盟链:如 Hyperledger Fabric,使用 PBFT 或 Raft。

选择建议

  1. 非拜占庭故障场景

    • 优先选择 Raft(实现简单)或 ZAB(适合顺序事务)。
    • 案例:etcd、Consul、ZooKeeper。
  2. 拜占庭故障场景

    • 低性能需求:PoW(比特币)。
    • 高性能需求:PBFT 及其变体(如 Hyperledger Fabric)。
  3. 性能敏感场景

    • 使用 Fast Paxos 或优化的 Raft 实现。

常见误区与挑战

  1. 过度设计

    • 简单场景无需复杂算法,如单机系统使用本地锁即可。
  2. 忽略网络分区

    • 在分区时需权衡可用性和一致性(如 MongoDB 的 write concern 设置)。
  3. 性能与一致性的权衡

    • 强一致性(如 Paxos)可能导致延迟增加,需根据业务调整。

总结

分布式一致性算法是构建可靠分布式系统的基石,选择时需考虑:

  • 故障类型:非拜占庭(如服务器崩溃)或拜占庭(如恶意攻击)。
  • 性能需求:吞吐量和延迟要求。
  • 实现复杂度:团队技术栈和维护成本。

优先使用成熟框架(如 etcd、ZooKeeper),避免自行实现底层算法。在实践中,需通过压测和故障注入测试验证算法的可靠性。

算法对比

算法容错类型复杂度性能应用场景
Paxos非拜占庭故障理论研究、分布式数据库
Raft非拜占庭故障分布式协调(etcd、Consul)
ZAB非拜占庭故障ZooKeeper
PBFT拜占庭故障联盟链(Hyperledger)
PoW拜占庭故障公有链(比特币、以太坊)

非拜占庭故障(也称良性故障)指节点故障后表现出可预测、非恶意的行为。这类故障通常是由于硬件故障、软件崩溃、网络中断等原因导致,节点不会故意发送错误信息或破坏系统一致性。

非拜占庭故障(也称良性故障)指节点故障后表现出可预测、非恶意的行为。这类故障通常是由于硬件故障、软件崩溃、网络中断等原因导致,节点不会故意发送错误信息或破坏系统一致性。

Zookeeper

ZooKeeper 是 Apache 开源的分布式协调服务,为分布式系统提供统一命名服务、配置管理、集群协调、分布式锁等核心功能。以下是关于 ZooKeeper 的详细介绍:

数据结构

跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode(节点的存放数据上限为1M)。每一个节点可以通过路径来标识,结构图如下:

临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除

持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除

ZooKeeper和Redis一样,也是C/S结构(分成客户端和服务端)

临时有序、持久有序

监听通知机制

  Zookeeper 上创建的节点,可以对这些节点绑定监听事件,比如可以监听节点数据变更、节点删除、子节点状态变更等事件,通过这个事件机制,可以基于  Zookeeper 实现分布式锁、集群管理等功能。

特点

  1. 集中存储
    将所有服务的配置信息(如数据库连接串、参数阈值)存储在 ZooKeeper 的 Znode 中,形成统一的配置中心。
  2. 实时更新
    通过 Watcher 机制,当配置变更时,所有订阅该配置的客户端会立即收到通知,并获取最新值。
  3. 版本控制
    每个 Znode 的元数据包含 version 字段,配置更新时版本号自动递增,支持回滚和审计。
  4. 高可用性
    集群模式下,Leader 节点处理写请求,Follower 节点提供读服务,确保配置始终可访问。

从设计模式角度来看,zk是一个基于观察者设计模式的框架,它负责管理跟存储大家都关心的数据,然后接受观察者的注册,数据发现生变化zk会通知在zk上注册的观察者做出反应。

典型场景

  • 微服务配置:如 Spring Cloud 应用通过 ZooKeeper 获取数据库连接池参数。
  • 分布式系统参数调优:如 Kafka 集群的 replication.factor 参数动态调整。
  • 灰度发布:通过配置中心控制新功能的开关,实现流量灰度。

ZooKeeper 树的核心价值

这棵 “树” 通过 层级结构 组织数据,通过 节点类型 适配不同业务场景,通过 Watcher 实现事件驱动,通过 ZAB 协议 保障一致性,最终成为分布式系统中 协调与元数据管理 的基础设施。理解其节点特性和设计逻辑,是掌握 ZooKeeper 分布式锁、配置中心、Leader 选举等核心功能的关键。

 Znode 按 生命周期 和 顺序性 分为 4 种类型,每种类型对应不同的业务场景:

节点类型特点典型用途
持久节点(Persistent)节点创建后一直存在,直到主动删除(与客户端会话无关)。存储固定配置(如 /config/server1)。
持久顺序节点(Persistent_Sequential)在持久节点基础上,名称自动附加递增序号(如 worker- → worker-0000000001)。分布式锁序号生成、任务队列分配。
临时节点(Ephemeral)节点与会话绑定,会话结束后自动删除(不能有子节点)。服务健康检查(如 /services/server1 存活状态)。
临时顺序节点(Ephemeral_Sequential)在临时节点基础上,名称自动附加递增序号。公平锁竞争(如多个客户端竞争锁时按序号排序)。

数据与元数据的强关联性

每个 Znode 包含 数据内容(Data) 和 元数据(Metadata),元数据记录节点的关键信息:

  • 数据内容
    存储少量业务数据(如配置参数、锁状态),通过 getData 接口读取,通过 setData 接口更新。
  • 元数据字段
    • czxid:创建节点的事务 ID(ZXID,全局唯一)。
    • mzxid:最后一次修改节点的事务 ID。
    • ctime:节点创建时间(毫秒级时间戳)。
    • mtime:节点最后一次修改时间。
    • version:数据版本号(每次修改自增,用于乐观锁控制)。
    • cversion:子节点版本号(子节点增删时自增)。
    • aclVersion:权限版本号(ACL 变更时自增)。
    • ephemeralOwner:若为临时节点,记录创建该节点的会话 ID;否则为 0

Watcher 机制:事件驱动的响应式模型

  • 监听节点变化
    客户端可通过 exists/getData/getChildren 接口为节点注册 Watcher(监听器),监听以下事件:
    • 节点创建(NodeCreated
    • 节点删除(NodeDeleted
    • 节点数据更新(NodeDataChanged
    • 子节点变更(NodeChildrenChanged
  • 一次性触发特性
    Watcher 事件触发后自动失效,需重新注册才能继续监听(避免持续占用资源)。
  • 典型应用
    动态感知配置变更(如配置中心)、服务上下线通知(如服务注册发现)。

一致性与顺序性保证

  • 线性一致性(Linearizability)
    写操作由 Leader 节点通过 ZAB 协议 广播到多数派节点,确保所有节点数据一致。
  • 全局有序性
    所有事务(写操作)通过 ZXID 全局唯一编号,保证按顺序执行,读操作可选择从 Leader 节点获取最新数据(默认从 Follower 节点读取,可能存在毫秒级延迟)。

核心功能

  1. 配置管理

    • 集中存储分布式系统的配置信息,支持动态更新(如 Kafka 的 broker 配置)。
  2. 命名服务

    • 提供分布式命名空间,类似文件系统目录结构(如服务注册发现中的服务路径)。
  3. 集群协调

    • Leader 选举(如 Hadoop YARN ResourceManager 的主备切换)。
    • 状态同步(如分布式任务的全局状态)。
  4. 分布式锁

    • 实现公平锁、读写锁(如 HBase 的分布式锁机制)。
  5. 健康检查

    • 通过临时节点(Ephemeral Node)监控服务存活状态(如 Dubbo 的服务注册发现)。

统一配置管理

比如我们现在有三个系统A、B、C,他们有三份配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,然后,这三份配置又非常类似,很多的配置项几乎都一样。

此时,如果我们要改变其中一份配置项的信息,很可能其他两份都要改。并且,改变了配置项的信息很可能就要重启系统

于是,我们希望把ASystem.yml、BSystem.yml、CSystem.yml相同的配置项抽取出来成一份公用的配置common.yml,并且即便common.yml改了,也不需要系统A、B、C重启。

做法:我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应。

参考资料:

基于zookeeper实现统一配置管理

基于zookeeper实现统一配置管理-CSDN博客

命名服务

命名服务是指通过指定的名字来获取资源或者服务的地址,利用 zk 创建一个全局唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

分布式锁

锁的概念在这我就不说了,如果对锁概念还不太了解的同学,可参考下面的文章

  1. 锁?分布式锁?乐观锁?行锁?

我们可以使用ZooKeeper来实现分布式锁,那是怎么做的呢??下面来看看:

系统A、B、C都去访问/locks节点

访问的时候会创建带顺序号的临时(EPHEMERAL_SEQUENTIAL)节点,比如,系统A创建了id_000000节点,系统B创建了id_000002节点,系统C创建了id_000001节点。

接着,拿到/locks节点下的所有子节点(id_000000,id_000001,id_000002),判断自己创建的是不是最小的那个节点

如果是,则拿到锁。

释放锁:执行完操作后,把创建的节点给删掉

如果不是,则监听比自己要小1的节点变化

举个例子:

  • A拿到/locks节点下的所有子节点,经过比较,发现自己(id_000000),是所有子节点最小的。所以得到锁
  • B拿到/locks节点下的所有子节点,经过比较,发现自己(id_000002),不是所有子节点最小的。所以监听比自己小1的节点id_000001的状态
  • C拿到/locks节点下的所有子节点,经过比较,发现自己(id_000001),不是所有子节点最小的。所以监听比自己小1的节点id_000000的状态
  • A执行完操作以后,将自己创建的节点删除(id_000000)。通过监听,系统C发现id_000000节点已经删除了,发现自己已经是最小的节点了,于是顺利拿到锁
  • 系统B如上

公平竞争与有序执行

核心特点
  1. 基于临时顺序节点
    • 客户端在锁路径(如 /locks/distributed-lock)下创建 临时顺序节点
    • 通过比较节点序号,最小序号的节点获得锁,其余节点监听前一个节点的删除事件。
  2. 公平性保证
    节点序号严格递增,先创建的节点优先获得锁,避免 “惊群效应”。
  3. 异常自动释放
    若持有锁的客户端崩溃,其临时节点自动删除,下一个节点自动获得锁。
  4. 可重入锁支持
    通过记录客户端 ID 和加锁次数,支持同一客户端在锁未释放时重复加锁。
典型场景
  • 分布式资源竞争:如多个任务同时操作 HBase 表,需通过锁保证互斥。
  • 全局唯一性约束:如生成全局唯一订单号,确保同一时刻只有一个节点生成 ID。
  • 批量任务调度:如定时任务在集群中仅需一个节点执行。

底层技术支撑

应用场景依赖的 ZooKeeper 特性
配置管理1. 持久节点存储配置数据
2. Watcher 监听数据变更
3. 版本控制保证变更可追溯
命名服务1. 临时节点自动删除机制(健康检查)
2. 顺序节点保证服务列表有序
3. 集群高可用确保服务地址始终可访问
分布式锁1. 临时顺序节点实现公平竞争
2. Watcher 监听锁释放事件
3. ZAB 协议保证操作原子性

集群管理

所谓集群管理无在乎两点:是否有机器退出和加入、选举master。

监听节点就行。 

我们三个系统A、B、C为例,在ZooKeeper中创建临时节点即可:

只要系统A挂了,那/groupMember/A这个节点就会删除,通过监听groupMember下的子节点,系统B和C就能够感知到系统A已经挂了。(新增也是同理)

除了能够感知节点的上下线变化,ZooKeeper还可以实现动态选举Master的功能。(如果集群是主从架构模式下)

原理也很简单,如果想要实现动态选举Master的功能,Znode节点的类型是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)就好了。

Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了。

 ZK的角色:

    1、leader(主节点) ,不是固定的,启动后根据选举算法选出

    2、follower(从节点)

  https://zhuanlan.zhihu.com/p/62526102

架构设计

  1. 集群模式

    • Quorum:由奇数个节点组成(如 3、5、7),确保多数派(Quorum)可用。
    • 角色
      • Leader:处理写请求,执行原子广播(ZAB 协议)。
      • Follower:接收客户端读请求,参与选举和提案投票。
      • Observer:扩展读性能,不参与选举(可选角色)。
  2. 数据模型

    • ZNode:类似文件系统的节点,存储少量数据(默认≤1MB)。
    • 类型
      • 持久节点(Persistent):手动删除才会消失。
      • 临时节点(Ephemeral):会话结束自动删除(用于健康检查)。
      • 顺序节点(Sequential):创建时自动附加递增序号(用于分布式锁)。
  3. ZAB 协议(ZooKeeper Atomic Broadcast)

    • 原子广播:确保事务按顺序在集群中执行。
    • 崩溃恢复:Leader 故障时,快速选举新 Leader 并同步数据。

典型应用场景

  1. 服务注册与发现

    • 服务提供者将地址注册到 ZooKeeper(如 Dubbo、Spring Cloud Zookeeper)。
    • 服务消费者订阅节点变化,动态感知提供者上线 / 下线。
  2. 分布式锁

    • 实现方式
      // 创建临时顺序节点
      String lockPath = zk.create("/locks/lock-", CreateMode.EPHEMERAL_SEQUENTIAL);
      // 获取最小序号节点,判断是否获得锁
      List<String> children = zk.getChildren("/locks", false);
      if (lockPath.equals("/locks/" + Collections.min(children))) {// 获得锁
      } else {// 监听前一个节点zk.exists(previousNode, true);
      }
      
  3. 配置中心

    • 应用启动时从 ZooKeeper 加载配置,监听配置变更事件。
    • 如 Kafka 的 broker 配置、HBase 的集群参数。
  4. 主备切换

    • 通过临时节点实现主节点选举(如 Hadoop YARN 的 ResourceManager 高可用)。

关键技术细节

  1. 会话(Session)

    • 客户端与 ZooKeeper 建立的 TCP 连接,超时自动失效。
    • 临时节点与会话绑定,会话结束时自动删除。
  2. Watcher 机制

    • 客户端可监听 ZNode 变化(创建、删除、数据更新)。
    • 事件触发后,Watcher 自动失效,需重新注册(一次性触发)。
  3. ZXID(ZooKeeper Transaction ID)

    • 全局唯一事务 ID,确保事务顺序(如0x100000001)。
    • Leader 生成 ZXID,保证递增性和原子性。
  4. 一致性保证

    • 线性一致性(Linearizability):写操作通过 Leader 广播,读操作可配置是否从 Leader 获取(确保最新数据)。

优缺点

优点缺点
高可用性(集群模式)不适合存储大量数据(ZNode≤1MB)
强一致性(ZAB 协议)写性能随节点数增加而下降
丰富的 Watcher 机制客户端 API 较底层,使用复杂
社区活跃,广泛集成(Hadoop、Kafka 等)配置不当易导致脑裂(Split-Brain)

最佳实践

  1. 集群规模

    • 推荐奇数节点(3、5、7),容忍(N-1)/2节点故障(如 5 节点容忍 2 节点故障)。
  2. 配置优化

    • tickTime:基本时间单位(默认 2000ms),影响会话超时和选举周期。
    • initLimit:Follower 与 Leader 同步的最大时间(initLimit * tickTime)。
    • syncLimit:Follower 与 Leader 通信的最大延迟。
  3. 避免脑裂

    • 配置electionAlg=3(默认),使用 TCP 选举协议。
    • 确保多数派(Quorum)可用。
  4. 监控与告警

    • 监控zkServer.sh status输出,检查集群健康状态。
    • 告警指标:连接数、请求延迟、Leader 变更频率。

替代方案

  1. etcd

    • 基于 Raft 协议,API 更友好,性能更高(尤其写操作)。
    • 适用于 Kubernetes(默认存储组件)、服务发现(如 Consul)。
  2. Consul

    • 提供服务发现、健康检查、KV 存储一体化解决方案。
    • 支持多数据中心,分布式锁实现更简单。
  3. Nacos

    • 阿里巴巴开源,支持动态配置管理、服务注册发现。
    • 更适合微服务架构,中文文档完善。

总结

ZooKeeper 是分布式系统的 “协调基石”,尤其适合需要强一致性、高可用的场景(如分布式锁、配置中心)。但由于其 API 较底层,维护成本较高,在轻量级场景中可考虑 etcd 或 Consul。在使用时需注意集群规模、配置参数调优,避免单点故障和脑裂问题。

定义:简单的说zookepper=文件系统+监听通知机制

是一个分布式协调服务,CP为了分布式应用提供了一致性服务的软件,

可以基于它实现统一配置管理、命名服务、分布式锁、集群管理负载均衡、分布式队列、Master 选举等。

场景:

配置管理 【数据发布与订阅配置中心

                数据发布到zk节点上,供订阅者动态获取数据,

                实时更新watch机制。比如全局配置信息、地址列表。K-V结构。

命名服务: 通过名字获取服务资源或者服务地址。

集群管理 :是否有机器退出和加入、选举mater。

分布式锁: 临时有序节点 ,监听器

                【临时zk 死掉会释放锁,有序 就是先获取最小的,然后依次执行】

持久化/临时目录  -->有序

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点 进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了

ZK 通常指 ZooKeeper,是一个分布式协调服务,在分布式系统中起着关键作用。

从架构上看,ZooKeeper 由服务器集群组成,集群中的节点分为领导者(Leader)、跟随者(Follower)和观察者(Observer)。Leader 负责处理事务请求,比如写操作,同时管理整个集群的状态变化。Follower 用于处理客户端的读请求,并与 Leader 保持数据同步,还参与领导者选举。Observer 可以接收客户端的读请求,能提高集群的读性能,其数据也从 Leader 同步,但不参与选举过程。

在数据存储方面,ZooKeeper 使用类似文件系统的树形数据结构,其中的每个节点称为 znode。znode 可以存储数据,数据格式一般是字节数组,并且每个 znode 都有一个唯一的路径来标识。znode 有多种类型,包括持久节点、临时节点和顺序节点。持久节点在创建后会一直存在,除非被显式删除;临时节点在创建它的客户端会话结束时自动删除,可用于实现分布式锁等功能;顺序节点在创建时会在节点名后加上一个递增的数字,常被用于实现分布式队列等功能。

ZooKeeper 有重要的应用场景。在分布式锁的应用中,通过在 ZooKeeper 上创建临时顺序节点,多个客户端竞争锁时,只有序号最小的节点对应的客户端获得锁使用完后删除节点释放锁

配置管理上,系统的配置信息可以集中存储在 ZooKeeper 的 znode 中,客户端可以对配置节点进行监听,当配置发生改变时,ZooKeeper 会通知客户端,实现配置的动态更新。

对于服务发现,服务提供者将自己的服务信息注册到 ZooKeeper 的某个 znode 下,服务消费者通过查询该节点获取服务提供者列表,并且能通过监听机制及时知晓服务提供者的变化。

此外,ZooKeeper 在一些大型分布式系统中有广泛应用。比如在 Hadoop 生态系统中,它被用于协调多个组件之间的资源分配、任务调度和状态管理等。在 Kubernetes 中,也可以用于集群的管理和协调,保障集群的稳定运行。

 Dubbo

 Dubbo,它是一款高性能、轻量级的开源微服务框架,以下是详细介绍:

待续

相关文章:

  • 牛顿均差知识
  • 负进制转换
  • STC32G12K128实战:串口通信
  • 计算机网络-MPLS LDP基础实验配置
  • 易学探索助手-个人记录(十)
  • Kuka AI音乐AI音乐开发「人声伴奏分离」 —— 「Kuka Api系列|中文咬字清晰|AI音乐API」第6篇
  • python打卡day25
  • 数字高程模型(DEM)公开数据集介绍与下载指南
  • vscode extention踩坑记
  • 七部门:设立“国家创业投资引导基金”,优先支持取得关键核心技术突破的科技型企业上市融资
  • 学会使用ai作图
  • c++在头文件中声明全局的变量
  • TNNLS-2020《Autoencoder Constrained Clustering With Adaptive Neighbors》
  • Deep Learning(手写字识别 - CNN)
  • 阿里的库存秒杀实现与Inventory Hint技术解析
  • Python 接入DeepSeek
  • 高等数学第七章---微分方程(§7.1-§7.3微分方程概念、一阶微分方程、一阶微分线性方程)
  • 生成式人工智能认证(GAI认证)官网 - 全国统一认证中文服务平台上线
  • 一种资源有限单片机处理cJSON数据的方法
  • WordPress 文章和页面:它们的区别是什么?
  • 押井守在30年前创造的虚拟世界何以比当下更超前?
  • 秘鲁总理辞职
  • MSCI中国指数5月调整:新增5只A股、1只港股
  • 牛市早报|中方调整对美加征关税措施,五部门约谈外卖平台企业
  • 大英博物馆展歌川广重:他是梵高最钟爱的浮世绘名家
  • 国家林业和草原局原党组成员、副局长李春良接受审查调查