客户端与主服务器之间的通信是通过KeepAlive协议来维持的。图4.14中从左到右时间在增加,斜向上的箭头表示一次KeepAlive请求,斜向下的箭头则是主服务器的一次回应。M1、M2和M3表示不同的主服务器租约期。C1、C2和C3则是客户端对主服务器租约期时长做出的一个估计。KeepAlive是周期发送的一种信息,它主要有两方面的功能:延迟租约的有效期和携带事件信息告诉用户更新。主要的事件包括文件内容被修改、子节点的增加、删除和修改、主服务器出错、句柄失效等。正常情况下,通过KeepAlive 协议租约期会得到延长,事件也会及时地通知给用户。但是由于系统有一定的失效概率,引入故障处理措施是很有必要的。通常情况下系统可能会出现两种故障:客户端租约期过期和主服务器故障,对于这两种情况系统有着不同的应对方式。
1、客户端租约过期
刚开始时,客户端向主服务器发出一个KeepAlive请求(图4.14中的1),如果有需要通知的事件时则主服务器会立刻做出回应,否则主服务器并不立刻对这个请求做出回应,而是等到客户端的租约期C1快结束的时候才做出回应(图4.14中的2),并更新主服务器租约期为 M2。客户端在接到这个回应后认为该主服务器仍处于活跃状态,于是将租约期更新为C2并立刻发出新的KeepAlive请求(图4.14中的3)。同样的,主服务器可能不是立刻回应而是等待C2接近结束,但是在这个过程中主服务器出现故障停止使用。在等待了一段时间后C2到期,由于并没有收到主服务器的回应,系统向客户端发出一个危险(Jeopardy)事件,客户端清空并暂时停用自己的缓存,从而进入一个称为宽限期(GracePeriod)的危险状态。这个宽限期默认是45秒。在宽限期内,客户端不会立刻断开其与服务器端的联系,而是不断地做探询。图4.14中新的主服务器很快被重新选出,当它接到客户端的第一个KeepAlive请求(图4.14中的4)时会拒绝(图4.14中的5),因为这个请求的纪元号(Epoch Number)错误。不同主服务器的纪元号不相同,客户端的每次请求都需要这个号来保证处理的请求是针对当前的主服务器。客户端在主服务器拒绝之后会使用新的纪元号来发送KeepAlive请求(图4.14中的6)。新的主服务器接受这个请求并立刻做出回应(图4.14中的7)。如果客户端接收到这个回应的时间仍处于宽限期内,则系统会恢复到安全状态,租约期更新为C3。如果在宽限期未接到主服务器的相关回应,则客户端终止当前的会话。
2、主服务器出错
在客户端和主服务器端进行通信时可能会遇到主服务器故障,图4.14就出现了这种情况。正常情况下旧的主服务器出现故障后系统会很快地选举出新的主服务器,新选举的主服务器在完全运行前需要经历以下九个步骤:
(1)产生一个新的纪元号以便今后客户端通信时使用,这能保证当前的主服务器不必处理针对旧的主服务器的请求。
(2)只处理主服务器位置相关的信息,不处理会话相关的信息。
(3)构建处理会话和锁所需的内部数据结构。
(4)允许客户端发送KeepAlive请求,不处理其他会话相关的信息。
(5)向每个会话发送一个故障事件,促使所有的客户端清空缓存。
(6)等待直到所有的会话都收到故障事件或会话终止。
(7)开始允许执行所有的操作。
(8)如果客户端使用了旧的句柄则需要为其重新构建新的句柄
(9)一定时间段后(1分钟),删除没有被打开过的临时文件夹
如果这一过程在宽限期内顺利完成,则用户不会感觉到任何故障的发生,也就是说新旧主服务器的替换对于用户来说是透明的,用户感觉到的仅仅是一个延迟。使用宽限期的好处正是如此。
在系统实现时,Chubby还使用了一致性客户端缓存(Consistent Client-Side Caching)技术,这样做的目的是减少通信压力,降低通信频率。在客户端保存一个和单元上数据一致的本地缓存,这样需要时客户可以直接从缓存中取出数据而不用再和主服务器通信。当某个文件数据或者元数据需要修改时,主服务器首先将这个修改阻塞;然后通过查询主服务器自身维护的一个缓存表,向所有对修改的数据进行了缓存的客户端发送一个无效标志(Invalidation);客户端收到这个无效标志后会返回一个确认(Acknowledge),主服务器在收到所有的确认后才解除阻塞并完成这次修改。这个过程的执行效率非常高,仅仅需要发送一次无效标志即可,因为主服务器对于没有返回确认的节点就直接认为其是未缓存的。