Zookeeper 源码解读系列, 单机模式(八)

前言

我们在上一篇文章中已经讲了Closs Session以及其和临时节点的关系,我们讲了在客户端输入quit命令的时候Zookeeper的源码是怎么一个流程。所以我们这一篇博客就是要讲解最后一个小点:ZookeeperACL机制是什么样的,在源码中又是一个什么过程。同时笔者认为有必要说明一下,本篇内容主要是为了解读源码服务的,所以Zookeeper的权限操作的内容,以及权限分分类组成就不会过多的涉及,这些网络上已经有了很多帖子去讲解。所以笔者这里除非是很有必要,否则不会过多讲解,如果以后有时间再补上一篇,请想要了解的同学自行查找资料学习。

此篇笔者希望尽量减少博客的长度,而且也前面的几篇博客为了跳转需要,已经贴了太多的冗余代码这也导致文章冗长。从这篇往后的博客中如果涉及到前期直接的代码跳转,笔者就全部放在一个代码块里用来减少文章的篇幅。同时本篇也将会是单机模式的最后一节内容,过了这一小节,我们就可以开始正式的探究Zookeeper的集群模式是什么样的流程了。本篇也会被收录到【Zookeeper 源码解读系列目录】中。

Zookeeper的权限操作简介

为了更好的讲解权限问题,我们找一个老熟人作为我们的主角:xiaoming。再说具体例子之前,我们先说一下Zookeeper的权限,Zookeeper一共有5大权限:

权限名称命令简写命令说明
Admina设置节点acl的权限
Createc创建节点的权限
Readr读节点权限
Writew写节点权限,包括set等等
Deleted删除节点,如果节点有子节点,则只能删除子节点。清空子节点以后才能删除当前节点。

那么设置权限的命令格式就是:setAcl /节点名字 auth:用户名:密码:权限。具体到示例就是:比如要给xiaoming管理、删除、读的权限,没有写权限,就是setAcl /node auth:xiaoming:123123:adr。如果我们这样给了xiaoming权限,那么xiaoming就不能写操作,这样当setData修改节点的值得时候,肯定有一个checkAcl得操作检查当前账号有没有权限。然后getAcl查看权限,还有添加权限addauth digest xiaoming:123123。也不仅仅是账号名字,也可以是ip,比如SetAcl /node auth:192.168.0.1:adr,中间的账号名字可以替换为ip,这就是给了客户端ip192.168.0.1adr权限。

客户端中的addauth

我们找addauth为例子进行讲解,还是一层层的进入,但是为了减少篇幅,这次要更加的简略:

----入口----> ZooKeeperMain.main();
----转到----> ZooKeeperMain.run();
----转到----> ZooKeeperMain.processCmd(cl);
----转到----> ZooKeeperMain.processZKCmd(co);

所以我们跟着代码一路就走到了processZKCmd(co)这个方法里,删去冗余代码:

protected boolean processZKCmd(MyCommandOptions co)
    throws KeeperException, IOException, InterruptedException
{
    /**略**/
    if (cmd.equals("***") && args.length >= **) {
        /**略**/
    }else if (cmd.equals("addauth") && args.length >=2 ) {
        byte[] b = null;
        if (args.length >= 3)
            b = args[2].getBytes();
        zk.addAuthInfo(args[1], b);
    } 
    /**略**/
    return watch;
}

然后找到if (cmd.equals("addauth")这个处理addauth的逻辑分支里,这里会用Zookeeper对象调用addAuthInfo(args[1], b)这个方法,那就点进去:

public void addAuthInfo(String scheme, byte auth[]) {
    cnxn.addAuthInfo(scheme, auth);
}

进入以后,这里又交给cnxn这个对象处理了,其实到了这里读过以前系列的同学应该可以才想到,下面客户=端的逻辑基本上没什么了,因为这个时候就是NIOClientCnxn处理了,这个类简单了说它就干了一个事儿:通过socket发送消息给到服务端,那我们进入看:

public void addAuthInfo(String scheme, byte auth[]) {
    if (!state.isAlive()) {
        return;
    }
    authInfo.add(new AuthData(scheme, auth)); //添加到队列里
    queuePacket(new RequestHeader(-4, OpCode.auth), null,
            new AuthPacket(0, scheme, auth), null, null, null, null,
            null, null); //构造packet,存入发送队列
}

果然发现authInfo.add(new AuthData(scheme, auth));先构造一个AuthData的实例对象,添加到本地队列authInfo,然后仍然是调用queuePacket(***)构造packet,加到outgoingQueue队列里,最后和别的命令一样传到服务端。但是这里要注意,我们输入的是addauth命令,但是封装的是OpCode.auth验证的操作,所以转到服务端的时候,我们要去看的分支是OpCode.auth

服务端中的addauth

我们接下来就要看服务端是怎么处理这个请求的了,按照之前的命令传递流程:

----入口----> QuorumPeerMain.main();
----转到----> ZooKeeperServerMain.main(args);
----转到----> ZooKeeperServerMain.initializeAndRun(args);
----转到----> ZooKeeperServerMain.runFromConfig(config);
----转到----> NIOServerCnxnFactory.startup(zkServer);
----转到----> NIOServerCnxnFactory.start();
----转到----> NIOServerCnxnFactory.run();
----转到----> NIOServerCnxn.doIO(k);
----转到----> NIOServerCnxn.readPayload();
----转到----> NIOServerCnxn.readRequest();
----转到----> ZooKeeperServer.processPacket(this, incomingBuffer);

到这里我们停下来分析AddAuth命令在这个方法里面是怎么走的:

public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
    /**略**/
    if (h.getType() == OpCode.auth) {//addauth命令
        LOG.info("got auth packet " + cnxn.getRemoteSocketAddress());
        AuthPacket authPacket = new AuthPacket();//创建一个authPacket实例
        //反序列化给authPacket赋值
        ByteBufferInputStream.byteBuffer2Record(incomingBuffer, authPacket);
        String scheme = authPacket.getScheme(); //获取scheme
        AuthenticationProvider ap = ProviderRegistry.getProvider(scheme);
        Code authReturn = KeeperException.Code.AUTHFAILED;
        if(ap != null) {
            try {
                //这里调用生成的实例,以digest为例
                authReturn = ap.handleAuthentication(cnxn, authPacket.getAuth());
            } catch(RuntimeException e) {
                LOG.warn("Caught runtime exception from AuthenticationProvider: " + scheme + " due to " + e);
                authReturn = KeeperException.Code.AUTHFAILED;                   
            }
        }
        if (authReturn!= KeeperException.Code.OK) {
            if (ap == null) {
                /**打印LOG**/
            } else {
                LOG.warn("Authentication failed for scheme: " + scheme);
            }
            ReplyHeader rh = new ReplyHeader(h.getXid(), 0,
                    KeeperException.Code.AUTHFAILED.intValue());
            cnxn.sendResponse(rh, null, null);
            cnxn.sendBuffer(ServerCnxnFactory.closeConn);
            cnxn.disableRecv();
        } else {
            /**打印LOG**/
            ReplyHeader rh = new ReplyHeader(h.getXid(), 0,
                    KeeperException.Code.OK.intValue());
            cnxn.sendResponse(rh, null, null);
        }
        return;
    } else {//不是auth,就直接来到这里
        /**略**/
        submitRequest(si); //提交,走处理器链逻辑
    }
    /**略**/
}

这个方法其实也是我们的老熟人儿了,之前我们一直在讲的是非OpCode.auth的情况,所以直接就到了下面的else里,提交请求submitRequest(si);直接走处理器链了。但是OpCode.auth就不是这么处理的了。这次我们定位到if (h.getType() == OpCode.auth)。首先代码里构造了一个AuthPacket的空对象,然后从incomingBuffer里反序列化出来数据给authPacket赋值,再往后构造String,获取传递进来的scheme = authPacket.getScheme();,这里scheme应该怎么理解呢?其实就是几种不同的Auth的方式一共有四种,分别是world、auth、ipdigest,这里就不细说区别了。接着根据传递进来的不同scheme生成不同的AuthenticationProvider,这个接口有三个继承类DigestAuthenticationProviderIPAuthenticationProviderSASLAuthenticationProvider会根据传入来生成不同的实例。比如我们传入的是digest则生成DigestAuthenticationProvider实例,我们就以这个为例子接着往下看。此时我们的ap已经是有值的了,那么就进入这个if(ap != null)块里,就直接去直接去调用DigestAP里面的方法ap.handleAuthentication(cnxn, authPacket.getAuth());,所以就去DigestAuthenticationProvider类里面找到这个handleAuthentication()方法:

public KeeperException.Code 
    handleAuthentication(ServerCnxn cnxn, byte[] authData)
{
    String id = new String(authData);//拿出传入的数据
    try {
        String digest = generateDigest(id);//构造签名
        if (digest.equals(superDigest)) { //认证超级管理员
            cnxn.addAuthInfo(new Id("super", ""));
        }
        cnxn.addAuthInfo(new Id(getScheme(), digest));//把addauth后面的信息存到authInfo里去
        return KeeperException.Code.OK;
    } catch (NoSuchAlgorithmException e) {
        LOG.error("Missing algorithm",e);
    }
    return KeeperException.Code.AUTHFAILED;
}

进入后,首先拿出传入的数据authData包装成为一个叫做id字符串,这个authData就是我们刚才输入的xiaoming:123123,再下面发现了一个生成签名的方法generateDigest(id);,这个生成签名只是给每一个用户一个特殊码用来区分用户的,里面是用的id加上SHA1生成唯一码,再用base64构建的,这里没什么可说的,想看的同学自己点进去看一眼就明白了,就是对用户名和密码进行一些字符串转换和加密。然后就是判断超级管理员账户了if (digest.equals(superDigest)),这个superDigest就是超级管理员,其默认值是从配置中读取出来的,也就是说我们启动前配置的超级管理员,就是在这里被加入服务器的。当然你执行addauth命令的时候直接指定用户名super也是可以的构造一个超级管理员的。如果是个普通用户,就直接存到authInfo里去cnxn.addAuthInfo(new Id(getScheme(), digest));,在后面就是直接return Code.OK,跳出继续走到cnxn.sendResponse(rh, null, null);返回给客户端,那么自此addauth命令彻底结束。总结来说,addauth命令就是在ServerCnxn类里面的authInfo这个List里面存把addauth里面的数据存入进入,这个ServerCnxn.authInfo属性我们一会儿还要说到,这里先记下。那么最终存入的形式就是

xiaoming:123123
zhangsan:1234124
ip:192.168.0.22  //如果auth是ip的话,我们放到后面讲

addauth ip

当执行addauth后面加的是一个ip又是什么样的情况呢?省略掉之前的步骤,这次方法的提供方就要换成IPAuthenticationProvider.handleAuthentication(cnxn, authPacket.getAuth())

    public KeeperException.Code
        handleAuthentication(ServerCnxn cnxn, byte[] authData)
    {
        String id = cnxn.getRemoteSocketAddress().getAddress().getHostAddress();
        cnxn.addAuthInfo(new Id(getc(), id));
        return KeeperException.Code.OK;
    }

进入方法以后,注意cnxn.addAuthInfo(new Id(getScheme(), id));这里。我们看new Id(getScheme(), id)传进入的参数有两个,第一个是Scheme就是我们传入的"ip"这个字符串,第二个是id并不是我们要传入的数据,而是取得客户端host地址的一个列表元素,而自己写入的192.168.0.1反而是无效的。所以如果你输入addauth ip 192.168.0.1命令是无效的,Zookeeper会自动把你输入的ip忽略掉而替换为连接的ip。这一点其实不太明白Zookeeper的开发人员是怎么想的。但是要加ip白名单应该怎么办呢?只有用setAcl命令才能给ip加入白名单列表,就是 setAcl /nodename ip:192.168.1.33:cdrwa。这样这个节点就可以对特定的ip打开所有的权限了,如下图所示。
在这里插入图片描述
可以看到这里其实也是把ip和对应的数值绑定一个类似的map结构中,这点和用户名密码是一致的。但是这里要提醒一点,如果想要给某个ip加上权限,一定要首先把本机添加进去,否则就会尴尬的发现除了你刚刚添加的ip,其余的客户端都无法操作这个node了。可以看出Zookeeper这里的权限控制的其实非常的不好。

ACL的验证与设置

通过上述addauth的代码分析,我们输入的信息都被存入了ServerCnxn.authinfo这个对象存里面。而authinfo对象的内容就是账户名字和密码,构造的id这些信息,但是这个到底有什么用呢?那么我们就得看setACL这个方法,但是我们做这些操作的时候会首先验证权限,setACL其实就是对某一个节点存一个ACL的记录,所以我们得先关注checkACL方法。所以我们就去服务端去看下,到底是怎么checkACL以及怎么setACL的,首先是客户端:

----入口----> ZooKeeperMain.main();
----转到----> ZooKeeperMain.run();
----转到----> ZooKeeperMain.processCmd(cl);
----转到----> ZooKeeperMain.processZKCmd(co);

还是走到了processZKCmd(co)这个方法里,这回我们找到if (cmd.equals("setAcl") && args.length >= 3)这个逻辑块里面:

protected boolean processZKCmd(MyCommandOptions co)
    throws KeeperException, IOException, InterruptedException
{
    /**略**/
    if (cmd.equals("***") && args.length >= **) {
        /**略**/
    }else if (cmd.equals("setAcl") && args.length >= 3) {
         path = args[1];
         stat = zk.setACL(path, parseACLs(args[2]), 
         			args.length > 4 ? Integer.parseInt(args[3]) : -1);
         printStat(stat);
     }
    /**略**/
    return watch;
}

进入以后找到zk.setACL(path, parseACLs(args[2]), args.length > 4 ? Integer.parseInt(args[3]) : -1);方法,点进去:

public Stat setACL(final String path, List<ACL> acl, int aclVersion)
    throws KeeperException, InterruptedException
{
    final String clientPath = path;
    PathUtils.validatePath(clientPath); //验证路径
    final String serverPath = prependChroot(clientPath);
    RequestHeader h = new RequestHeader();
    h.setType(ZooDefs.OpCode.setACL); //OpCode.setACL
    SetACLRequest request = new SetACLRequest();
    request.setPath(serverPath);
    if (acl != null && acl.size() == 0) {
        throw new KeeperException.InvalidACLException(clientPath);
    }
    request.setAcl(acl);
    request.setVersion(aclVersion);
    SetACLResponse response = new SetACLResponse();
    ReplyHeader r = cnxn.submitRequest(h, request, response, null); //提交
    if (r.getErr() != 0) {
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                clientPath);
    }
    return response.getStat();
}

这里有什么就不分析了,主要是看h.setType(ZooDefs.OpCode.setACL);这句话,setAcl命令的操作码就是OpCode.setACL所以我们去服务端找的就是OpCode.setACL,而不是OpCode.auth了。提交请求以后直接转到服务端:

----入口----> QuorumPeerMain.main();
----转到----> ZooKeeperServerMain.main(args);
----转到----> ZooKeeperServerMain.initializeAndRun(args);
----转到----> ZooKeeperServerMain.runFromConfig(config);
----转到----> NIOServerCnxnFactory.startup(zkServer);
----转到----> NIOServerCnxnFactory.start();
----转到----> NIOServerCnxnFactory.run();
----转到----> NIOServerCnxn.doIO(k);
----转到----> NIOServerCnxn.readPayload();
----转到----> NIOServerCnxn.readRequest();
----转到----> ZooKeeperServer.processPacket(this, incomingBuffer);
----转到----> ZooKeeperServer.submitRequest(si);
----转到----> PrepRequestProcessor.processRequest(si); //添加请求到队列
----转到----> PrepRequestProcessor.run();
----转到----> PrepRequestProcessor.pRequest(request);

我们就直接来到了请求处理器链这里,进入pRequest(request)方法,找到OpCode.setACL的分支:

protected void pRequest(Request request) throws RequestProcessorException {
		/**略**/
    try {
        switch (request.type) {
            case OpCode.***:
            /**略**/
            break;
        case OpCode.setACL: //到这里来
            SetACLRequest setAclRequest = new SetACLRequest();                
            pRequest2Txn(request.type, zks.getNextZxid(), request, setAclRequest, true);
            break;
        default:
            LOG.warn("unknown type " + request.type);
            break;
        }
    } catch (***Exception e) {
    	/**Exceptions**/
    }
    request.zxid = zks.getZxid();
    nextProcessor.processRequest(request);
}

继续进入pRequest2Txn(***)方法,一样找到OpCode.setACL的分支:

protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize)
        throws KeeperException, IOException, RequestProcessorException
{
    /**略**/
    switch (type) {
        /**略**/
        case OpCode.***:
            /**略**/
            break;
        case OpCode.setACL:
            zks.sessionTracker.checkSession(request.sessionId, request.getOwner());
            SetACLRequest setAclRequest = (SetACLRequest)record;//拿出数据
            if(deserialize)//反序列化
                ByteBufferInputStream.byteBuffer2Record(request.request, setAclRequest); 
            path = setAclRequest.getPath(); //拿出路径
            validatePath(path, request.sessionId);//验证路径
            listACL = removeDuplicates(setAclRequest.getAcl());//去重
            if (!fixupACL(request.authInfo, listACL)) {
                throw new KeeperException.InvalidACLException(path);
            }
            nodeRecord = getRecordForPath(path);
            checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN,
                    request.authInfo); //验证ACL
            version = setAclRequest.getVersion();
            currentVersion = nodeRecord.stat.getAversion();
            if (version != -1 && version != currentVersion) {
                throw new KeeperException.BadVersionException(path);
            }
            version = currentVersion + 1;
            request.txn = new SetACLTxn(path, listACL, version);//列表事务化,为持久化准备
            nodeRecord = nodeRecord.duplicate(request.hdr.getZxid());
            nodeRecord.stat.setAversion(version);
            addChangeRecord(nodeRecord);
            break;
        default:
            LOG.error("Invalid OpCode: {} received by PrepRequestProcessor", type);
    }
}

进入这个分支以后,略过拿数据、验证等等操作这些都和以前一样,不多说。直接找到listACL = removeDuplicates(setAclRequest.getAcl());这句话是用来去重的,比如输入权限是addddr,经过这个方法以后就会简化为adr。再往下看到一个if判断里面有一个方法fixupACL(request.authInfo, listACL),这方法其实就是真正修改权限的地方,这里的两个参数request.authInfo是需要被验证的id信息,就是刚刚设置的请求赋权的账户列表,里面寸的就是我们之前小节里面说的id,listACL就是分配给节点的acl列表,就是id和其对应的权限的列表。这个我们一会儿再看,我们先看验权的部分:checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN, request.authInfo);这里,这个方法一共传入了四个参数:zks是连接信息;acl是节点已经存在的节点规则;ZooDefs.Perms.ADMIN是当前这个操作需要的权限,这里是要修改,所以我们要的权限就是ADMINrequest.authInfo是需要被验证的id信息,这里的数据就是我们调用addauth加入进去的那些信息,用来看是谁要被验证:

static void checkACL(ZooKeeperServer zks, List<ACL> acl, int perm,
        List<Id> ids) throws KeeperException.NoAuthException {
    if (skipACL) {//跳过ACL的配置
        return;
    }
    if (acl == null || acl.size() == 0) {//当前节点是否没有任何acl规则
        return;
    }
    for (Id authId : ids) {
        if (authId.getScheme().equals("super")) {//如果传入super,就直接返回通过
            return;
        }
    }
    for (ACL a : acl) { //开始循环验证规则
        Id id = a.getId(); //当前节点上的id
        if ((a.getPerms() & perm) != 0) {
            if (id.getScheme().equals("world")
                    && id.getId().equals("anyone")) {
                return;
            }
            AuthenticationProvider ap = ProviderRegistry.getProvider(id
                    .getScheme());
            if (ap != null) {
                for (Id authId : ids) {                        
                    if (authId.getScheme().equals(id.getScheme())
                            && ap.matches(authId.getId(), id.getId())) {
                        return;
                    }
                }
            }
        }
    }
    throw new KeeperException.NoAuthException();
}

进入这个方法以后,首先看到if (skipACL),这个是检查有没有配置过跳过ACL的配置,就是在配置文件中配置skipACL=yes的参数了,这里就会直接跳过,不在验证ACL了,这个也是Zookeeper验权的第一个后门,只要你知道zoo.cfg文件在哪里,重启了就可以绕过验证。然后第二个验证if (acl == null || acl.size() == 0)如果当前节点没有任何acl规则,那就不需要验证了,直接返回。再往下for (Id authId : ids)循环,如果发现传入的id列表里面有super用户,也是直接跳过。这里就是第二个验权的后门,我只要在setACL之前,运行一个addauth super的命令,就不用验证了。因为这里的逻辑就是只要ids里面发现一个super用户,所有的验证都会被跳过,虽然我们知道这里是想实现超级管理员用户的作用的,但是确实逻辑上有些不妥当。再往下的for (ACL a : acl)循环就是开始验证真正我们传入的规则了,进入循环以后首先把当前节点上的id拿出来Id id = a.getId();,要注意这个a也是当前节点上的acl,不要搞混了。接着取出节点上的权限a.getPerms()和验证的权限perm做与&操作,如果结果不等于0,说明符合权限,就是“我”这个节点有“你”想要的权限,至于为什么要&操作我们最后再讲,涉及到位移操作比较麻烦,大家先记住就好。进入以后,如果发现拿到的schemeworld或者anyone直接验权过。再往下拿到验证器AuthenticationProvider对每一个请求authIdscheme和当前节点idscheme进行对比,两者都通过才会通过验证,如果不通过,抛出异常。就是这个if语句里面的条件:先authId.getScheme().equals(id.getScheme()对比各自的scheme,再对比是不是匹配ap.matches(authId.getId(), id.getId())。这里就是checkAcl的逻辑。

Zookeeper原生ACL验证的缺陷

经过刚才我们分析了,除了有后门以外,其实非常的不好用,因为首先要验权就得addauth把所有的账户添加进去,然后再进行其他的操作。但是addauth命令就像一个登陆指令,只要执行了,就像已经在这个账户下登陆的,所以如果权限不够,那基本上干不成其他操作了。就比如我刚才说的,设置了一个ip白名单,但是我本身的客户端却无法再操作那个node了。

除了这些以外,其实还有一个bug,那就是它会把所有的用户给同步统一成一种权限。比如说我们本身有user1:ad,就是说user1对这个节点有admindelete的权限。我们现在想给这个节点添加两个用户user2user3,由于这个节点已经有了user1,且user1还有admin权限。那么我如果不加上user1,也就是addauth user1,那么验证就不通过。所以必须加上user1,然后我们想要只修改user2的账户给adrw的权限, 用(也只能用)setAcl /node auth:user2:123123:adrw命令,那么运行后我们会神奇的发现:user1user2user3三个账号同时都有adrw的权限。怎么就出现这样的问题呢?问题其实就在fixupACL(request.authInfo, listACL)这个方法里,那么我们进入看一下这个参数,首先authInfo里面包含了所有addauthid对吧,此时user1user2user3里面都有。然后listACL就是我想要修改的权限。那么我们进入方法:

private boolean fixupACL(List<Id> authInfo, List<ACL> acl) {
    if (skipACL) {
        return true;
    }
    if (acl == null || acl.size() == 0) {
        return false;
    }
    Iterator<ACL> it = acl.iterator(); //这里的acl就是addauth后所有的内容
    LinkedList<ACL> toAdd = null;
    while (it.hasNext()) {
        ACL a = it.next();
        Id id = a.getId();
        if (id.getScheme().equals("world") && id.getId().equals("anyone")) {
            // wide open
        } else if (id.getScheme().equals("auth")) {
            it.remove();
            if (toAdd == null) {
                toAdd = new LinkedList<ACL>();
            }
            boolean authIdValid = false;
            for (Id cid : authInfo) {//这里循环的cid不包括权限
                AuthenticationProvider ap =
                    ProviderRegistry.getProvider(cid.getScheme());
                if (ap == null) {
                    LOG.error("Missing AuthenticationProvider for "
                            + cid.getScheme());
                } else if (ap.isAuthenticated()) {
                    authIdValid = true;
                    //对toAdd里面的cid添加a.getPerms()的权限
                    toAdd.add(new ACL(a.getPerms(), cid));
                }
            }
            if (!authIdValid) {
                return false;
            }
        } else {
            AuthenticationProvider ap = ProviderRegistry.getProvider(id
                    .getScheme());
            if (ap == null) {
                return false;
            }
            if (!ap.isValid(id.getId())) {
                return false;
            }
        }
    }
    if (toAdd != null) {
        for (ACL a : toAdd) {//把这个add循环又加到了acl中
            acl.add(a);
        }
    }
    return acl.size() > 0;
}

方法比较长,但是关注点其实没多少,首先一个点就是Iterator<ACL> it = acl.iterator();,这个it取的就是我们要添加的权限,也就是addauth后所有的内容。然后对每一个id进行for (Id cid : authInfo)循环,在循环里做了一个什么事儿呢?对toAdd这个权限列表里面的cid添加a.getPerms()的权限,而这个a就是传进来的acl列表里面的元素,这里全部的id被全部循环了。有点绕,简单来说就是把adrw这个四个权限给user1、user2、user3循环了一遍。这个时候user1、user2、user3已经都有了adrw权限,最后再用一个for循环把toAdd里面的内容全部添加到acl里面,最终返回的用户都有同样的权限。这里为什么Zookeeper的作者要这么设计其实笔者也很困惑,也可能国外的大佬们确实想法独特,所以只能说,Zookeeper的ACL机制能少用则少用,还是在我们自己的程序外面做好控制比较好。

Perms机制

我们先把源码贴出来

    public interface Perms {
        int READ = 1 << 0;  //0000 0001 1

        int WRITE = 1 << 1; //0000 0010 2

        int CREATE = 1 << 2; //0000 0100 4

        int DELETE = 1 << 3;//0000 1000 8

        int ADMIN = 1 << 4;//0001 0000 16

        int ALL = READ | WRITE | CREATE | DELETE | ADMIN; // 0001 1111 31
    }

这里的权限标识方法其实是用16进制标识的,比如我们的READ权限就是0000 0001,转化为10进制就是1,同理ADMIN是0001 0000,转化为10进制就是16。那么我们的adr权限是什么呢?就是这些权限的16进制加在一起0001 1001,转化为10进制就是25。所以如果我要运算一个节点是不是有ADMIN权限,最快的方法就是&操作,只要&的结果不是0,那就说明权限是有的。比如25 & 16就是0001 1001 & 0001 0000 = 0001 0000就不是0,说明有权限。反之25 & 2就是0001 1001 & 0000 0010 = 0000 0000结果就是0,说明没有权限。其实很多架构和程序都有这样的操作,因为直接操作数字码是最快的。如果这里也看不懂,还是自己百度一下逻辑运算是个什么东西,真的没有办法再解释了。

总结

讲完ACL以后,我们看到ACL这里其实还是非常不好用的,但是瑕不掩瑜,Zookeeper还是非常牛的一款分布式架构,当然也可能是笔者道行比较肤浅,没有办参透其中的奥秘。但是不管如何的Zookeeper的单机模式终于告一段落。下面就将是集群模式的源码分析的内容了,感谢大家不吝时间的支持,看我啰嗦到现在,我们集群再见。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值