此文章主要借鉴:《Zookeeper分布式过程协同技术详解》,真的想吐槽,翻译得真不咋地,但自己又没时间和精力去看原版,讲究着看吧。

Zookeeper·服务器会在本地处理只读请求,如果一个客户端向服务端只是请求数据,那么Zookeeper服务器将直接在本地处理请求,所以Zookeeper在处理只读请求为主要负载的时候性能会非常高。
如果客户端的请求会改变Zookeeper的状态例如:create,delete,setData将会被转发到leader,leader执行相应的请求,并形成状态的更新,也叫事务。其中,事务包含了对应请求处理而改变Zookeeper状态所需要执行的步骤。一个例子:客户端提交了一个对Z节点的setData请求,setData将会改变该znode节点数据信息,并会增加该节点的版本号,因此,对于这个请求来说,事务包括了两个重要字段:节点中新的数据和该节点的新的版本号。处理该事务,必须一致。

所有变更都必须以原子方式执行。以setData来说,变更节点数据信息,并不改变版本号将会导致错误的产生,因此Zookeeper集群以事务方式运行。
Zookeeper在事务管理上也经历了两个阶段,之前是每个服务器中启动一个单独的线程来处理事务,通过单独的线程来保障事务之间的顺序执行互不干扰,之后Zookeeper增加了多线程机制
同一个事务具有幂等性,对同一个事务执行两次结果还是一样。

当leader产生了事务,就会为事务分配一个标识符,也叫会话ID(zxid),通过Zxid对事务进行标识,就可以按照leader所指定的顺序在各个服务器中按序执行。
服务器之间选举leader的时候,也会交换zxid信息,这样就可以知道哪个无故障服务器接受了更多的事务。

zxid是一个long整数,两部分构成:时间戳 计数器组成。每个部分32位。

设置leader的主要目的是为了对客户端发起的Zookeeper状态变更请求进行排序,leader为每一个请求转换为一个事务,这些事务发送给follower,确保集群按照leader确定的顺序接受和处理这些事务。

每个服务器启动后进入Looking状态,开始选举一个新的群首或查找已经存在的群首,如果leader已经存在,那这个刚启动的服务器就会与leader建立连接,确保自己与leader一致。

如果所有服务器都处于Looking状态,这些服务器之间就会进行通信选举一个leader,每一个服务器都会广播自己投给自己的投票信息,这样每一台服务器都会收到投票信息,然后根据收到的投票信息,决定自己的投票信息要不要改,如果别人的最大的zxid比自己的zxid大,或者别人最大的zxid和自己的zxid一样但是sid比自己的大 这两种情况都会让收到投票的服务器修改自己的投票为比自己zxid大的或者zxid相同sid比自己大的,重新把投票发出去,最后所有服务器都会收到一样的投票,就选出了leader了。
简而言之,只有状态最新的服务器将会赢得选举。

Zab状态更新的广播协议,我把它理解为同步协议:
在收到了一个写请求操作之后,追随者会将请求发给群首,leader将探索性的执行该请求,并将执行的结果以事务的方式对状态更新进行广播。一个事务中包含服务器需要执行变更的确切操作,当事务提交时,服务器就会将这些变更反馈到数据树上,数据树是Zookeeper用于保存状态信息的数据结构。

我们需要面对的问题是服务器是如何确定一个事务已经提交了?所以引入了zab,通过这个协议提交事务分为两个阶段:
1、leader向所有follower发送一个proposal消息
2、当一个追随者接收到消息p后,会响应群首一个ACK消息,通知leader已经接受该proposal。
3、当收到仲裁数量的服务器发送的确认消息后,群兽就会发送消息通知follower进行提交操作。

Zab提供了两个保障:
1、如果leader顺序广播了事务a,b 那么每个服务器在一定先提交a,再提交事务b也就是 每个服务器执行事务的顺序与leader提交的顺序是保持一致的。
2、如果某个服务器按照a,b提交事务,那么其他服务器顺序必然一样。

zab在仲裁数量服务器中记录的事务,集群中仲裁数量的服务器需要在leader提交事务前对事务达成一致。
(1、之前在这一点上有一些疑问:如果非仲裁成员的服务器未得到及时更新,这个时候来之客户端的只读请求到了非最新的服务器读数据不是发生的数据滞后吗?如何解决的?)
(2、如果leader向follower提议得到follower的accept之后,follower这个时候down机,并没有得到commit,恢复的时候用户去读数据是不是会读到未更新的数据?
这种情况又要分两种,1follower是真的down机,那再恢复的时候就会处于follower状态,会找到leader并与leader同步信息,用户读数据肯定是最新的。而且退一万步就算恢复后连接不到主机,我想follower也应该会自动提交之前为提交的事务,怎么做到的呢?在仲裁模式下,收到的提案消息是被该follower记录下了,这样可以确保所有的服务器最终提交已经accept的事务)

一个leader还会确保在提交完所有之前的时间戳内需要提交的事务之后,才会开始新的广播事务。
一个时间戳的最初状态必须包含所有的之前已经提交的事务,或者某些已经被accept但是尚未提交完成的事务。这一点非常重要,在leader进行时间戳e的任何新的提案前,必须保证时间时间戳e-1到e内所有的提案被提交。
为什么必须要这一点保证的,想一下:如果leader给follower提议并被accept后突然down掉,这个时候重新选举新leader,新的leader选举了之后会有新的时间戳,这个时间戳包含了所有之前已经提交的事务和某些已经被accept但是尚未提交完成的事务的信息,那这个时候新的leader只需要把某些已经被accept但是尚未提交完成的事务再次提交即可恢复正常了。

Zookeeper观察者
观察者:在不影响写入性能的情况下缩放Zookeeper
虽然客户端直接连接到投票选举的Zookeeper成员执行良好,但这个架构很难扩展到大量的客户端。问题就是因为我么添加了更多的投票成员,写入性能下降。这是由于这样的事实:一个写入操作要求共识协议至少是整体的一半,因此投票的成本随着投票者越多会显著增加。
我们引入了一个新的Zookeeper节点类型叫做Observer,它帮助处理这个问题并进一步完善了Zookeeper的可扩展性。观察者不参与投票,它只监听投票的结果,不是导致了他们的共识协议。除了这个简单的区别,观察者精确的和追随者一样运行 - 客户端可能链接他们并发送读取和写入请求。观察者像追随者一样转发这些请求到领导者,而他们只是简单的等待监听投票的结果。正因为如此,我们可以尽可能多的增加观察者的数量,而不影响投票的性能。
观察这还有其他优势。因为他们不投票,他们不是Zookeeper整体的主要组件。因此他们可以故障,或者从集群断开连接,而不影响Zookeeper服务的可用性。对用户的好处是观察者可以连接到比追随者更不可靠的网络。事实上,观察者可以用于从其他数据中心和Zookeeper服务通信。观察者的客户端会看到快速的读取,因为所有的读取都在本地,并且写入导致最小的网络开销,因为投票协议所需的消息数量更小。
所以从上面得知,观察者实际上提高了读效率,并且不会随观察者数量的增多产生太大的额外开销。因为他不是参与投票。
一是通过减少投票机器数量,从而提高性能。

此外,也可以从降低每一台zk服务器的负载压力角度来看,能够降低负载压力。
因为观察者如果宕机,影响也不大,因为不是核心人员。
但是注意,引入观察者,从性能角度来看,是好东西。但是如果从集群高可用的角度来看,要 慎用。(5台机器,2个观察者,1个follow挂掉,此时不满足过半统一机制,选不出leader,集群无法启动)
此外再注意,比如3台机器,把两台配置成观察者,这么做是不行的,因为不满足过半机制, 所以选不出leader,导致集群启动不了。