zookeeper 技术内幕

《从paxos到zk》大致概述

1.系统模型

树:不使用文件等,而使用znode的数据节点概念,一个数据节点叫一个znode,root路径为/

事务ID:在zk里,事务的含义为能够改变服务器状态的操作,比如节点的create/delete/set/客户端会话创建与失效等。每个事务都会分配一个全局的zxid

节点类型:persist、ephemeral(_sequential)

节点状态:通过get方法得到节点stat

版本:通过version来控制版本实现乐观锁,version代表的是这个节点被更改了几次

watcher:包括客户端线程、客户端watchmanager和zk服务器三部分。client向服务器注册watcher的时候会存在WatchManager里,服务器触发watcher,向client发送通知,取出manager里相应的回调函数进行操作

watcher事件:KeeperState(SyncConnect、DissConnected、Expired等)和EventType(None成功建立会话/NodeCreate/Delete/DataChanged/ChildChanged、None、None等)

Watcher Process:回调方法

Watcher:

 大体分三个步骤:客户端注册Watcher、服务器端处理Watcher、客户端回调

1.客户端注册Watcher

构造zookeeper的时候,传入了一个watcher对象(存在客户端的ZKWatchManager中并作为默认Watcher),对于getData / getChildren / exist三个接口也可以向服务器注册Watcher(ZKWatchManager有三个相应的集合保存注册成功的Watchers)

WatchRegistration暂时保存这些尚未注册成功的Watcher,Pocket用来包装Watcher(其中保存的是)放入client发送队列等待发送给server(序列化时并未将watcher进行序列化)

Request发送请求等待server的返回ack,ack后SendThread用来readResponse,Watcher放入ZKWatchManager(最终放入一个Map<path,Set<Watcher>>里)

2.服务器端处理Watcher

watcher注册

processRequest在得到一个getWatch返回true的时候会将ServerCnxn(client与server之间的连接接口),最终被存储在WatchManager的WatchTable(数据节点的路径托管)和watch2Paths(从Watcher的粒度来控制)中

watcher触发

通过传入path(以setData为例)调用triggerWatch,在其中对dataWatches和childWatches进行通过调用process触发watches

Watcher触发逻辑是:封装WatchedEvent、查Watcher(并删除)、通过process方法触发Watcheres(不处理真正的逻辑而是借助ServerCnxn实现WatchedEvent传递倒客户端)

3.客户端回调

通过ServerCnxn对应的TCP发送的WatchedEvent,通过readResponse来处理

对于一个通知类型的响应,步骤如下:反序列化WatcherEvent并得到相对节点的相对路径,WatcherEvent转化成WatchedEvent,交给eventThread进行回调

得到一个event,通过EventType path state从dataWatches、existWatches、childWatches中取出Watcher放入Set里(并删除原),这个Set放入队列里,会不停的通过run进行处理

ACL

 ACL权限控制:访问控制列表,针对用户和组进行细粒度权限控制

zk的权限可以从:权限模式、授权对象和权限来进行理解,通常使用scheme:id:permission

Scheme

四种模式:IP模式(通过例如 " ip:192.168.0.* " 进行控制)、Digest模式(一个类似username:pw的String,最终通过加密会变成一个字符串,例如foo:zk-book会变成foo:asdfadsf的加密后的一串)、World模式(对所有用户开放)和Super模式(superuser设立)

ID

不同模式对应的ID不同:IP对应的是具体IP或段,Digest对应的是username:BASE64(SHA-1(username:pw)),World仅一个可用的anyone代表大家都可以来,Super同Digest

Permission

C(create)D(delete)R(read)W(write)A(admin节点权限管理权限)

 由于默认的权限控制有时过于简单,因此zk提供了AuthenticationProvider接口进行自定义权限控制

2.客户端

初始化(初始化zkObj 设置默认Watcher 初始化服务端ip地址列表 初始化ClientCnxn{消息发送队列&响应队列}==>创建SendThread+EventThread)

会话创建(SendThread和EventThread启动 随机获取一个服务器地址 连TCP构建ConnectRequest 发送请求)

响应(得到response反序列化得到ConnectResponseObj 通知SendThread并更新客户端状态 sendThread会生成一个事件传给EventThread 传入waitingEvents中等待被process触发)

服务器地址(zk构造方法,传入的ip地址是一个string{ip1,ip2,ip3...,ipn},环形队列)

ClientCnxn(packet进行封装作为服务端响应的载体{仅对requestHeader request readOnly序列化};发送请求时送入一个发送队列,发送完后送入等待响应队列;服务端收到后序列化成WatchedEvent放入待处理队列)

SendThread(周期性的Ping服务端进行心跳,若TCP连接断开,自动且透明的完成重连)

EventThread(不断的从watingEvents中取Obj,并调用process对这个响应的Watcher进行触发回调)

会话

状态:connecting(ip列表逐个进行连接——>) connected(网络波动,client会进行重连——>) reconnecting(连上——>) reconnected(会话超时 / 权限 / client退出——>) closed

创建:创建一个Session,用SessionID来唯一标识一个会话,因此SessionID需要全局唯一(SessionTracker分配的),SessionID高八位代表所在机器,后56位代表位运算后的时间戳;同时SessionTracker作为服务器端的Session管理器,通过SessionById SessionSet SessionWithTimeout(全是HashMap意味着一个Session保存了三份)

管理:分桶策略指的是在管理会话的时候把创建Session的下一次超时时间作为策略{下一段时间内可能超时的Session放同一区块};如何保证Cli与Server的连接有效?通过cli的Ping(心跳),Server收到后会重置超时时间并迁移Session到新区块,总的说就是Cli定时Ping,Server收到就(在server里)重新激活一次Cli;由于激活的会话都被迁移了,所以在这个到时间点的区块里,都是超时的Session,移除

Tracker清理超时Session:会话状态Closed以便Server在关闭期间不会再处理cli发来的请求了,向Cli发送关闭,根据SessionID去内存数据里看看有哪些临时节点要删得到一个List,删节点的事情要放入事务队列,删临时点,移除会话,关对应的NIOServerCnxn

重连:SESSION_EXPIRE情况{超时了,服务器都删了,重新实例化zk对象恢复临时数据把},SESSION_MOVED{会话从Client1短暂断开后,在Client2重连上了},CONNECTION_LOSS{客户端会自动尝试重连} 

Leader选举

启动时选举:当有多个Server的时候,开始发起投票,大家都会投给自己,例如两台机子,会得到(1,0)和(2,0),1号机收到2号的投票后,发现2>1,会把自己变成(2,0),过半投2,2为Leader,剩下的Follower

运行时选举:Leader崩了,非Observer变为Looking状态,每个Server都会发出一个投票(第一次投自己),接收投票后会拿zxid最大的做leader,统计、变更、改状态

算法简介:先找zxid大的,然后相同的情况下,选sid大的(相同轮次情况下)

服务器角色

leader保证事务处理的顺序性,集群服务器的调度者

follower处理非事务请求并转发事务给Leader,并参与Proposal和leader选举的投票

observer观察集群的状态变化并同步过来,与Follower的区别在于不参与任何投票,仅提供非事务功能

服务器启动与请求处理

单机启动

集群启动

会话创建请求

setData请求

事务请求

GetData请求

数据存储

数据内存:DataTree不包含任何业务逻辑并通过一个ConcurrentHashmap存储了服务器上的所有数据节点<path,node>、DataNode为最小数据单元保存数据内容ACL列表节点状态等、ZKDatabase管理zk的会话、datatree、事务日志

事务日志:日志文件存在dataLogDir/version-x/下,文件后缀为第一行的zxid,便于查找。日志有一个预分配的操作,以免产生系统为新数据开辟磁盘块的操作。

快照:某时刻全量内存数据写入磁盘,同样也用zxid作为文件后缀(快照开始时的服务器最新zxid)。每次事务执行后都会通过过半随机的策略决定是否快照,然后创建异步线程生成快照数据文件名并序列化。

初始化:

数据同步:差异化同步(leader向learner发送DIFF指令,leader会把proposal同步给他{内容+commit两个数据包}),先回滚再差异化同步(Leader再proposal的时候挂了,于是zxid的epoch++,但重启后zxid仍为原来的,也就是leader发现某个learner包含了一条自己没有的事务记录,需要先回滚到leader最接近的一条),仅回滚(peer>max,回滚差异化的简单模式,仅回滚操作),全量同步(leader的全部内存数据同步给learner)

原文地址:https://www.cnblogs.com/tillnight1996/p/12980892.html