Chapter 5 故障处理

故障发生的主要点有三个:node

  • ZooKeeper服务
  • 网络
  • 应用程序

如图5-1所展现的简单结构数据库

三个服务器组成了ZooKeeper的服务。进程会随机链接到其中一个服务器,也可能断开后再次链接到另外一个不一样的服务器,服务器使用内部协议来保持客户端之间状态的同步,对客户端呈现一致性视图。
clipboard.png

图5-2展现了系统的不一样组件中可能发生的一些故障
clipboard.png服务器

1、可恢复的故障

一个客户端从ZooKeeper得到响应时,客户端能够很是确定这个响应信息与其余响应信息或其余客户端所接收的响应均保持一致性。

有时,ZooKeeper客户端库与ZooKeeper服务的链接会丢失,并且没法提供一致性保障的信息,当客户端库发现本身处于这种状况时,就会使用Disconnected事件和ConnectionLossException异常来表示本身没法了解当前的系统状态。网络

ZooKeeper客户端库会积极地尝试,使本身离开这种状况,它会不断尝试从新链接另外一个ZooKeeper服务器,直到最终从新创建了会话。一旦会话从新创建,ZooKeeper会产生一个SyncConnected事件,并开始处理请求。多线程

ZooKeeper还会注册以前已经注册过的监视点,并会对失去链接这段时间发生的变动产生监视点事件。并发

Disconnected事件和ConnectionLossException异常的产生的一个典型缘由是由于ZooKeeper服务器故障。
图5-3展现了这种故障的一个示例异步

客户端链接到服务器s2 ,其中s2 是两个活动ZooKeeper服务器中的一个,当s2 发生故障,客户端的Watcher对象就会收到Disconnected事件,而且,全部进行中的请求都会返回ConnectionLossException异常。

clipboard.png

若是此时客户端正在进行某些请求,好比刚刚提交了一个create操做的请求,当链接丢失发生时,对于同步请求,客户端会获得ConnectionLossException异常,对于异步请求,会获得CONNECTIONLOSS返回码。然而,客户端没法经过这些异常或返回码来判断请求是否已经被处理。

一个很是糟糕的方法是简单处理,当接收到ConnectionLossException异常或CONNECTIONLOSS返回码时,客户端中止全部工做,并从新启动,虽然这样可使代码更加简单,可是,本多是一个小影响,却变为重要的系统事件。分布式

当一个进程失去链接后就没法收到ZooKeeper的更新通知,尽管这听起来很没什么,可是一个进程也许会在会话丢失时错过了某些重要的状态变化。spa

图5-4展现了这种状况的例子线程

客户端c1做为群首,在t2 时刻失去了链接,可是并没发现这个状况,直到t4 时刻才声明为终止状态,同时,会话在t2 时刻过时,在t3 时刻另外一个进程成为群首,从t 2 到t 4 时刻旧的群首并不知道它本身被声明为终止状态,而另外一个群首已经接管控制。
clipboard.png

若是开发者不仔细处理,旧的群首会继续担当群首,而且其操做可能与新的群首相冲突。所以,当一个进程接收到Disconnected事件时,在从新链接以前,进程须要挂起群首的操做。正常状况下,从新链接会很快发生,若是客户端失去链接持续了一段时间,进程也许会选择关闭会话,固然,若是客户端失去链接,关闭会话也不会使ZooKeeper更快地关闭会话,ZooKeeper服务依然会等待会话过时时间过去之后才声明会话已过时。

注意:很长的延时与过时

已存在的监视点与Disconnected事件

为了使链接断开与重现创建会话之间更加平滑,ZooKeeper客户端库会在新的服务器上从新创建全部已经存在的监视点。当客户端链接ZooKeeper的服务器,客户端会发送监视点列表和最后已知的zxid(最终状态的时间戳),服务器会接受这些监视点并检查znode节点的修改时间戳与这些监视点是否对应,若是任何已经监视的znode节点的修改时间戳晚于最后已知的zxid,服务器就会触发这个监视点。

每一个ZooKeeper操做都彻底符合该逻辑,除了exists。exists操做与其余操做不一样,由于这个操做能够在一个不存在的节点上设置监视点,若是仔细看前一段中所说的注册监视点逻辑,会发现存在一种错过监视点事件的特殊状况。

图5-5说明了这种特殊状况

clipboard.png

致使错过了一个设置了监视点的znode节点的建立事件,客户端监视/event节点的建立事件,然而就在/event被另外一个客户端建立时,设置了监视点的客户端与ZooKeeper间失去链接,在这段时间,其余客户端删除了/event,所以当设置了监视点的客户端从新与ZooKeeper创建链接并注册监视点,ZooKeeper服务器已经不存在/event节点了,所以,当处理已经注册的监视点并判断/event的监视时,发现没有/event这个节点,因此就只是注册了这个监视点,最终致使客户端错过了/event的建立事件。由于这种特殊状况,须要
尽可能避免监视一个znode节点的建立事件,若是必定要监视建立事件,应尽可能监视存活期更长的znode节点,不然这种特殊状况可能会伤害你。

2、不可恢复的故障

两种状况下,ZooKeeper都会丢弃会话的状态:

  • 会话过时
  • 已认证的会话没法再次与ZooKeeper完成认证

3、群首选举和外部资源

【ZooKeeper没法保护与外部设备的交互操做】
当运行客户端进程的主机发生过载,就会开始发生交换、系统颠簸或因已经超负荷的主机资源的竞争而致使的进程延迟,这些都会影响与ZooKeeper交互的及时性:

  • 一方面,ZooKeeper没法及时地与ZooKeeper服务器发送心跳信息,致使ZooKeeper的会话超时。
  • 另外一方面,主机上本地线程的调度会致使不可预知的调度:一个应用线程认为会话仍然处于活动状态,并持有主节点,即便ZooKeeper线程有机会运行时才会通知会话已经超时。

图5-6经过时间轴展现了这个棘手的问题

clipboard.png

在这个例子中,应用程序经过使用ZooKeeper来确保每次只有一个主节点能够独占访问一个外部资源,这是一个很广泛的资源中心化管理的方法,用来确保一致性。在时间轴的开始,客户端c1 为主节点并独占方案外部资源。
事件发生顺序以下:
一、在 t1 时刻,由于超载致使与ZooKeeper的通讯中止,c1 没有响应,c1 已经排队等候对外部资源的更新,可是还没收到CPU时钟周期来发送这些更新。
二、在t2 时刻,ZooKeeper声明了c1 与ZooKeeper的会话已经终止,同时删除了全部与c1 会话关联的临时节点,包括用于成为主节点而建立的临时性节点。
三、在t3 时刻,c2 成为主节点。
四、在t4 时刻,c2 改变了外部资源的状态。
五、在t5 时刻,c1 的负载降低,并发送已队列化的更新到外部资源上。
六、在t6 时刻,c1 与ZooKeeper重现创建链接,发现其会话已通过期且丢掉了管理权。遗憾的是,破坏已经发生,在t5 时刻,已经在外部资源进行了更新,最后致使系统状态损坏。

解决这个问题有几个方法:

  • 一个方法是确保应用不会在超载或时钟偏移的环境中运行,当心监控系统负载能够检测到环境出现问题的可能性,良好设计的多线程应用也能够避免超载,时钟同步程序能够保证系统时钟的同步。
  • 另外一个方法是经过ZooKeeper扩展对外部设备协做的数据,使用一种名为隔离(fencing)的技巧,分布式系统中经常使用这种方法用于确保资源的独占访问。

用一个例子来讲明如何经过隔离符号来实现一个简单的隔离,只有持有最新符号的客户端,才能够访问资源:

在建立表明群首的节点时,能够得到Stat结构的信息,其中该结构中的成员之一,czxid,表示建立该节点时的zxid,zxid为惟一
的单调递增的序列号,所以可使用czxid做为一个隔离的符号。

当对外部资源进行请求时,或在链接外部资源时,还须要提供这个隔离符号,若是外部资源已经接收到更高版本的隔离符号的请求或链接时,咱们的请求或链接就会被拒绝。
也就是说若是一个主节点链接到外部资源开始管理时,若旧的主节点尝试对外币资源进行某些处理,其请求将会失败,这些请求会被隔离开。即便出现系统超载或时钟偏移,隔离技巧依然能够可靠地工做。

图5-7展现了如何经过该技巧解决图5-6的状况。

clipboard.png

一、当c 1 在t 1 时刻成为群首,建立/leader节点的zxid为3(真实环境中,zxid为一个很大的数字),在链接数据库时使用建立的zxid值做为隔离符号。 二、以后,c 1 因超载而没法响应,在t 2 时刻,ZooKeeper声明c 1 终止,c 2 成为新的群首。 三、c 2 使用4所做为隔离符号,由于其建立/leader节点的建立zxid为4。 四、在t 3时刻,c 2 开始使用隔离符号对数据库进行操做请求。 五、在t 4 时刻,c 1 '的请求到达数据库,请求会因传入的隔离符号(3)小于已知的隔离符号(4)而被拒绝,所以避免了系统的破坏。 不过,隔离方案须要修改客户端与资源之间的协议,须要在协议中添加zxid,外部资源也须要持久化保存来跟踪接收到的最新的zxid。
相关文章
相关标签/搜索