您的位置:首页 > 汽车 > 新车 > Zookeeper

Zookeeper

2024/9/8 12:32:53 来源:https://blog.csdn.net/Casual_Lei/article/details/140394744  浏览:    关键词:Zookeeper

Zookeeper是一个分布式协调服务,用于管理和协调分布式应用程序的组件。它提供了集中式的服务,用于维护配置信息、命名、分布式同步和组服务。Zookeeper可以帮助开发人员简化分布式应用的设计和实现。

Zookeeper的核心概念

  1. 节点(ZNode)

    • Zookeeper的数据模型类似于文件系统,由一棵层次化的节点树组成。
    • 每个节点称为ZNode,可以存储数据和子节点。
    • ZNode有两种类型:临时节点(Ephemeral)和持久节点(Persistent)。
      • 临时节点:会话结束时自动删除。
      • 持久节点:需要明确删除操作才能删除。
  2. 会话(Session)

    • 客户端与Zookeeper服务器之间的连接称为会话。
    • 会话是有超时时间的,客户端需要定期发送心跳来维持会话。
  3. 版本(Version)

    • 每个ZNode都有版本信息,包括数据版本(dataVersion)、子节点版本(cversion)和ACL版本(aversion)。
    • 版本信息在更新时自动递增,用于实现乐观锁机制。
  4. 监视(Watchers)

    • 客户端可以在ZNode上设置监视器,当ZNode发生变化时,客户端会收到通知。
    • 监视是一次性的,需要重新设置。

Zookeeper的工作原理

  1. 集群架构

    • Zookeeper通常部署为集群,称为Zookeeper Ensemble。
    • 集群中的每个服务器称为一个节点,节点分为领导者(Leader)和跟随者(Follower)。
    • Leader负责处理写请求,并同步到Followers,Followers负责处理读请求。
  2. 一致性协议

    • Zookeeper使用Zab协议(Zookeeper Atomic Broadcast)来保证集群的一致性。
    • Zab协议类似于Paxos协议,确保在Leader和Follower之间的状态同步和数据一致性。
  3. 数据复制

    • Zookeeper的每个节点都维护一个内存中的数据副本,通过事务日志和快照机制保证数据的持久性。
    • 当Leader接收到写请求时,它会生成事务ID(ZXID),并将请求广播给所有Followers进行复制。

Zookeeper的应用场景

  1. 配置管理

    • 集中式存储和管理配置信息,确保分布式系统中的各个组件使用一致的配置信息。
  2. 命名服务

    • 提供分布式命名服务,将资源名称映射到物理地址,实现动态服务发现。
  3. 分布式锁

    • 通过创建临时节点,实现分布式锁,确保在分布式环境中只有一个客户端可以访问共享资源。
  4. 集群管理

    • 管理集群中的节点状态,监控节点的加入和离开,实现高可用性和负载均衡。
  5. Leader选举

    • 在分布式系统中,通过Zookeeper实现Leader选举,确保系统中只有一个主节点进行操作。

在Spring Boot中集成Zookeeper

在Spring Boot中,可以使用Spring Cloud Zookeeper来简化与Zookeeper的集成。

  1. 添加依赖

    <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zookeeper</artifactId>
    </dependency>
    
  2. 配置Zookeeper连接信息
    application.yml文件中配置Zookeeper服务器地址:

    spring:cloud:zookeeper:connect-string: localhost:2181
    
  3. 使用Zookeeper进行配置管理
    通过注解@EnableZookeeperConfig启用Zookeeper配置管理:

    @SpringBootApplication
    @EnableZookeeperConfig
    public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
    }
    
  4. 使用Zookeeper进行服务注册和发现
    通过注解@EnableDiscoveryClient启用服务注册和发现:

    @SpringBootApplication
    @EnableDiscoveryClient
    public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
    }
    

Zookeeper是一个强大的分布式协调服务,广泛应用于分布式系统的配置管理、命名服务、分布式锁、集群管理和Leader选举等场景。通过Spring Cloud Zookeeper,可以简化与Zookeeper的集成,实现分布式系统的高可用性和一致性。

使用Zookeeper实现分布式锁的方案

临时无序节点 + 重试(自旋)- 非公平锁

实现步骤

  1. 初始化锁目录

    • 确保锁目录存在,例如 /locks。如果不存在,则创建。
  2. 创建临时无序节点

    • 每个客户端尝试在 /locks 目录下创建一个临时无序节点,表示锁的请求。
    String lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    
  3. 尝试获取锁

    • 获取 /locks 目录下所有子节点,检查当前是否只有自己一个节点存在。
    List<String> children = zk.getChildren("/locks", false);
    if (children.size() == 1 && children.contains(lockPath.substring("/locks/".length()))) {// 获取到锁
    } else {// 没有获取到锁,自旋重试
    }
    
  4. 自旋重试

    • 如果没有获取到锁,则进行自旋重试,直到获取到锁为止。
    while (true) {List<String> children = zk.getChildren("/locks", false);if (children.size() == 1 && children.contains(lockPath.substring("/locks/".length()))) {// 获取到锁break;} else {// 等待一段时间再重试Thread.sleep(100);}
    }
    
  5. 释放锁

    • 删除自己创建的临时节点,释放锁。
    zk.delete(lockPath, -1);
    

代码示例

以下是一个简单的分布式锁实现示例:

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;import java.util.List;public class DistributedLock {private ZooKeeper zk;private String lockPath;private static final int SESSION_TIMEOUT = 30000;public DistributedLock(String zkHost) throws Exception {this.zk = new ZooKeeper(zkHost, SESSION_TIMEOUT, event -> {});Stat stat = zk.exists("/locks", false);if (stat == null) {zk.create("/locks", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}}public void acquireLock() throws Exception {lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);while (true) {List<String> children = zk.getChildren("/locks", false);if (children.size() == 1 && children.contains(lockPath.substring("/locks/".length()))) {System.out.println("Acquired lock: " + lockPath);return;} else {Thread.sleep(100);}}}public void releaseLock() throws Exception {zk.delete(lockPath, -1);System.out.println("Released lock: " + lockPath);}
}

优点

  • 实现简单:无需处理复杂的顺序和监听逻辑,代码简洁明了。
  • 适用于非公平锁场景:在一些应用场景中,锁的公平性并不是必须的,这种实现方法可能会更适用。

缺点

  • 不公平:锁的获取顺序不保证先来先得,可能会导致饥饿现象。
  • 性能问题:自旋重试机制在高并发场景下可能会增加Zookeeper的负载和网络流量。
  • 资源浪费:自旋重试会导致资源浪费,特别是在锁竞争激烈的情况下,频繁的重试会占用大量CPU和网络资源。

这种非公平锁的实现适合一些对锁公平性要求不高的应用场景,但在高并发和资源竞争激烈的场景下,可能需要考虑更复杂的实现方法,如临时顺序节点和监听器结合的方法。

临时顺序节点 + watch - 公平锁

实现步骤

  1. 初始化锁目录

    • 确保锁目录存在,例如 /locks。如果不存在,则创建。
  2. 创建临时顺序节点

    • 每个客户端尝试在 /locks 目录下创建一个临时顺序节点,表示锁的请求。
    String lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    
  3. 获取所有子节点并排序

    • 获取 /locks 目录下所有子节点,并按顺序排序。
    List<String> children = zk.getChildren("/locks", false);
    Collections.sort(children);
    
  4. 判断是否获取到锁

    • 如果当前节点是最小节点,则获取到锁。
    • 如果不是最小节点,则找到比当前节点小的前一个节点,并监听该节点的删除事件。
    String thisNode = lockPath.substring("/locks/".length());
    int index = children.indexOf(thisNode);
    if (index == 0) {// 获取到锁
    } else {String prevNode = children.get(index - 1);Stat stat = zk.exists("/locks/" + prevNode, new Watcher() {@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted) {// 上一个节点被删除,尝试获取锁acquireLock();}}});if (stat == null) {// 上一个节点已经不存在,重试获取锁acquireLock();}
    }
    
  5. 释放锁

    • 删除自己创建的临时顺序节点,释放锁。
    zk.delete(lockPath, -1);
    

代码示例

以下是一个简单的公平锁实现示例:

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;import java.util.Collections;
import java.util.List;public class DistributedFairLock {private ZooKeeper zk;private String lockPath;private String thisNode;private static final int SESSION_TIMEOUT = 30000;public DistributedFairLock(String zkHost) throws Exception {this.zk = new ZooKeeper(zkHost, SESSION_TIMEOUT, event -> {});Stat stat = zk.exists("/locks", false);if (stat == null) {zk.create("/locks", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}}public void acquireLock() throws Exception {this.lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);this.thisNode = lockPath.substring("/locks/".length());while (true) {List<String> children = zk.getChildren("/locks", false);Collections.sort(children);int index = children.indexOf(thisNode);if (index == 0) {System.out.println("Acquired lock: " + lockPath);return;} else {String prevNode = children.get(index - 1);Stat stat = zk.exists("/locks/" + prevNode, new Watcher() {@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted) {try {acquireLock();} catch (Exception e) {e.printStackTrace();}}}});if (stat == null) {acquireLock();} else {synchronized (this) {wait();}}}}}public void releaseLock() throws Exception {zk.delete(lockPath, -1);System.out.println("Released lock: " + lockPath);}
}

优点

  • 公平性:锁的获取顺序严格按照节点创建的顺序,保证公平性。
  • 效率高:只有在前一个节点释放锁时,才会尝试获取锁,减少不必要的重试。

缺点

  • 复杂度高:实现相对复杂,需要处理节点的监听和重试逻辑。
  • ZooKeeper负载:在高并发场景下,ZooKeeper的负载可能较高。

这种实现方式适合对锁的公平性要求较高的应用场景,如订单系统、队列系统等。通过临时顺序节点和Watch机制,确保了锁的获取顺序,有效避免了饥饿现象。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com