zookeeper——NWU_LK

zookeeper

zookeeper的设计目标
  1. 高性能,因为zookeeper将数据都放到了内存里,适用于以读为主的应用场景
  2. 高可用,zookeeper一般以集群的方式对外提供服务,每台机器间保持通信,只要有半数以上机器存活,就能正常对外进行服务
  3. 严格顺序访问,对于来自客户端的每个更新请求,zookeeper都分配一个全局唯一的递增编号,这个编号反映了事务执行的先后顺序
zookeeper的特点
  1. 一个领导者Leader,多个跟随者Follower
  2. 集群中只要有半数以上的机器存活就能对外正常服务
  3. 全局数据一致性,每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的
  4. 更新要求顺序进行,来自同一个Client的更新请求按其发送顺序依次执行
  5. 数据更新原子性,一次数据更新要么全部成功,要么失败。
  6. 实时性,在一定时间范围内,Client能读到最新数据。
zookeeper的数据结构

在这里插入图片描述
整体可以看做一棵树,每个节点叫做ZNode,每个ZNode默认能存储1MB的数据,每个ZNode都可以通过其路径唯一标识。ZNode大致分为三部分:

  1. 节点的数据,即znode data
  2. 节点的子节点children
  3. 节点的状态stat,用来描述当前节点的创建、修改记录,包括cZxid、ctime等

stat属性介绍:

  • cZxid:数据节点创建时的事务id
  • mZxid:数据节点最后一次更新的事务id
  • pZxid:数据节点的子节点最后一次被修改时的事务ID
  • ctime:数据节点创建时的时间
  • mtime:数据节点最后一次更新的时间
  • dataVersion:节点数据的更改次数
  • cversion:子节点的更改次数
  • aclVersion:节点的ACL(权限列表)的更改次数
  • ephemeralOwner:如果znode是临时节点,则这是znode所有者的 session ID。 如果znode不是临时节点,则该字段设置为0。
  • dataLength:这是znode数据字段的长度。
  • numChildren:这表示znode的子节点的数量。

ZNode可以分为以下几类:

  • 持久化节点
  • 持久化有序节点
  • 临时节点
  • 临时有序节点
zookeeper的应用场景
  1. 配置管理
  2. 分布式锁:比如某个服务在操作时,其他服务都不能进行操作,对该操作进行加锁
  3. 集群管理
  4. 生成分布式唯一ID
zookeeper的安装
  1. 下载安装包,并上传到虚拟机
    zookeeper的tar包下载

  2. 解压到/usr/local目录下,并打开

  3. 在zookeeper的根目录下创建data目录用来存储内存快照及事务文件日志:mkdir data

  4. 打开conf目录,根据样例配置文件复制一份配置文件:cp zoo_sample.cfg zoo.cfg

  5. 修改新配置文件的内容,主要指定data目录
    在这里插入图片描述

  6. 在zookeeper的bin目录下启动zookeeper服务器:./zkServer.sh start,用命令查看是否启动./zkServer.sh status

  7. 使用客户端登录:./zkCli.sh

  8. 退出客户端:quit

  9. 关闭服务器:./zkServer.sh stop

zookeeper的常用命令

登录客户端后使用以下命令:

  • 新增节点:create [-s] [-e] path data。其中-s和-e为可选参数,-s表示有序节点,-e表示临时节点。
  • 获取节点数据:get path
  • 修改节点数据:set path data [version],修改之后dataVersion会加1,如果传入的version和node本身的dataNode不符合,那么会修改失败。
  • 删除节点数据:delete path [version],version是可选参数,如果传入的version和node本身的dataNode不符合,那么会删除失败;当删除的节点下面还有子节点是也会删除失败。
  • 删除节点和子节点:rmr path
  • 查看节点状态:stat path,和get命令相比不返回数据信息
  • 查看子节点列表:ls path
  • 客户端对节点数据改变事件监听:get path watch,如果path对应的节点数据发生变化,那么会在客户端给与提醒
  • 客户端对节点状态改变事件监听:stat path watch,如果path对应的节点数据发生变化,那么会在客户端给与提醒
  • 客户端对节点的子节点增加删除监听:ls path watch,如果path对应的节点数据发生变化,那么会在客户端给与提醒
  • 远程登录zookeeper服务器:./zkCli.sh -server ip
zookeeper的权限控制

zookeeper对节点权限的控制主要通过access control list访问控制列表来实现。主要使用schema:id:permission来标识,其中schema表示授权的模式,id表示授权的对象,permission表示授予的权限。

授权相关命令

  • 读取对节点的权限:getAcl path
  • 设置权限:setAcl path acl,例如setAcl /node1 ip:192.168.10.10:crwda表示ip为192.168.10.10的机器对该节点有所有权限
  • 添加认证用户:addauth schema auth,该操作一般对应第3、4种授权模式

授权模式如下:

(在zookeeper中可以多种模式相结合,只需要用逗号将其分开即可。)

  • world:只有一个用户anyone,代表登录的所有人,也是默认的模式
    命令:setAcl <path> ip:<ip>:<acl>
  • ip:对客户端使用IP地址进行认证
    命令:setAcl <path> world:anyone:<acl>
  • auth:使用已添加认证的用户认证
    创建认证用户命令:addauth digest <user>:<password>
    授权命令:setAcl <path> auth:<user>:<acl>
  • digest:使用“用户名:密码”方式认证
    授权命令:setAcl <path> digest:<user>:<password>:<acl>,这里的密码必须是加密后的密码,可以用命令echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64对指定用户名和密码进行加密然后作为密码输入
  • super:超管配置,需要手动修改zkServer.sh脚本(略)

授予的对象:权限赋予的实体,即IP地址或者用户

授予的权限:

  • create(c ):可以在当前节点下创建子节点
  • delete(d ):可以删除子节点
  • read(r ):可以读取节点数据和显示子节点的列表
  • write(w ):可以创建、修改、删除节点数据
  • admin(a ):可以设置节点访问控制列表权限
java操作zookeeper

zookeeper的连接是异步的,首先引入相关依赖

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
</dependency>
  1. 连接zookeeper
public static void main(String[] args) {
    try {
    	//创建计数器对象
        CountDownLatch countDownLatch=new CountDownLatch(1);
        //参数1:IP地址,参数2:客户端与服务器之间的会话超时毫秒时间,参数3:监视器对象
        ZooKeeper zookeeper=new ZooKeeper("192.168.189.135:2181", 5000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
                    System.out.println("连接创建成功");
                    countDownLatch.countDown();
                }
            }
        });
        countDownLatch.await();
        zookeeper.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  1. 创建节点
  • 同步方式:
//参数1:节点路径,参数2:节点数据,参数3:权限列表,参数4:节点类型
zookeeper.create("/node1","node1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

其中权限也可以自定义,比如

List<ACL> acls=new ArrayList<>();
Id id=new Id("world","anyone");
//只读权限
acls.add(new ACL(ZooDefs.Perms.READ,id));
zookeeper.create("/node2","node2".getBytes(), acls, CreateMode.PERSISTENT);
  • 异步方式:
zookeeper.create("/node3", "node3".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new AsyncCallback.StringCallback() {
    @Override
    public void processResult(int rc, String path, Object ctx, String name) {
        System.out.println(rc);  //0代表成功
        System.out.println(path); //节点的路径
        System.out.println(ctx);  // 节点的上下文
        System.out.println(name); //节点的名称
    }
},"i am context");
  1. 更新节点
    同步方式:stat setData(String path, byte[] data, int version)
    异步方式:void setData(String path, byte[] data, int version, StatCallback cb, Object ctx)
  2. 删除节点
    同步方式:void delete(String path,int version),version为-1表示不考虑版本
    异步方式:void delete(String path, int version, VoidCallback cb, Object ctx)
  3. 读取节点数据
    同步方式:byte[] getData(String path, boolean watch, Stat stat),第二个参数表示使用zookeeper对象创建时的监听器,此处也可以是自定义的监视器对象,stat可以获取节点的状态。
    异步方式:void getData(String path, boolean watch, DataCallback cb, Object ctx)
  4. 读取子节点数据
    同步方式:List<String> getChildren(String path, boolean watch),第二个参数也可以是监视器对象。
    异步方式:void getChildren(String path, boolean watch, ChildrenCallback cb, Object ctx)
  5. 判断节点是否存在
    同步方式:Stat exists(String path, boolean watch),当返回的对象为空时表示节点不存在。
    异步方式:exists(String path, boolean watch, StatCallback cb, Object ctx)
事件监听机制

watcher概念:zookeeper提供了数据发布订阅功能,多个订阅者可能同时监听某一特定主题对象,当该主题对象的自身状态发生变化时,会实时通知所有订阅者。zookeeper采用watcher机制实现发布订阅功能。该机制在被订阅对象发生变化时会异步的通知客户端。watcher机制和观察者模式类似。

watcher的架构:

  • zookeeper服务端
  • zookeeper客户端
  • 客户端的ZKWatchManager对象

客户端首先将watcher注册到服务端,同时将watcher保存在客户端的管理容器(ZKWatchManager)中。当节点数据发生变化,那么服务端会通知客户端,接着客户端的watch管理器会触发相关watcher来回调用处理流程。
在这里插入图片描述
watcher的特性:

  • 一次性,一旦被触发就会被移除,再次使用就要重新注册
  • watcher回调是顺序串行化执行的
  • watchEvent是最小的通信单元,结构上只包含童稚状态、事件类型和节点路径,并不会告诉节点变化前后具体内容
  • watcher只有在当前session彻底失效时才会无效,若在session有效期内重连成功,则watcher依然存在,仍可接受通知

watcher的通知状态(KeeperState):客户端与服务端连接状态发生变化时的通知类型

枚举属性说明
SyncConnected客户端与服务器正常连接时
Disconnected客户端与服务器断开时
Expired会话session失效时
AuthFailed身份认证失败时

watcher的事件类型(EventType):数据节点发生变化时对应的通知类型

枚举属性说明
None
NodeCreated监听的节点被创建时
NodeDeleted监听的节点被删除时
NodeDataChanged监听的节点内容被修改时
NodeChildrenChanged监听节点的子节点列表发生变化时

捕获相应事件

注册方式CreatedChildrenChangedChangedDeleted
k.exists()可监控可监控可监控
k.getData()可监控可监控
k.getChildren()可监控可监控
zookeeper作为配置中心

设计思路:

  1. 连接zookeeper服务器
  2. 读取zookeeper中的配置信息,注册watcher监听器,存入本地事件
  3. 当zookeeper中的配置信息发生变化时,通过watcher的回调方法捕获数据变化事件
  4. 重新获取配置信息
zookeeper生成分布式唯一ID

再过去的单库单表系统中,通常可以使用数据库字段自带的auto_increment属性来自动为每条记录生成一个唯一ID。但是分库分表之后,就无法依靠数据库的auto_increment属性来标识一条记录了。此时我们就可以用zookeeper生成分布式全局唯一ID。zookeeper通过创建临时有序节点来实现生成分布式唯一ID(主要是截取该临时有序节点path中的序号,该序号不会重复)

zookeeper设计分布式锁

设计思路:

  1. 每个客户端往/locks下创建临时有序节点/locks/lock_num,创建成功后/locks下面会有为每个客户端创建的对应的节点。如/locks/lock_0000001
  2. 客户端获取/locks下子节点,并进行排序,判断排在最前面的是否为自己,如果自己排在最前面,那么代表获取锁成功
  3. 如果自己的锁节点不在第一位,则监听自己前一位的锁节点
  4. 当前一位锁节点对应的客户端执行完成,释放了锁,将会触发监听的逻辑
  5. 监听客户端重新执行第二步逻辑,判断自己是否能获得锁。

实现分布式锁的创建

public class MyLock {
    CountDownLatch countDownLatch=new CountDownLatch(1);
    ZooKeeper zookeeper;
    private static final String LOCK_ROOT_PATH="/locks";
    private static final String LOCK_NODE_NAME="lock_";
    private String lockPath;
    public MyLock(){
        try {
            zookeeper=new ZooKeeper("192.168.189.135:2181", 5000, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    if (watchedEvent.getType()==Event.EventType.None){
                        if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
                            System.out.println("连接创建成功");
                            countDownLatch.countDown();
                        }
                    }
                }
            });
            countDownLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //用于监视上一个节点的监视器
    Watcher watcher=new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            if (event.getType()==Event.EventType.NodeDeleted){
                synchronized (this){
                    notifyAll();
                }
            }
        }
    };
    public void acquireLock() throws Exception{
        createLock();
        attemptLock();
    }
    private void createLock() throws Exception {
        //判断 /locks 节点是否存在
        Stat stat = zookeeper.exists("/locks", false);
        if (stat==null){
            zookeeper.create("/locks",new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        //在/locks下创建临时有序节点
        lockPath = zookeeper.create("/locks/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    }
    private void attemptLock() throws Exception {
        List<String> children = zookeeper.getChildren("/locks", false);
        Collections.sort(children);
        int index=children.indexOf(lockPath.substring("/locks".length()+1));
        if (index==0){
            System.out.println("获取锁成功");
        }else {
            //获取上一个节点
            String path = children.get(index - 1);
            Stat stat = zookeeper.exists("/locks/" + path, watcher);
            if (stat==null){  //如果上一个节点已经释放锁
                attemptLock();
            }else {
                synchronized (watcher){
                    watcher.wait();
                }
                attemptLock();
            }
        }
    }
    private void releaseLock() throws Exception{
        zookeeper.delete(this.lockPath,-1);
        zookeeper.close();
        System.out.println("锁已释放");
    }
}
zookeeper集群的搭建
  • 集群搭建:略
ZAB协议

zookeeper通过zab协议来保证分布式事务的最终一致性,基于zab协议,zookeeper集群中的角色主要有以下三类:

  • 领导者:领导者负责进行投票的发起和决议,更新系统状态
  • 追随者:follower用于接收客户端请求并向客户端返回结果,在选主过程中参与投票
  • 观察者:可以接受客户端连接,将写请求转发给leader节点。但观察者不参与投票,只同步leader的状态

针对集群中的读请求,每一个节点都含有最新的数据,因此每个节点都能处理读请求;如果是写请求,那么只能有leader节点处理,即便是与follower相连的客户端发起的写请求,该请求也会被转发给leader节点处理,处理写请求分为以下步骤:

  1. leader从客户端收到一个写请求
  2. leader生成一个新的事务并为这个事务生成唯一的ZXID
  3. leader将这个事务提议(proposal)发送给所有的followers节点
  4. follower节点将收到的事务请求加入到历史队列(history queue)中,并发送ack给leader
  5. 当leader收到半数以上的ack消息,leader会发送commit请求
  6. 当follower收到commit请求后会将历史队列中的事务请求commit
zookeeper的leader选举

服务器状态:

  • looking:寻找leader的状态。表示需要进入leader选举
  • leading:领导者状态。表明当前服务器是leader
  • follower:追随者状态。表示当前服务器是follower
  • observing:观察者状态。表明当前服务器是observer

Leader选举是保证分布式数据一致性的关键。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举。

  • 服务器启动时的leader选举
    若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下。
  1. 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,其中myid表示该节点的序号,序号越大权重越大。此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
  2. 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
  3. 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,先检查ZXID,ZXID大的服务器优先成为leader,如果ZXID相同,那么就比较myid,myid大的优先作为leader服务器
  4. 统计投票。每次投票后,服务器会统计投票信息,判断是否有半数以上的机器接收到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2,0)的投票信息,此时便认为已经选出了leader。
  5. 改变服务器状态。一旦确认了leader,每个服务器就会变更为相应的角色。
  • 服务器运行时的leader选举
    当leader挂掉,并且剩余的节点数大于半数时,zookeeper可以接着对外提供服务。选举过程如下
  1. 变更状态。Leader挂后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
  2. 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 122),(3, 122),然后各自将投票发送给集群中所有机器。
  3. 接收来自各个服务器的投票。与启动时过程相同。
  4. 处理投票。与启动时过程相同,此时,Server1将会成为Leader。
  5. 统计投票。与启动时过程相同。
  6. 改变服务器的状态。与启动时过程相同。
zookeeper中的观察者

观察者特点:

  1. 不参与leader选举
  2. 不参与集群中写数据时的ack反馈

观察者的配置:

  1. 首先在想成为observer角色的配置文件中修改peerType=observer
  2. 在所有server的配置文件中,配置observer模式的server那一行追加:obersver,例如
    server.3=192.168.60.130:2289:3389:obersver
JAVA连接zookeeper集群
zookeeper=new ZooKeeper("192.168.189.135:2181,192.168.189.135:2182,192.168.189.135:2183", 5000, new Watcher() {
    @Override
    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType()==Event.EventType.None){
            if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
                System.out.println("连接创建成功");
                countDownLatch.countDown();
            }
        }
    }
});

在这里插入图片描述

低级的欲望通过放纵就可以获得,高级的欲望通过自律方可获得,顶级的欲望通过煎熬才可获得

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值