notes notes
首页
读书笔记
系统设计
项目实战
学习笔记
源码
运维
其它
极客时间 (opens new window)
GitHub (opens new window)
首页
读书笔记
系统设计
项目实战
学习笔记
源码
运维
其它
极客时间 (opens new window)
GitHub (opens new window)
  • 并发编程

    • 并发编程
    • 多线程
    • 高级篇
  • 设计模式

    • 设计模式
  • 网络编程

    • Netty

      • NIO基础
      • Netty入门
      • Netty进阶
      • 优化与源码
  • 源码篇

    • 环境搭建
    • Spring
  • 云原生

    • Kubernetes
    • Helm
  • ElasticSearch

    • ElasticSearch
  • Java 虚拟机

    • 深入拆解 Java 虚拟机
    • JVM与GC调优
  • MQ

    • RabbitMQ

      • RabbitMQ笔记
      • RabbitMQ集群搭建文档
  • Redis

    • Redis进阶
  • ShardingSphere

    • Sharding-JDBC
  • SpringCloud

    • SpringCloud
  • ZooKeeper

    • ZooKeeper
      • 概述
        • 特点
        • 数据结构
        • 应用场景
      • 安装
        • 单机安装
        • 配置文件
        • 集群安装
      • 选举机制
        • 第一次选举
        • 非第一次选举
      • 客户端命令
        • 常用命令
        • 节点信息
        • 节点类型
      • 监听器原理
      • zookeeper jar包操作
      • 客户端向服务端写数据流程
        • 写入请求直接发送给Leader节点
        • 写入请求发送给follower节点
      • 服务器动态上下线
      • 分布式锁案例
      • 拜占庭将军问题
        • Paxos算法
        • ZAB协议
        • CAP 理论
  • 学习笔记
  • ZooKeeper
starry
2023-08-03
目录

ZooKeeper

版本3.5.7

# 概述

https://zookeeper.apache.org/ (opens new window) Zookeeper 是一个开源的分布式的,为分布式框架提供协调服务的 Apache 项目。 Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的 注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。

# 特点

image.png

  • Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。
  • 集群中只要有 半数以上节点存活,Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器。
  • 全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
  • 更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。
  • 数据更新原子性,一次数据更新要么成功,要么失败。
  • 实时性,在一定时间范围内,Client能读到最新数据。

# 数据结构

ZooKeeper 数据模型的结构与 Unix 文件系统很类似,整体上可以看作是一棵树,每个节点称做一个 ZNode。每一个 ZNode 默认能够存储 1MB 的数据,每个 ZNode 都可以通过其路径唯一标识。 image.png

# 应用场景

提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

# 安装

https://archive.apache.org/dist/zookeeper/ (opens new window)

# 单机安装

  • 安装并配置JDK
  • 下载jar包,解压
  • 创建存放数据的目录
[root@localhost zookeeper-3.5.7]# mkdir zkData
  • 修改配置文件,示例是zoo_sample.cfg,复制一份文件名为zoo.cfg
[root@localhost conf]# mv zoo_sample.cfg zoo.cfg
[root@localhost conf]# vim zoo.cfg
# 修改数据存放的位置
dataDir=/usr/local/src/zookeeper-3.5.7/zkData
  • 启动zk
# 启动
[root@localhost zookeeper-3.5.7]# bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /usr/local/src/zookeeper-3.5.7/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
# 查看java进程
[root@localhost zookeeper-3.5.7]# jps -l
13623 org.apache.zookeeper.server.quorum.QuorumPeerMain
13727 sun.tools.jps.Jps
# 查看zk状态
[root@localhost zookeeper-3.5.7]# bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/src/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone

# 配置文件

# 通信心跳 时间, ,Zookeeper 服务器 与 客户端
tickTime=2000
# LF初始通信时间限制
# Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)
initLimit=10
# LF同步通信时限
# Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer
syncLimit=5
# 保存Zookeeper中的数据
# 注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。
dataDir=/usr/local/src/zookeeper-3.5.7/zkData
# 客户端连接端口 ,通常不做修改。
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

# 集群安装

  • 3台集群都安装和配置好,修改数据存储目录
  • 在数据存放目录创建一个文件,名字为myid,里面写入数字,我们这写的就是1、2、3,机器在集群中的唯一标识,每个都改
[root@localhost zookeeper-3.5.7]# vim zkData/myid
[root@localhost zookeeper-3.5.7]# cat zkData/myid 
3
  • 修改每个主机名,每个机器的上的名字不一样
[root@localhost zkData]# hostnamectl set-hostname zk01.localdomain
  • 修改每个hosts
192.168.83.130 zk01
192.168.83.131 zk02
192.168.83.132 zk03
  • 修改每个zk配置文件
# 添加集群配置
server.1=zk01:2888:3888
server.2=zk02:2888:3888
server.3=zk03:2888:3888
  • server. 后面的数字就是 myid 的数字
  • zk01:zk 的 ip,我们配置了 host 映射,直接填写映射名称即可
  • 2888:这个服务器 Follower 与集群中的 Leader 服务器交换信息的端口
  • 3888:万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
  • 启动每个zk
bin/zkServer.sh start
  • 查看不同集群的状态
# myid:1
[root@zk01 ~]# /usr/local/src/zookeeper-3.5.7/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/src/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
# myid:2
[root@zk02 ~]# /usr/local/src/zookeeper-3.5.7/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/src/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
# myid:3
[root@zk03 ~]# /usr/local/src/zookeeper-3.5.7/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/src/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

# 选举机制

  • SID:服务器ID。用来标识ZooKeeper集群中的唯一一台机器,每台机器不能重复,和myid一致。
  • ZXID:事务ID。用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和ZooKeeper服务器对于客户端"更新请求"的处理逻辑有关。
  • Epoch:每个Leader任期的代号。没有Leader时,同一轮投票过程中的逻辑时钟是相同的。每投完一次票这个数据就会增加。

# 第一次选举

image.png

  1. 服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
  2. 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING;
  3. 服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
  4. 服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
  5. 服务器5启动,同4一样当小弟。

# 非第一次选举

image.png 当ZooKeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入Leader选举:

  • 服务器初始化启动
  • 服务器运行期间无法和Leader保持连接

当一台机器进入Leader选举流程时,当前集群也可能处于两种状态

  • 集群中本来就存在一个Leader(脑裂) 对于已经存在Leader的情况,机器视图去选举Leacder时,会被告知当前服务器的Leader信息(交换投票信息),对于该机器来说,仅仅需要和Leader机器建立连接,进行状态同步即可
  • 集群中确实不存在Leader

假设ZK集群由5台服务器组成,SID分别为1、2、3、4、5,ZXID分别为8、8、8、7、7, 并且此时SID为3的服务器是Leader。某时刻,3和5服务器出现故障,开始进行Leader选举。 SID为1、2、4的机器投票情况:

机器SID EPOCH ZXID SID
1 1 8 1
2 1 8 2
4 1 7 4

选举Leader 规则:

  1. EPOCH大的直接胜出
  2. EPOCH相同,事务id大的胜出
  3. 事务id相同,服务器id大的胜出

# 客户端命令

连接服务端

bin/zkCli.sh -server host:port
# 不写ip:端口默认连接本机的2181端口
bin/zkCli.sh

# 常用命令

语法 描述
help 显示所有操作命令
ls path 查看当前 znode 的子节点

-w 监听子节点变化 -s 附加次级信息 | | create | 普通创建 -s 含有序列 -e 临时(重启或超时消失) | | get path | 获得节点的值 -w 监听节点内容变化 -s 附加次级信息 | | set | 设置节点的具体值 | | stat | 查看节点状态 | | delete | 删除节点 | | deleteall | 递归删除节点 |

# 节点信息

# 查看当前znode包含的数据
[zk: localhost:2181(CONNECTED) 1] ls /
[zookeeper]
# 查看当前节点的详细数据
[zk: localhost:2181(CONNECTED) 2] ls -s /
[zookeeper]cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
  • cZxid:创建节点的事务 zxid

每次修改zk状态都会产生一个zk事务id,事务id是zk中所有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生

  • ctime:znode 被创建的时间(时间戳)
  • mZxid:znode 最后被更新的事务 zxid
  • mtime:znode 最后修改的时间
  • pZxid:znode 最后更新的子节点 zxid
  • cversion:znode 子节点变化,znode 子节点修改次数
  • dataVersion:znode 数据变化号
  • aclVersion:znode 访问控制列表的变化号
  • ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0
  • dataLength:znode 的数据长度
  • numChildren:znode 的子节点数量

# 节点类型

image.png 持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除 短暂(Ephemeral):客户端和服务器端断开连接后,创建的节点自己删除 说明:创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护 注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序

创建普通节点

[zk: localhost:2181(CONNECTED) 37] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 38] create /user "tom"
Created /user
[zk: localhost:2181(CONNECTED) 39] create /user/other "jack"
Created /user/other
[zk: localhost:2181(CONNECTED) 41] get /user
tom
[zk: localhost:2181(CONNECTED) 42] get -s /user/other
jack
cZxid = 0x20000001f
ctime = Sat Jun 18 21:12:41 CST 2022
mZxid = 0x20000001f
mtime = Sat Jun 18 21:12:41 CST 2022
pZxid = 0x20000001f
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 43] set /user "luck"
[zk: localhost:2181(CONNECTED) 44] get -s /user
luck
cZxid = 0x20000001e
ctime = Sat Jun 18 21:12:19 CST 2022
mZxid = 0x200000020
mtime = Sat Jun 18 21:16:39 CST 2022
pZxid = 0x20000001f
cversion = 1
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 1

递增持久节点

[zk: localhost:2181(CONNECTED) 45] deleteall /user
[zk: localhost:2181(CONNECTED) 46] create -s /user "tom"
Created /user0000000006
[zk: localhost:2181(CONNECTED) 47] create -s /user "jack"
Created /user0000000007
[zk: localhost:2181(CONNECTED) 48] create -s /name "tom"
Created /name0000000008
[zk: localhost:2181(CONNECTED) 49] create -s /name "tom"
Created /name0000000009

临时节点

[zk: localhost:2181(CONNECTED) 50] create -e /nick "tom"
Created /nick
[zk: localhost:2181(CONNECTED) 51] create -e -s /nick "tom"
Created /nick0000000011
[zk: localhost:2181(CONNECTED) 51] quit
# 退出重新进入,临时节点没有了
[zk: localhost:2181(CONNECTING) 0] ls /
[name0000000008, name0000000009, user0000000006, user0000000007, zookeeper]

# 监听器原理

[zk: localhost:2181(CONNECTED) 17] create /user
Created /user
[zk: localhost:2181(CONNECTED) 18] create /user/name "tom"
Created /user/name
[zk: localhost:2181(CONNECTED) 19] create /user/name2 "jack"
Created /user/name2
[zk: localhost:2181(CONNECTED) 20] set /user/name "tom2"
[zk: localhost:2181(CONNECTED) 21] set /user/name "tom3"
[zk: localhost:2181(CONNECTED) 22] 
[zk: localhost:2181(CONNECTED) 3] ls -w /user
[]
[zk: localhost:2181(CONNECTED) 4] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/user
[zk: localhost:2181(CONNECTED) 5] get -w /user/name
tom
[zk: localhost:2181(CONNECTED) 6] 
WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/user/name

可以看到一次监听命令只能监听一次状态的变化,如果需要监听多次,就需要再次执行监听命令

image.png 监听原理

  1. 首先要有一个main()线程
  2. 在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)。
  3. 通过connect线程将注册的监听事件发送给Zookeeper。
  4. 在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。
  5. Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程。
  6. listener线程内部调用了process()方法

# zookeeper jar包操作

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>RELEASE</version>
</dependency>

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.8.2</version>
</dependency>

<dependency>
  <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId>
  <version>3.5.7</version>
</dependency>
package com.starry.zkdemo;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author starry
 * @version 1.0
 * @date 2022/6/18 23:11
 * @Description
 */
@Slf4j
public class zkClientTest {


    ZooKeeper zooKeeper;

    /**
     * 初始化,添加 watch 回调
     */
    @Before
    @SneakyThrows
    public void init() {
        String connections = "192.168.83.130:2181,192.168.83.131:2181,192.168.83.132:2181";
        int timeout = 600000;
        zooKeeper = new ZooKeeper(connections, timeout, new Watcher() {
            @Override
            @SneakyThrows
            public void process(WatchedEvent watchedEvent) {

                // 返回给定路径的节点的子节点列表。
                // 如果 watch 为 true 并且调用成功(没有抛出异常),则会在给定路径的节点上留下一个 watch。(再次监听)
                List<String> children = zooKeeper.getChildren("/", true);
                log.info("-------print begin---------");
                for (String child : children) {
                    log.info(child);
                }
                log.info("-------print end---------");
            }
        });

    }

    /**
     * 创建节点
     */
    @Test
    @SneakyThrows
    public void testCreate() {
        String result = zooKeeper.create("/name111", "nick".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        log.info(result);
    }

    /**
     * 获取节点
     */
    @Test
    @SneakyThrows
    public void testGet() {
        // watch:true 继续监听,使用创建zk连接时传入的回调函数
        List<String> list = zooKeeper.getChildren("/", true);
        list.forEach(log::info);

        // 阻塞,防止运行完就结束
        System.in.read();
    }

    /**
     * 判断节点是否存在
     */
    @Test
    @SneakyThrows
    public void testExists() {
        Stat exists = zooKeeper.exists("/user", false);
        log.info(exists == null ? "not exist" : "exist");

        Stat exists2 = zooKeeper.exists("/user100", false);
        log.info(exists2 == null ? "not exist" : "exist");
    }

}

# 客户端向服务端写数据流程

# 写入请求直接发送给Leader节点

image.png

  • 客户端发送写入请求给 leader,leader 会写入数据
  • 并将请求发送给 follower,follower 也写入数据
  • 当 follower 写入完成后返回 ack 给 leader,告诉 leader 我的数据写完了
  • 如果一半以上的节点后完成了数据的写入,就直接返回给客户端响应写入数据成功

# 写入请求发送给follower节点

image.png

  • 客户端发送写请求到follower,follower将请求转发给leader
  • leader收到请求执行写操作,并将请求分发给所有的follower
  • follower写完后,发送ack给leader
  • 集群中一半以上完成写操作后,leader就返回写入成功
  • 返回ack给转发请求的follower,follower再将ack返回给客户端

# 服务器动态上下线

服务端注册到zk中,客户端从zk中获取已经注册的客户端 image.png

package com.starry.zkdemo.case1;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.junit.Before;
import org.junit.Test;

import java.nio.charset.StandardCharsets;

/**
 * @author starry
 * @version 1.0
 * @date 2022/6/19 22:30
 * @Description
 */
@Slf4j
public class DistributeServer {

    ZooKeeper zooKeeper;

    @Before
    @SneakyThrows
    public void init() {
        String connections = "192.168.83.130:2181,192.168.83.131:2181,192.168.83.132:2181";
        int timeout = 600000;
        zooKeeper = new ZooKeeper(connections, timeout, event -> {});
    }

    @Test
    @SneakyThrows
    public void mainTest() {
        // 服务注册到zk,临时+序列
        zooKeeper.create("/servers/server01", "111.111.111.111:111".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        Thread.sleep(2000);
        zooKeeper.create("/servers/server02", "222.222.222.222:222".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        Thread.sleep(2000);
        zooKeeper.create("/servers/server03", "333.333.333.333:333".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

    }



}


package com.starry.zkdemo.case1;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.ZooKeeper;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

/**
 * @author starry
 * @version 1.0
 * @date 2022/6/19 22:30
 * @Description
 */
@Slf4j
public class DistributeClient {

    ZooKeeper zooKeeper;

    @Before
    @SneakyThrows
    public void init() {
        String connections = "192.168.83.130:2181,192.168.83.131:2181,192.168.83.132:2181";
        int timeout = 600000;
        zooKeeper = new ZooKeeper(connections, timeout, event -> {
            getOnlineServer();
        });

    }

    @SneakyThrows
    private void getOnlineServer() {
        List<String> children = zooKeeper.getChildren("/servers", true);
        log.info("-------server list---------");
        // 获取节点
        for (String child : children) {
            // 获取节点的数据
            byte[] data = zooKeeper.getData("/servers/" + child, false, null);
            log.info("online server: {}", new String(data));
        }
        log.info("---------------------------");
    }

    @Test
    @SneakyThrows
    public void mainTest() {
        getOnlineServer();
        System.in.read();
    }

}

# 分布式锁案例

image.png 原生的 Java API 开发存在的问题

  • 会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
  • Watch 需要重复注册,不然就不能生效
  • 开发的复杂性还是比较高的
  • 不支持多节点删除和创建。需要自己去递归

Curator 是一个专门解决分布式锁的框架,解决了原生Java API 开发分布式遇到的问题。https://curator.apache.org/index.html (opens new window)

  • 导入依赖
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>4.3.0</version>
        </dependency>
  • 测试
package com.starry.zkdemo.case2;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class DistributedLock {

    public static CuratorFramework getClient() {
        String connections = "192.168.83.130:2181,192.168.83.131:2181,192.168.83.132:2181";
        int timeout = 600000;
        // 重试策略
        RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString(connections)
                .connectionTimeoutMs(timeout)
                .retryPolicy(policy)
                .build();
        client.start();
        System.out.println("zookeeper 启动成功...");
        return client;
    }


    public static void main(String[] args) {
        String path = "/locks";
        InterProcessMutex lock1 = new InterProcessMutex(getClient(), path);
        InterProcessMutex lock2 = new InterProcessMutex(getClient(), path);

        new Thread(()->{
            try {
                lock1.acquire();
                System.out.println("线程1 获取到锁");
                lock1.acquire();
                System.out.println("线程1 再次获取到锁");
                Thread.sleep(5 * 1000);
                lock1.release();
                System.out.println("线程1 释放锁");
                lock1.release();
                System.out.println("线程1  再次释放锁");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try {
                lock2.acquire();
                System.out.println("线程2 获取到锁");
                lock2.acquire();
                System.out.println("线程2 再次获取到锁");
                Thread.sleep(5 * 1000);
                lock2.release();
                System.out.println("线程2 释放锁");
                lock2.release();
                System.out.println("线程2  再次释放锁");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

}

两个线程锁定的同一个znode,只有一个线程运行完(删除znode)下一个线程才能继续执行

# 拜占庭将军问题

拜占庭将军问题(Byzantine Generals Problem),描述的是分布式对等网络 (opens new window)通信容错问题。 在分布式计算 (opens new window)中,不同的计算机 (opens new window)通过通讯交换信息达成共识而按照同一套协作策略行动。但有时候,系统中的成员计算机可能出错而发送错误的信息,用于传递信息的通讯网络也可能导致信息损坏,使得网络中不同的成员关于全体协作的策略得出不同结论,从而破坏系统一致性。拜占庭将军问题被认为是容错性问题中最难的问题类型之一。

拜占庭将军问题是一个协议问题,拜占庭帝国军队的将军们必须全体一致的决定是否攻击某一支敌军。问题是这些将军在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标: 欺骗某些将军采取进攻行动; 促成一个不是所有将军都同意的决定 , 如当将军们不希望进攻时促成进攻行动; 或者迷惑某些将军 , 使他们无法做出决定。如果叛徒达到了这些目的之一,则任何攻击行动的结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。

# Paxos算法

# ZAB协议

# CAP 理论

上次更新: 2024/03/03, 08:36:37
SpringCloud

← SpringCloud

Theme by Vdoing | Copyright © 2023-2024 Starry | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式