好久没更新博客,主要是前段时间看了一点机器学习的东西,公式不好打字,后来准备面试又看了很多java的基础,过几天会把java的基础整理一下丢在博客上。
今天主题是zookeeper,bigdata课上老师讲了zookeeper的内容,再加上自己也试了一下,就放在这里整理了。整理只为了自己看,离精通或者熟悉还差着一万八千里呢!
Zookeeper简介
用途
zk是一个高扩展和高可用的服务,主要用来支持
- Distributed configuration
- Consensus
- Group Membership
- Leader Election
- Naming
- Coordination
基础架构
Zookeeper分布在不同的服务器上,每个节点是一个server,其中有一个会作为leader,如图:
具体来说,zk的架构有如下特点:
- 所有的server都有一份数据、logs、硬盘snipshots的拷贝,被存在内存数据库中(它并不存储真正的数据,所以上面空间通常只有几十M,仅用来存必要的一些配置等)
- 在启动时会选择一个作为leader
- client可以选择任何一个server链接
- 当大多数(超过一半)server完成了一个改变,那这个修改就被认为成功,并返回update response(可能造成读取旧数据)
ZK的数据模型
zk的service到底是什么呢?其实就是一个被全部server追踪的分布式文件系统(这个角度来看有点像hdfs),它也是层次命名空间(类似linux),上面的每个节点称为一个znode。每一个znode都存储着一定的数据,并且可能有子的znode。
Znode的主要特点:
- 基于Key对象来实现/维护分布式一致性信息
- 通过数据改变的版本信息、ACL改变和timestamp来维护一致性
- 每次改变版本号都会增加
- Znode不是用来存数据的,只是用来存储一些configuration和meta-data
- 每个znode可以存timestamp和version信息
Znode的类型:
- Persist vs. Ephemeral
- persist节点:一旦被创建不会轻易丢失,即使数据库重启也依然在,每个persist可以包含数据,也可以包含子节点
- ephemeral节点:在session结束或过期后自动删除。服务器的重启也会导致ephemeral接触(可以用于做分布式锁)
- Sequence vs. Non-sequence
- sequence节点:创建出的节点名在指定名称后带有10位10进制数的序号。多克客户端创建同一名称的节点时,都能创建成功,只是序号不同
- non-sequence节点:多客户端在同时创建同一non-sequence节点时,只有一个可创建成功,其他失败。创建出的节点与指定名称完全相同
Watch机制:
- 主动推送:当watch被触发时,zk服务器辉主动将更新推送给client,而不是靠client的轮询(观察者模式)
- 一次性触发:watch只会被触发一次,后续更新通知必须重新注册一个watch
Session的特点:
- session是每个client到server的连接,zk支持每个client在保存同个session的基础上切换到不同的server
- 内建超时机制
- 执行顺序的保证是基于同个session的(即会话一致性)
Zookeeper的一致性
数据一致性:
一致性是指多副本中数据的一致性,以下是个简单整理(程度由强到弱):
- 强一致性:有原子一致性、线性一致性,有两个要求:
- 任何一次读都能读到某个数据最近一次写到数据
- 系统中所有进程,看到的操作顺序都和全局时钟下看到的顺序一致
- 顺序一致性:也有两个要求:
- 任何一次读都能读到最近一次写的数据
- 系统的所有进程顺序一致,但不一定和全局时钟下看到的数据一致
- 弱一致性:指数据更新后,有些操作可能访问不到,最终一致性是一种弱一致性,是指任意时刻节点上同一份数据不一定相同,但一段时间后,数据总会达到一致。里面有根据访问分为:
- 因果一致性:如果A通知B它更新了一个数据(A,B有因果关系),那么B后续访问只能看到更新过的值
- 读写一致性:当进程A自己更新一个值后,它自己只能访问到更新过的值(会话一致性同理)
- 单调一致性:当进程看到数据对象的某个值,它就不会再访问到那个值之前的值
- 补充一点:paxos是共识(consensus)机制,而不是一致性协议
ZK保证的一致性原则
- FIFO一致性:对于同一个客户端,执行一个请求(get、delete这类操作)应该按照他们被发送的顺序;保证该客户端对于notification和状态改变的顺序一致
- 顺序一致性:所以客户端看到的并行写入操作的结果的顺序是一样的,ZAB实现,有点类似paxos
- 原子性:update操作要不成功要不失败,不会有中间结果
- 单系统镜像:同一个client不论连接那个server,看到的数据都应该是完全一致的。对于不同的client则可能看到不同的(因为有延迟)的数据
- 持续性(单调一致性):当一个更新完成后,他会一直保持直到它被再次更新
- 高可用性:2F+1个服务器可以最多容忍F台服务器崩溃
- 最终一致性:数据在一段时间后最终辉达成一致
ZAB(zookeeper atomic broadcat)协议:
目标
可靠投递:如果一个事务被提交到一个服务器,那它最终会被提交到所有服务器
全局有序:如果一台服务器上事务A在事务B之前提交,那么在所有服务器上事务A一定都在B之前提交
因果有序:如果事务A在事务B之前发生(A,B有因果关系),如果一起被提交,一定是A在B之前执行
协议内容——广播
为了保证一致性,所有写操作都要经过leader完成,由leader进行广播。广播是个简化的二阶段提交过程:
- leader收到消息请求后,给消息赋予一个全局唯一的64位自增id,叫zxid(类似于rdbms的事务id),通过zxid比较可以实现因果有序
- leader通过fifo队列将带有zxid的proposal分发给所有follwer
- 当follwer收到proposal,先存下来,然后返回leader一个ack
- 当leader收到过半ack后,leader会向所有follwer发送commit命令,同时在本地执行该请求
- follwer收到commit命令后,执行该请求
值得一提的是,写请求是通过leader广播完成的,但读请求leader或者follwer都可以直接处理,只要从本地内存中读取数据返回client即可
协议内容——恢复
已经proposal的命令应该被备份。例如一些服务器在收到commit之前leader就挂掉了,这个时候就无法执行该请求。当它重启后应该重新执行(client如果挂掉也需要和server重新同步一下)
领导选举
基于TCP的FastLeaderElection
每个zookeeper的服务器都需要在数据文件夹下创建一个名为myid的文件(里面只有一个整数),用来唯一标识该服务器。而在配置文件必须与其一致,选票包括:
- logicClock:表示服务器发起投票轮数,自增整数
- state:当前服务器状态
- self_id:当前服务器myid
- self_zxid:当前服务器最大zxid
- vote_id:推举的服务器myid
- vote_zxid:被推举服务器上最大zxid
具体投票流程为:
- 初始化选票:清空票箱(票箱中记录了每个服务器最后一次投票)
- 发送初始化选票:每个服务器都通过广播票投给自己
- 接受外部投票:尝试从其他服务器获得选票,并计入自己票箱。
- 判断选举轮数(logicClock):如果外部logicClock大于自己,则说明自己选举落后于其他服务器,立即清空票箱并将自己logicClock更新为收到的logicClock。再对比自己之前的投票和收到投票logicClock,确认是否需要更新;如果外部logicClock小于自己,则忽略这次投票;相等则进行下一步选票PK
- 选票PK:是基于myid和zxid的比较,即先选zxid大的,如果zxid相同再选myid比较大的
- 统计投票:如果半数服务器认可了自己的投票,则终止,否则继续
- 更新服务器状态:每个服务器根据投票结果更新服务器状态为leading或following
应用
分布式一致性锁
利用名称唯一性,枷锁操作时,只需要所有客户端创建/test/lock节点,只有一个创建成功,即可以获得锁。而解锁时,只要删除/test/lock节点,其他客户端通过watch机制可以继续进入竞争创建节点。如下图:
并发的惊群效应
以上的实现非常简单,但会产生惊群效应,即当锁释放时,所以客户端都会被唤醒,但只有一个客户端可以获得锁。这个可以通过改良分布式锁实现的方式解决。
让所有客户端都在/lock下创建临时顺序节点,如果创建客户端自身节点编号是/lock下最小的节点,则获得锁。其他节点只监视比自己小的的最大节点(如创建节点1、2、5,1获得锁,2只监视1,5只监视2),只有当自己监视的节点释放锁自己才可以获得锁。这种其实是一种按照创建顺序排队的实现。