Zookeeper介绍及其使用

1. Zookeeper概述

Zookeeper 是一个开源的分布式协调服务框架 ,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题。可以从设计角度认知Zookeeper是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据发生变化,Zookeeper将负责通知注册在其之上的那些观察者作出相应的操作,Zookeeper即等于文件系统和通知机制。

2. Zookeeper特点

  • Zookeeper 本质上是一个分布式文件系统, 适合存放小文件,也可以理解为一个数据库

  • Zookeeper集群是由一个领导者(leader),多个跟随者(follower)组成的集群

  • Zookeeper集群中半数以上节点存活,集群就能正常服务

  • 保持全局一致性,每个集群节点都保存一份相同的数据副本,客户端无论连接到哪个节点,数据都是一致的

  • 更新数据请求顺序依次执行,来自同一个客户端的更新请求按照其发送顺序依次执行

  • 数据更新原子性,一次数据更新要么成功,要么失败

  • 在一定时间范围内,客户端可以读到最新数据

  • Zookeeper 中存储数据的最基本单位是一个又一个 Znode节点

    节点有路径,例如:/hbase/table,/hadoop/HA等 ,同时节点也可以携带数据

    通常阐述一个节点是 ,这个节点的路径是/xx/xx,其数据是xx

  • 可以对Zookeeper中的Znode节点进行相关操作,诸如:

    通过路径获取Znode

    获取某个Znode携带数据

    修改某个Znode携带数据

    添加某个Znode

    删除某个Znode

3. Zookeeper应用场景

3.1. 统一命名服务

命名服务是分步实现系统中较为常见的一类场景,分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等,通过命名服务,客户端可以根据指定名字来获取资源的实体,在分布式环境中,上层应用仅仅需要一个全局唯一的名字。Zookeeper可以实现一套分布式全局唯一ID的分配机制。

3.2. 统一配置管理

分布式环境下,一般要求集群中,所有节点的配置信息都是一样的。对配置文件修改后,可以快速同步到各个节点上。配置管理可以交给Zookeeper实现,可以将配置信息写入一个Znode中,各个客户端监听这个Znode,一旦Znode中的数据被修改,则Zookeeper会通知各个客户端服务器。

3.3. 数据发布和订阅

数据发布/订阅系统,需要发布者将数据发布到Zookeeper的节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。

发布/订阅一般有两种设计模式:推模式和拉模式,服务端主动将数据更新发送给所有订阅的客户端称为推模式;客户端主动请求获取最新数据称为拉模式。

Zookeeper采用了推拉相结合的模式,客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,那么服务端就会向相应的客户端推送Watcher事件通知,客户端接收到此通知后,主动到服务端获取最新的数据。

3.4. 分布式协调和通知

在绝大多数分布式系统中,系统机器间的通信无外乎心跳检测工作进度汇报系统调度

心跳检测

不同机器间需要检测到彼此是否在正常运行,可以使用Zookeeper实现机器间的心跳检测,基于其临时节点特性(临时节点的生存周期是客户端会话,客户端若宕机后,其临时节点自然不再存在),可以让不同机器都在Zookeeper的一个指定节点下创建临时子节点,不同的机器之间可以根据这个临时子节点来判断对应的客户端机器是否存活。通过Zookeeper可以大大减少系统耦合。

工作进度汇报

通常任务被分发到不同机器后,需要实时地将自己的任务执行进度汇报给分发系统,可以在Zookeeper上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点,这样不仅可以判断机器是否存活,同时各个机器可以将自己的任务执行进度写到该临时节点中去,以便中心系统能够实时获取任务的执行进度。

系统调度

Zookeeper能够实现如下系统调度模式:分布式系统由控制台和一些客户端系统两部分构成,控制台的职责就是需要将一些指令信息发送给所有的客户端,以控制他们进行相应的业务逻辑,后台管理人员在控制台上做一些操作,实际上就是修改Zookeeper上某些节点的数据,Zookeeper可以把数据变更以时间通知的形式发送给订阅客户端。

3.5. 分布式锁

分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,可以保证不同系统访问一个或一组资源时的一致性。

公平锁和可重入锁

可以利用如下例子理解分布式锁:

一个村子有一口井,水质非常的好,村民们都抢着取井里的水。井就那么一口,村里的人很多,那么如何合理安排取水?最终想出了一个凭号取水的方案。井边安排一个看井人,维护取水的秩序。取水之前,先取号。号排在前面的,就可以先取水。先到的排在前面,那些后到的,没有排在最前面的人,一个一个挨着,在井边排成一队。这种排队取水模型,就是一种锁的模型。排在最前面的号,拥有取水权,就是一种典型的独占锁。另外,先到先得,号排在前面的人先取到水,取水之后就轮到下一个号取水,至少,看起来挺公平的,说明它是一种公平锁。

在公平独占锁的基础上,我们再进一步,看看可重入锁的模型。假定,取水时以家庭为单位,哪个家庭任何人拿到号,就可以排号取水,而且如果一个家庭有一个人拿到号,其它家人这时候过来打水不用再取号。如果是同一个家庭,可以直接复用排号,不用重新取号从后面排起。以上就是可以重入锁的模型。只要满足条件,同一个排号,可以用来多次取水。在锁的模型中,相当于一把锁,可以被多次锁定,这就叫做可重入锁。

Zookeeper分布式锁原理

Zookeeper 天生就是一副分布式锁,在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一。比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FH9IFmls-1630836086348)(.\images\分布式锁01.png)]

其次,Zookeeper节点的递增性,可以规定节点编号最小的那个获得锁。

一个zookeeper分布式锁,首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程都会在这个节点下创建个临时顺序节点,由于序号的递增性,可以规定排号最小的那个获得锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。

每个线程抢占锁之前,先抢号创建自己的ZNode。同样,释放锁的时候,就需要删除抢号的Znode。抢号成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。当前一个Znode 删除的时候,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后。

4. Zookeeper数据结构说明

4.1. 整体数据结构

Zookeeper数据结构是一个树状结构,与Linux文件系统结构类似。每个节点称作Znode,可以存储1M大小数据。且每个Znode节点可以通过其路径唯一标识。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUvAQ03n-1630836086352)(.\images\Zookeeper数据结构.png)]

4.2. 节点类型说明

持久

客户端和服务器端断开连接后,创建的节点不删除

短暂

客户端和服务器端断开连接后,创建的节点自动删除

Zookeeper节点类型:

  • 持久化节点

    客户端与Zookeeper断开后,此节点依然存在

  • 持久化顺序编号节点

    客户端与Zookeeper断开后,节点存在;在创建节点时,Zookeeper会给节点顺序编号

  • 临时节点

    客户端与Zookeeper断开后,此节点自动删除

  • 临时顺序编号节点

    客户端与Zookeeper断开后,此节点自动删除;

4.3. Stat结构体说明

  • czxid-创建节点的事务zxid

    每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。

    事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的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

5. Zookeeper客户端工具安装

下载地址:
https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip

解压后进入build目录执行命令

java -jar zookeeper-dev-ZooInspector.jar
输入连接地址,即可看到zookeeper的节点信息
在这里插入图片描述

6. Zookeeper客户端Shell命令

命令说明参数
create [-s] [-e] path data acl创建Znode-s 指定是顺序节点
-e 指定是临时节点
ls path [watch]列出Path下所有子Znode
get path [watch]获取Path对应的Znode的数据和属性
ls2 path [watch]查看Path下所有子Znode以及子Znode的属性
set path data [version]更新节点version 数据版本
delete path [version]删除节点, 如果要删除的节点有子Znode则无法删除version 数据版本
rmr path删除节点, 如果有子Znode则递归删除
history列出历史记录
help显示所有操作命令

create

  • 创建普通节点
[zk: localhost:2181(CONNECTED) 3] create /test01 "a01"
Created /test01
[zk: localhost:2181(CONNECTED) 4] create /test01/test02 "b01"
Created /test01/test02
  • 创建临时节点
[zk: localhost:2181(CONNECTED) 7] create -e /test01/test03 "c01"
Created /test01/test03

​ 当前客户端可以查看到

[zk: localhost:2181(CONNECTED) 3] ls /test01 
[test02, test03]

​ 退出当前客户端然后再重启客户端

[zk: localhost:2181(CONNECTED) 12] quit
[root@node01 zookeeper-3.4.10]$ bin/zkCli.sh

​ 再次查看根目录下短暂节点已经删除

[zk: localhost:2181(CONNECTED) 0] ls /test01
[test02]
  • 创建顺序节点
[zk: localhost:2181(CONNECTED) 2] create -s /test01/test02/a "content"
Created /test01/test02/a0000000000
[zk: localhost:2181(CONNECTED) 3] create -s /test01/test02/b "content"
Created /test01/test02/b0000000001
[zk: localhost:2181(CONNECTED) 4] create -s /test01/test02/c "content"
Created /test01/test02/c0000000002

ls

[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

get

[zk: localhost:2181(CONNECTED) 5] get /test01
a01
cZxid = 0x100000003
ctime = Wed Aug 29 00:03:23 CST 2018
mZxid = 0x100000003
mtime = Wed Aug 29 00:03:23 CST 2018
pZxid = 0x100000004
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 1

ls2

[zk: localhost:2181(CONNECTED) 1] ls2 /
[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

set

[zk: localhost:2181(CONNECTED) 6] set /test01/test02 "haha"

delete

[zk: localhost:2181(CONNECTED) 4] delete /test01/test04

rmr

[zk: localhost:2181(CONNECTED) 15] rmr /test01/test05

history

[zk: localhost:2181(CONNECTED) 17] history

help

[zk: localhost:2181(CONNECTED) 1] help

监听器使用

  • 监听节点数据变化

在node01主机上注册监听/test01节点数据变化

[zk: localhost:2181(CONNECTED) 8] get /test01 watch

在node02主机上修改/test01节点的数据

[zk: localhost:2181(CONNECTED) 1] set /test01 "hello"

观察node01主机收到数据变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/test01
  • 监听节点的子节点变化

在node01主机上注册监听/test01节点的子节点变化

[zk: localhost:2181(CONNECTED) 1] ls /test01 watch
[test02, test04]

在node02主机/test01节点上 创建子节点

[zk: localhost:2181(CONNECTED) 2] create /test01/test05 "haha"
Created /test01/test05

观察node01主机收到子节点变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/test01

7. Zookeeper的API使用

在这里操作Zookeeper使用的是开源Curator框架,Curator是Netflix公司开源的一个Zookeeper客户端,后捐献给Apache,Curator框架在zookeeper原生API接口上进行了包装,解决了很多ZooKeeper客户端非常底层的细节开发。提供ZooKeeper各种应用场景(recipe, 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装,实现了Fluent风格的API接口,是最好用,最流行的zookeeper的客户端。

7.1. 引入jar包

    <dependencies>

        <dependency>

            <groupId>org.apache.curator</groupId>

            <artifactId>curator-framework</artifactId>

            <version>2.12.0</version>

        </dependency>

        <dependency>

            <groupId>org.apache.curator</groupId>

            <artifactId>curator-recipes</artifactId>

            <version>2.12.0</version>

        </dependency>

        <dependency>

            <groupId>com.google.collections</groupId>

            <artifactId>google-collections</artifactId>

            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

    <build>

        <plugins>

            <!-- java编译插件 -->

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>3.2</version>

                <configuration>

                    <source>1.8</source>

                    <target>1.8</target>

                    <encoding>UTF-8</encoding>

                </configuration>

            </plugin>

        </plugins>

    </build>

7.2. 节点操作

创建节点

创建持久节点

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.52.110:2181,192.168.52.120:2181,192.168.52.130:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
         /**
         * 创建一个 允许所有人访问的 持久节点
         */
        client.create()
                .creatingParentsIfNeeded()//递归创建,如果没有父节点,自动创建父节点
                .withMode(CreateMode.PERSISTENT)//节点类型,持久节点
                .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//设置ACL,和原生API相同
                .forPath("/test02/node01","123456".getBytes());
    
       client.close();
}

创建一个临时节点

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
         /**
         * 创建一个 允许所有人访问的 持久节点
         */
        client.create()
                .creatingParentsIfNeeded()//递归创建,如果没有父节点,自动创建父节点
                .withMode(CreateMode.EPHEMERAL)//节点类型,临时节点
                .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//设置ACL,和原生API相同
                .forPath("/test02/node02","haha".getBytes());
    
     //  client.close();
}

修改节点数据

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
         /**
         * 节点下面添加数据与修改是类似的,一个节点下面会有一个数据,新的数据会覆盖旧的数据
         */
        client.setData().forPath("/test02/node01", "hello".getBytes());
    
       client.close();
}

删除节点

删除单一节点

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
    	//删除节点
        Void aVoid = client.delete()
                .forPath("/test02/node02");
        System.out.println("=====>" + aVoid);
    
       client.close();
}

递归删除节点

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
    	//删除节点
        client.delete()
                .deletingChildrenIfNeeded()
                .forPath("/test02");
    
       client.close();
}

查询节点

获取/test02/node03节点数据 和 Stat信息

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
    	//获取/test02/node03节点数据  和 Stat信息
        //创建一个Stat对象  
        Stat statinfo = new Stat();
        byte[] node10 = client.getData()
                .storingStatIn(statinfo)//获取stat信息存储到stat对象
                .forPath("/test02/node03");
        System.out.println("=====>该节点信息为:" + new String(node10));
        System.out.println("=====>该节点的数据版本号为:" + statinfo.getVersion());
    
       client.close();
}

节点watch机制

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
	     //设置节点的cache  
	     TreeCache treeCache = new TreeCache(client, "/test02/node04");  

	     //设置监听器和处理过程  
	     treeCache.getListenable().addListener(new TreeCacheListener() { 
             
	            @Override  
	     public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {  

	                ChildData data = event.getData();  
	                if(data !=null){  
                        //根据监听事件类型进行匹配
	                    switch (event.getType()) { 
	                    case NODE_ADDED:  //添加节点
	                   System.out.println("NODE_ADDED : "+ data.getPath() +"  数据:"+ new String(data.getData()));  

	                        break;  

	                    case NODE_REMOVED:  //删除节点
	                        System.out.println("NODE_REMOVED : "+ data.getPath() +"  数据:"+ new String(data.getData()));  
	                        break;  

	                    case NODE_UPDATED:  //修改节点
	                        System.out.println("NODE_UPDATED : "+ data.getPath() +"  数据:"+ new String(data.getData()));  

	                        break;  
	                          
	                    default:  

	                        break;  

	                    }  

	                }else{  

	                    System.out.println( "data is null : "+ event.getType());  

	                }  

	            }  

	        });  

	        //开始监听  

	        treeCache.start();  

	        Thread.sleep(50000000);
}

判断节点是否存在

public static void main(String[] args) throws Exception {
        //连接字符串
        String connectionString = "192.168.108.130:2181,192.168.108.131:2181,192.168.108.132:2181";
        //指定重试策略
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
        //创建客户端对象
        CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
        //启动客户端
        client.start();
    
		Stat existsNodeStat = client.checkExists().forPath("/test02/node04");
        if(existsNodeStat == null){
            System.out.println("=====>节点不存在");
        }
        if(existsNodeStat.getEphemeralOwner() > 0){
            System.out.println("=====>临时节点");
        }else{
            System.out.println("=====>持久节点");
        }
    
       client.close();
}

8. Zookeeper原理

8.1. 监听器原理

  • 主进程main进程
  • 在主进程中创建Zookeeper客户端同时会启动两个线程,一个是负责网络通信,一个负责监听
  • 通过连接通信线程把注册的监听器事件发送给Zookeeper
  • 在Zookeeper的监听器列表中把注册的监听器加入
  • Zookeeper监听到节点数据变化或者路径变化,就会把此消息发送给监听线程
  • 监听线程在内部会调用process方法,执行相关操作

8.2. 选主机制

半数机制

集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。

选主流程

Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。

假设:假设有三台服务器组成的Zookeeper集群,它们的id从1-3,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,我们来看下选主过程:

  • 服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(2票),选举无法完成,服务器1状态保持为LOOKING;
  • 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,y已经达到半数以上,服务器2当选Leader。服务器1更改状态为FOLLOWING,服务器2更改状态为LEADING。
  • 服务器3启动,发起一次选举。此时服务器1,2已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器2为2票,服务器3为1票。此时服务器3服从多数,更改选票信息为服务器2,并更改状态为FOLLOWING
  • 至此选主过程结束

8.3. 写数据流程

  • 客户端向Zookeeper集群node01上发送一个写数据请求
  • 如果node01不是leader,则node01会把写请求转发给leader。leader会把这个写请求广播给各个节点,各个节点会把写请求加入待写队列,并向leader发送成功信息
  • 当leader收到半数以上节点成功信息,说明该写操作可以执行。leader会向各个节点发送提交信息,各个节点收到信息后会落实队列中的写请求,此时写操作成功
  • node01节点会通知客户端写操作成功,这时可以认为整个写操作成功

8.4. 读数据流程

相比写数据流程,读数据流程就简单得多;因为每台server中数据一致性都一样,所以随便访问哪台server读数据就行;没有写数据流程中请求转发、数据同步、成功通知这些步骤。

8.5. CAP原则

CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。

一致性(C)

在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)

可用性(A)

在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)

分区容忍性(P)

以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足 。

Zookeeper满足了以上的CP原则。

8.6. BASE理论

BASE理论是指,Basically Available(基本可用)、Soft-state( 软状态/柔性事务)、Eventual Consistency(最终一致性)。是基于CAP定理演化而来,是对CAP中一致性和可用性权衡的结果。

核心思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。

(1)Basically Available:基本可用,指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。

(2)Soft-state:软状态/柔性事务,即状态可以有一段时间的不同步。

(3)Eventual consistency:最终一致性,系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。

BASE是反ACID的,它完全不同于ACID模型,牺牲强一致性,获得基本可用性和柔性可靠性并要求达到最终一 致性。

BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。

8.7. ZAB协议

协议概述

  • ZooKeeper使用了一种称为ZooKeeper Atomic Broadcast(ZAB,zookeeper原子消息广播协议)的协议作为其数据一致性的核心算法
  • ZAB协议是为分布式协调服务zookeeper专门设计的一种支持崩溃恢复的原子广播协议
  • 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性

ZAB协议基本模式

ZAB协议包含两种基本的模式,即崩溃恢复和消息广播。

当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时, ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的Leader 服务器同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。

当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播 , 那么新加人的服务器就会自觉地进人数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要卸载Zookeeper,可以按照以下步骤进行: 1. 停止Zookeeper服务。可以使用以下命令: ``` sudo systemctl stop zookeeper ``` 2. 确认Zookeeper服务已停止。可以使用以下命令: ``` sudo systemctl status zookeeper ``` 确保输出中显示"Active: inactive (dead)",表示服务已经停止。 3. 卸载Zookeeper。可以使用以下命令: ``` sudo apt-get remove zookeeper ``` 4. 确认Zookeeper已卸载。可以使用以下命令: ``` dpkg -l | grep zookeeper ``` 如果没有任何输出,则表示Zookeeper已经成功卸载。如果输出中还有关于Zookeeper的信息,则可以尝试使用以下命令: ``` sudo apt-get remove --purge zookeeper ``` 这将卸载Zookeeper及其配置文件,以确保完全删除。 ### 回答2: 在卸载zookeeper之前,要确保已经停止zookeeper进程。可以使用以下命令停止进程: ``` $ sudo systemctl stop zookeeper.service ``` 接下来,可以使用以下步骤卸载zookeeper: 1. 删除zookeeper配置文件和数据目录 ``` $ sudo rm -r /etc/zookeeper/ $ sudo rm -r /var/lib/zookeeper/ ``` 2. 停用zookeeper系统服务 ``` $ sudo systemctl disable zookeeper.service ``` 3. 卸载zookeeper包 如果使用yum安装的zookeeper: ``` $ sudo yum remove zookeeper ``` 如果使用apt-get安装的zookeeper: ``` $ sudo apt-get remove zookeeper ``` 4. 确认zookeeper已经完全卸载 可以使用以下命令确认: ``` $ zkServer.sh status ``` 如果提示“Command not found”则表示zookeeper已经完全卸载。 卸载zookeeper的过程很简单,但要注意的是一定要先停止zookeeper进程,否则可能会导致进程卡死或其他异常情况。 ### 回答3: Zookeeper是一个功能强大的开源分布式协作系统,它通常用于管理和协调大型集群中的分布式应用程序。但是,有时候我们需要卸载Zookeeper。下面介绍如何在Linux系统上彻底卸载Zookeeper。 步骤1:停止Zookeeper服务 在卸载Zookeeper之前,必须先停止正在运行的Zookeeper服务。要停止Zookeeper服务,请在终端中运行以下命令: sudo systemctl stop zookeeper 步骤2:删除Zookeeper文件 一旦Zookeeper服务停止运行,现在就可以删除它的所有文件。要删除Zookeeper,请在终端中运行以下命令: sudo rm -rf /var/lib/zookeeper 该命令将删除Zookeeper数据目录中的所有文件。 步骤3:删除Zookeeper软件包 现在,我们可以从Linux系统中卸载Zookeeper软件包。要卸载该软件包,请在终端中运行以下命令: sudo apt remove zookeeper 以上命令将卸载Zookeeper软件包。 步骤4:删除Zookeeper配置文件 最后,您还需要删除Zookeeper配置文件。要删除配置文件,请在终端中运行以下命令: sudo rm /etc/zookeeper/conf/zoo.cfg 此命令将删除Zookeeper的配置文件。 现在,您已经成功地从Linux系统中卸载了Zookeeper

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值