zookeeper使用篇-Zookeeper系统核心模型

上篇我们学习了Zookeeper在分布式下的常见场景与解决方案,本篇我们开始学习Zookeeper核心模型,了解zk的数据模型、节点特性、版本与权限等核心功能
原理

数据模型

Zookeeper的视图结构和标准的Unix文件系统非常相似,在Zookeeper中没有目录和文件等概念,而是有一个数据节点的概念,称之为ZNode。而每一个ZNode则是我们每个路径创建对应的节点,由于每个ZNode上可以保存数据,并且还可以挂载子ZNode节点,所以形成了一个树形结构,大概如下:

1581512043783.png

而在每个ZNode中有一个事务ID的概念,在Zookeeper中将每个数据的变更操作视为事务操作,其中包括数据节点的创建、删除、更新与会话的变更等。而每一次事务操作,zookeeper都会为其分配一个全局的事务ID,用ZXID表示,定义为64位的数字,而ZXID的大小也代表了zookeeper执行操作的顺序性。

数据节点特性

节点类型

在Zookeeper中,每个ZNode都有自己的生命周期,而生命周期取决于每个ZNode的类型。在Zookeeper中数据节点主要可以分为持久化节点(PERSISTENT)临时节点(EPHEMERAL)以及顺序节点( SEQUENTIAL)三大类。而在使用过程中,无论是持久化节点还是临时节点,都可以组合顺序节点使用,因此总共组合成了四种数据节点,如下:

持久化节点

持久化节点是Zookeeper中最常见的一种数据节点,所谓持久化,即从节点创建开始,除非手动触发删除节点的操作,否则都会一直存在于Zookeeper服务器上,哪怕是服务器宕机等故障,也会在重启以后恢复。

持久化顺序节点

持久化顺序节点,其自身是持久化的节点,唯一的区别表现在顺序性上。当创建持久化顺序节点以后,zookeeper中的父节点会为第一级子节点维护一份顺序,用于记录每一个子节点创建的顺序性。因此基于顺序节点的特性,在节点被创建的时候,会自动添加一个数字后缀,作为一个新的、完整的节点名。

临时节点

和持久化节点不同的是,临时节点的生命周期与当前创建节点的客户端会话有关。如果创建当前节点的客户端会话过期失效,那么该客户端创建的所有临时节点都会被清理。因为临时节点并不会持久化存储,因此在Zookeeper中也不允许在临时节点中挂载其他节点,防止因为临时节点被清理导致的一系列问题。

临时顺序节点

和临时节点特性相同,临时顺序节点在创建的时候具有顺序性,会自动生成一个数字后缀,父节点会进行节点的顺序管理。还记得上篇我们解决分布式锁,以及分布式队列等场景中解决方案,就是在一个父节点中创建多个具有顺序性的临时节点,很多时候我们都会利用临时节点的特性组合顺序性来解决开发上的问题

节点状态信息

前面我们知道ZNode中可以存储数据,除此之外,每个ZNode中还会保存自身的一些状态信息。还记得我们开篇的时候,使用的get系列命令,以及Api中的get方法,都会获取到很多ZNode中存储的信息,而其中关于当前节点状态相关的信息都保存在Stat类实例中,我们先来看Stat类的源码定义:

public class Stat implements Record {
  private long czxid;
  private long mzxid;
  private long ctime;
  private long mtime;
  private int version;
  private int cversion;
  private int aversion;
  private long ephemeralOwner;
  private int dataLength;
  private int numChildren;
  private long pzxid;
    
  public Stat() {
  }
  public Stat(
        long czxid,
        long mzxid,
        long ctime,
        long mtime,
        int version,
        int cversion,
        int aversion,
        long ephemeralOwner,
        int dataLength,
        int numChildren,
        long pzxid) {
    this.czxid=czxid;
    this.mzxid=mzxid;
    this.ctime=ctime;
    this.mtime=mtime;
    this.version=version;
    this.cversion=cversion;
    this.aversion=aversion;
    this.ephemeralOwner=ephemeralOwner;
    this.dataLength=dataLength;
    this.numChildren=numChildren;
    this.pzxid=pzxid;
  }
   .........
}

可以看到在Stat类的定义中,定义了很多变量用来存储不同的数据,而这些变量分别代表什么意思,有什么作用,接下来我们来对这些变量进行说明:

czxid

czxid代表Create ZXID,即代表着当前zookeeper节点创建的时候分配的事务ID,并且当前ID在创建完成以后不会再变更

mzxid

mzxid代表Modified ZXID,即代表着当前节点每一次触发变更操作的时候分配的事务ID,这里保存的是最后一次变更的时候分配的事务ID。

ctime

ctime代表Create time,与czxid搭配使用,即在创建当前节点的时候,记录创建的时间

mtime

mtime代表Modified time,与mzxid搭配使用,即会记录最后一次节点变更操作的时间

version

version代表当前节点的版本号

cversion

cversion代表当前节点中创建的子节点的版本号

aversion

aversion代表当前节点中ACL权限相关的版本号

ephemeralOwner

ephemeralOwner用来保存创建节点的时候生成的会话sessionId,如果当前节点是持久化节点,这个值一般为0(0x0)

dataLength

dataLength保存了当前节点中存储的数据对应的字节长度

numChildren

numChildren中保存了当前节点中创建的子节点的个数

pzxid

pzxid保存了该节点的子节点列表中最后一次被修改的时候生成的事务ID,需要注意的是这里只有子节点列表变化才会重新生成pzxid,如果某个子节点内容修改等操作并不会生成新的pzxid

节点的版本控制

从stat类的定义中,我们看到,在ZNode中,存在多种事务操作的ID,但是zk是如何保证每次事务操作的正确性和稳定的呢?这个时候我们不禁要考虑分布式场景下一个概念--锁,在分布式系统中,一般事务操作,都要保证排他性,而主流的锁方案分为悲观锁乐观锁两种。悲观锁具有强烈的独占和排他性,但是整个处理过程中,数据会完全被锁定,其他的事务对该数据将做不了任何操作,哪怕是读取数据的操作,直到事务操作结束释放悲观锁为止。但是在分布式场景下,更多的操作是读取共享的数据,如果使用悲观锁,则会造成大量的数据被锁定,造成性能大幅度下降。因此乐观锁的概念出现,在乐观锁中的绝大多数操作都是不对数据加锁的,而是在更新操作之前,去检查当前事务读取的数据与即将要修改的数据是否一致,从而确定是否在读取完数据到更新数据之前的过程中,有木有别的事务对该数据进行了修改操作。如果发现已经被更新了,则回滚当前事务操作,如果没有修改则执行当前的事务。在JDK中,乐观锁的一个典型实现则是利用CAS理论实现的,而Zookeeper也基于类似的实现方案,在每次事务操作之前,都会在PrepRequestProcessor处理器中的setData数据更新操作之前,进行一次版本检查操作,如下:

int newVersion = checkAndIncVersion(nodeRecord.stat.getVersion(), setDataRequest.getVersion(), path);

checkAndIncVersion方法如下:

private static int checkAndIncVersion(int currentVersion, int expectedVersion, String path)
            throws KeeperException.BadVersionException {
//判断当前请求的版本不是-1,并且与原来的版本号不同时,则抛出异常,其他情况下则将版本号+1    
if (expectedVersion != -1 && expectedVersion != currentVersion) {
            throw new KeeperException.BadVersionException(path);
        }
        return currentVersion + 1;
    }

通知机制Watcher

在前面的文章中,我们学习了Zookeeper的发布订阅功能的实践,同时我们也知道zk中存在多种监听通知,可以实现一对一,一对多等不同的通知机制。而zk中的watcher注册和通知过程如下:

1581769528192.png

从这我们可以看出,整个Watcher机制中,主要包括watcherManager、客户端线程以及Zookeeper服务端三部分。大概的过程为客户端在朝服务器注册Watcher的同时,将watcher对象存储在客户端的WatcherManager中。当Zookeeper服务端触发了Watcher事件后,会向客户端发送通知,客户端线程则是从WatcherManager中取出Watcher对象来执行对应的逻辑。

Watcher接口

在Zookeeper中,Watcher接口表示一个标准的事件处理器。定义了对应的逻辑,其中KeeperStateEventType两个枚举类型的属性分别代表了通知的状态以及通知的事件类型,并且在Watcher接口中,定义了一个方法作为触发通知以后的逻辑处理方法:

process(WatchedEvent event),接下来我们来看看这两个核心的枚举类及其参数

KeeperState

KeeperState定义了Watcher事件的所有状态类型,代码如下:

public enum KeeperState {
            @Deprecated
            Unknown (-1), //未知状态,已经废弃
            Disconnected (0),//断开连接
            @Deprecated
            NoSyncConnected (1),//没有连接,已经废弃
            SyncConnected (3),//已经连接
            AuthFailed (4),//权限异常
            ConnectedReadOnly (5),//当前连接仅仅支持读操作
            SaslAuthenticated(6),
            Expired (-112),
            Closed (7);//关闭连接
    .........................
}

EventType

除了KeeperState以外,EventType代表了通知的类型,代码如下:

public enum EventType {
            None (-1),//未知
            NodeCreated (1),//创建节点成功通知
            NodeDeleted (2),//节点移除通知
            NodeDataChanged (3),//当前节点数据变化通知
            NodeChildrenChanged (4),//子节点列表变化通知
            DataWatchRemoved (5),
            ChildWatchRemoved (6);
}

而在Zookeeper开发过程中,往往是两种类型参数组合返回,我们将常见的场景通知和组合列成表格,大概如下:

KeeperState EventType 触发场景
SyncConnected None 建立连接成功
NodeCreated 节点被创建
NodeDeleted 节点被删除
NodeDataChanged 节点内容变化
NodeChild renChanged 节点所属子节点列表变化
Disconnected None 断开连接
Expired None 超时
AuthFailed None 1.错误的Scheme进行权限检查 2.SASL权限检查失败

ACL权限操作

在Zookeeper中,提供了ACL权限机制来保障节点及对应数据的安全,但是需要注意的是Zookeeper中的ACL机制和传统的ACL并不一样,分别分为:权限模式(Scheme)、授权对象(ID)和权限(Permission),通常是以Scheme:ID:Permission方式组合成一个有效的ACL信息。接下来我们具体的学习这三种组成模块:

权限模式---Scheme

权限模式在Zookeeper中来确定权限验证过程中的校验策略。常见的策略有四种:

IP

IP模式一般通过IP地址进行粗粒度的权限控制,例如"ip:192.168.1.1/24"代表是192.168.1.*这个网段的IP进行的权限控制。

Digest

Digest是使用最多的一种权限模式,基于传统的"username:password"模式来控制对应的权限,当我们使用Digest方式来验证权限的时候,Zookeeper中先后两次进行编码处理,分别是SHA-1和BASE64算法,加密过程的实现在DigestAuthenticationProvider类的

generateDigest(String idPassword)方法中进行封装

World

World模式属于一种开放的权限模式,此模式下几乎没有任何权限控制,所有用户都可以随意对任何节点进行操作

Super

在Zookeeper中存在一个超级管理员的模式,此模式不需要主动设置,在任何其他的权限策略下都可以使用,称之为Super权限,一旦获取了Super权限,即拥有了超级管理员权限,可以对所有的节点进行任意操作。

授权对象--ID

授权对象指的是权限所在的用户或者指定的权限实体,而在不同的模式下,授权对象都是不同的,各个权限模式与授权对象的关系如下:

权限模式 授权对象
IP 通常是一个ip或者一个ip段,例如192.168.1.1
Digest 自定义的,一般为"username:password"
World anyone
Super 与Digest一样,需要指定一个超级管理员信息

权限--Permission

权限即指的是经过权限模式认证后的被允许的操作,在Zookeeper中,权限操作分为五大类:

  • Create(C):数据节点的创建权限,并且允许创建子节点
  • Delete(D):允许删除该节点的子节点
  • Read(R):允许对该数据节点进行读取,也可以获取子节点列表、读取子节点等
  • Write(W):数据节点的更新权限,允许对该节点内容修改
  • Admin(A):数据节点的管理权限,允许对其设置ACL权限操作

实现自定义权限控制器

一般开发中,Zookeeper自带的权限操作已经满足日常使用,但是如果需要特殊的权限控制操作,Zookeeper同样支持自定义一个权限控制器,在Zookeeper中,权限主要在org.apache.zookeeper. server.auth.AuthenticationProvider接口中定义,其代码定义如下:

public interface AuthenticationProvider {
    String getScheme();

    KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]);

    boolean matches(String id, String aclExpr);

    boolean isAuthenticated();

    boolean isValid(String id);
}

我们需要实现自定义的权限控制器只要实现当前接口,在实现完毕以后,我们将该自定义的权限控制器注册到Zookeeper服务中去,而注册的方式有两种:

1.系统属性配置

在Zookeeper启动的时候,在启动参数中指定:

-Dzookeeper.authProvider.l=xxx.MyauthProvider

2.配置文件方式

Zookeeper中的zoo.cfg配置文件中可以添加如下的配置:

authProvider.l=xxx.MyauthProvider

Super模式使用

在Zookeeper使用过程中,往往存在一个场景,即原来节点的创建者设置了ACL权限,但是这个创建者已经不再使用了,而其他的客户端想要使用该节点,该怎么做呢?这个时候就需要Super模式的用户出马了!使用超级管理员权限,具体的步骤如下:

1.在Zookeeper启动的时候添加系统属性:

-Dzookeeper.DigestAuthenticationProvider. superDigest=root:kWN6aNSbjcKWPqjiV7c
gON24raU=

即指定了root用户拥有超级管理员权限,设置好以后启动Zookeeper,即可在客户端中使用了

2.编写客户端代码使用当前Super权限的管理员操作节点,例如:

public class AuthSample_Super {
fin a l sta tic String PATH = "/zk-book";
public sta tic void m ain(String[] args) throws Exception {
ZooKeeper  zookeeperl = new ZooKeeper("domainl.book.zookeeper:2181",5000,
null);
zookeeperl.addAuthlnfo("digest", "root:tru e".getBytes());
zookeeperl.create( PATH, "init".getBytes(), Ids.CREATORALLACL, CreateMode.EPHEMERAL );
ZooKeeper zookeeper2 = new ZooKeeper("domainl.book.zookeeper:2181",50000,
n u ll);
zookeeper2.addAuthlnfo("digest", "root:zk-book".getBytes());
System.out.println(zookeeper2.getData(PATH, false, null));
ZooKeeper zookeeper3 = new ZooKeeper("domainl.book. zookeeper:2181",50000,
n u ll);
zookeeper3.addAuthlnfo("digest", "root:false " . getBytes());
System.out.println(zookeeper3.getData(PATH,false,null ));
} 
}

运行以后,结果如下:

[B<a7b7072
org.apache.zookeeper.KeeperException$NoAuthException:
KeeperErrorCode = NoAuth for /zk-book

可见root用户的确可以操作一个受限制的节点

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容