动物园(Zookeeper)里有什么?

1.zookeeper定义

ZooKeeper 是一个开源的,分布式应用程序协调服务。
可以用于:数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等场景。

怎么理解分布式应用程序协调服务呢?很久很久以前一个开放服务访问的人不多,所有的模块功能都在一个单体应用中,部署在一台服务器上,各个模块之间在本机通信很方便,但随着访问量和业务复杂度的提升,需要把应用切分成多个模块,分别将他们部署到不同的服务器,称之为分布式服务。这个时候这个模块在不同机器上的通信就没那么方便了,zookeeper就可以解决这个问题。而它的不同使用场景,其实也是各个服务器直接通信内容的不同,以及通信机制的差异。

命名服务是一个比较易于理解的场景,在上面提到了把一个大应用切分成多个模块,分别部署到不同的服务器。再后来一台服务器处理一个服务也忙得焦头烂额,这个时候就需要把一个模块部署到多台服务器,但提供的服务都是一样的,那么如何区分这些模块呢?

于是就给他们取名服务A,服务B,服务C,并把这些信息通过zookeeper记录下来(如何记录我们后面再谈),当有人需要调用这个服务时,访问zookeeper就会告诉他去找哪个服务。这里面又可以加入负载均衡的机制,让调用者知道服务A现在比较忙,还是去找服务B吧。这只是个简单的案例,通过这个案例可以大体上知道,zookeeper干这么件事,但实际上命名服务做得也不止这么简单。

特性:

  • 顺序性:客户端发起的请求会按发送顺序,被zk处理
  • 原子性:更新操作要么成功要么失败,不会出现中间状态
  • 单一系统镜像:每个服务器的数据结构是一致的,但有时写操作来不及同步到所有follower,可能会出现部分follower的数据不是最新的情况。
  • 可靠性:一个更新操作一旦被接受即不会意外丢失,除非被其它更新操作覆盖 最终一致性:写操作最终(而非立即)会对客户端可见

2.数据模型

有人说zookeeper是一种服务
有人说zookeeper其实就是颗树(注:数据结构中的树)

他们说的都对,那服务怎么会和树扯上关系呢?
zookeeper内部会维护这样一个数据模型,一个由多个节点和枝干构成的树,针对这颗树又约定了一些规范,提供了一些操作方法。说zookeeper是树,只是一种简称,暴露他的核心本质。

2.1 节点分类

zookeeper中包含以下四种节点:

  1. PERSISTENT:持久化节点
  2. PERSISTENT_SEQUENTIAL:持久化排序节点
  3. EPHEMERAL:临时节点
  4. EPHEMERAL_SEQUENTIAL:临时排序节点

永久节点:一旦创建就会被持久化,只有主动调用删除节点的方法才会被删除
临时节点:在服务断连或者连接超时等情况下,会是去连接,节点会被自动删除。且临时节点不能存在子节点
排序节点:创建的节点名称末尾会自动添加序号,node-1,node-2

2.2节点构成

一个节点由以下三部分组成:

  1. stat:存放节点的状态类信息,版本和权限等信息
  2. data:节点数据
  3. children:子节点

2.3节点操作

  1. 创建节点
  2. 读节点数据
  3. 更新节点数据
  4. 删除节点
  5. 监控节点变化

3.集群

为什么需要有集群呢?
如果zk只存在于一台机器,当这台机器出现问题时,存储的数据就会有丢失的风险,而且也会直接影响服务。所以一般zk都是一个集群,由多台机器组成。

Zookeeper 是由多个 server 组成的集群,一个 leader,多个 follower。leader 为客户端服务器提供读写服务,除了leader外其他的机器只能提供读服务。

3.1 服务器承担的三种角色

  • leader:一个zk集群只有一个leader,负责维护follower和observe之间的心跳,所有写操作必须通过leader实现
  • follower:一个zk集群会存在多个follower,它可以直接相应用户的读请求,并将写请求转发给leader,负责上报心跳与投票。
  • observer:和follower类似,但是没有投票权。

3.2 ZAB协议

ZAB:Zookeeper Atomic Broadcast

为了保证写操作的一致性与可用性,ZooKeeper专门设计了一种名为原子广播(ZAB)的支持崩溃恢复的一致性协议。基于该协议,ZooKeeper实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。

一旦Leader节点无法工作,ZAB协议能够自动从Follower节点中重新选出一个合适的替代者,即新的Leader,该过程即为领导选举。该领导选举过程,是ZAB协议中最为重要和复杂的过程。

数据读写
写:

  1. 用户发起写入数据请求,只有leader才可以处理这种请求,此时如果follower接受到此请求,会把他转发给leader。
  2. leader完成请求后将请求结果同步给follower
  3. follower处理完数据更新后返回ACK码给leader
  4. leader收到超过半数的ACK,认为此次更新数据成功
  5. leader向follower和observer发出请求,同步数据
  6. leader将处理结果返回给客户端

读:
follower和observer可直接处理读请求,他们数量越多,可处理的请求量的越大,读性能也更好

3.3选举-FastLeaderElection

首先需要理解几个概念:

  1. myid:每个zk服务器都有一个myid的文件,存放着zk集群的唯一id,这个id可以作为该服务器在集群的唯一标识符
  2. zxid:事务id,单调递增,用于标识一次更新操作,用一个64位的整数标识,高32位代表当前leader的序号,每选举出一个新的leader,序号+1,低32位代表在当前leader下操作序号
  3. 服务器状态:
    • LOOKING:不确定Leader是谁,需要选举
    • FOLLOWING: 跟随者状态,我是follower,知道谁是leader
    • LEADING :领导者状态,我是leader,
    • OBSERVING :观察者状态,我是observer
  4. 数据结构:
    • logicClock:自增整数,表明该服务器发起的第几轮投票
    • state:服务器状态
    • self_id:当前服务器的myid
    • self_zxid:当前服务器最大的zxid
    • vote_id:被推举的服务器myid
    • vote_zxid:被推举的服务器所保存最大zxid

1.集群初始化
在集群刚启动时,所有服务器的logicClock为1,zxid为0,此时每个服务器都把leader票投给自己。
我们用这样的表达式来表示各服务器间的通信记录,x-y-z:服务器名-logicClock-self_zxid

2.投票给自己
假设有三台服务器:A,B,C,初始化结束后,三条服务器的信息分别为:A-1-0,B-1-0,C-1-0,然后开始投票,他们会向其余两台机器发送消息,同步自己将票投给自己,此时自己的票箱只有(A-1-0)。

3.更新选票
服务器单点投票完成后,需要把这个结果同步给其他设备,于是A将(A-1-0)的消息分别发给B和C,A收到B和C发来的消息(B-1-0)(C-1-0),并将这两个个消息都存入票箱,此时票箱有(A-1-0)(B-1-0)(C-1-0)
A开始对比选票的logicClock和zxid,发现他们都相等,根据myid选择最大的服务器C,将自己的投票结果更新为C,票箱中更新为(C-1-0),然后再将自己的投票结果发给BC。
B的操作和A一致,而C发现自己的myid最大,无需更新选票,还是选自己当leader。此时三个服务器达成一致,都投票给C。

4.根据选票确定角色
确定C为leader后,C进入leader状态,A和B进入follower状态,之后leader发起并维护与follower间的心跳。投票结束

这个时候新加入了一个follower或者原本的follower服务器重启了

5.follower重启投票给自己
follower又把票投给了自己,然后广播给其他服务器。

6.找到leader
此时leader收到后把自己的leader状态和选票发给它,别的follower也将自己的状态和选票发给它,它合计完发现已经有leader了,并且得到了超过一半人的认可,自己进入FOLLOWING状态。

这个时候leader宕机了

7.follower重新选举
leaderC宕机后,followerAB发现leader不工作了,因此进入LOOKING状态并发起新的一轮投票,并且都将票投给自己。
此时有两种情况:
1)如果A和B的zxid一致,表示C宕机前所有服务器数据一致,选myid大的为leader
2)不一致,选zxid较大的为leader

4.会话

会话(session)是指,从客户端到zk服务器的会话过程。
客户端访问zk服务器时,首先会建立一个tcp长连接,会话的生命周期也由此开始了。通过这个连接,客户端可以通过心跳与服务器保持有效的会话,向zk发送请求并接受相应,还可以接受zk服务器的watch事件。

4.1 会话属性

一个session包含以下四个属性:

  1. session-id:唯一标识一个session,每次客户端创建一个session时,zk服务器会为他分配一个全局唯一的id
  2. timeout:会话超时时间,客户端向服务器传超时时间后,服务器会根据自己的超时时间限制来确定最终的超时时间
  3. ticktime:下次会话超时时间,一般等于当前时间+超时时间,用来标识一个会话何时到期
  4. isClosing:标识会话是否结束,如果超时服务器会标记关闭,不会处理该会话的请求

4.2 会话管理

zk通过sessionTracker来管理会话。
采用分桶策略,将类似的会话放在同一区块中进行统一管理,方便zk针对不同类型的会话进行隔离处理,对同一类型的区块进行统一处理。

5.分布式锁

什么场景下需要分布式锁呢?
两个客户端需要同时对一个节点进行互斥操作,这个操作一般是更新数据节点类的写操作。A和B客户端同时访问一个zk节点,并将zk节点存储的数据+1,如果没有加锁,可能会有这样的场景发现:A和B同时读到数据为1,A写回数据2,B也写回数据2,本来经过两次更新数据应该是3,而现在变成了2,发送错误。
那么加锁之后呢?A先来读到这个节点,发现没有人加锁,他上个锁,执行自己的操作,这个时候B来了,发现这个节点被A上锁了,他只能等待A释放锁之后,再对数据进行更新。

如何用zk实现分布式锁
利用ck实现分布式锁,主要用到了上文中提到的一种特殊节点:
临时排序节点,创建的节点名称末尾会自动添加序号,node-1,node-2

  1. A和B同时访问一个锁节点,这个锁节点其实就是zk中的一个节点,一般为持久化节点,而他的子节点必须是临时排序节点
  2. A访问锁节点时,在锁节点下添加一个子节点Anode1,然后获取锁节点的子节点列表,此时他发现列表为[Anode1],第一位就是他自己,此时A拿到了这个节点的锁,这个过程也称之为加锁。
  3. 此时B也访问锁节点,他也加入一个子节点Bnode2,此时子节点列表为[Anode1,Bnode2],他发现第一位不是自己,此时锁在A手里,于是监听Anode1节点,看A何时释放锁,一旦释放他就获取到了锁,执行自己的操作
  4. A完成的数据更新,删掉了Anode1节点,这个过程称为释放锁
  5. B监听到Anode1节点的变化,访问子节点列表获取到了锁,开始执行自己的操作

参考文档:
实例详解ZooKeeper ZAB协议、分布式锁与领导选举
如果有人问你ZooKeeper是什么,就把这篇文章发给他
七张图彻底讲清楚ZooKeeper分布式锁的实现原理

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值