musicman 2015-04-25
大多数数据分布方式都会有一个瓶颈问题--中心服务器存储维护着元数据,系统中其他的节点通过访问它读取,修改元数据。这样,中心服务器节点的性能就容易成为系统的瓶颈。解决方法也有多种,你可能会搭建一个集群专门存储元数据,并对外进行读写。但这样又要涉及到数据的一致性问题。本文将介绍被广泛应用于各种实际的分布式系统中的最重要的分布式协议--Lease机制。
为了解决上面的性能瓶颈,lease机制设计了一套cache系统,在各个节点上cache元数据信息。
基本原理:中心服务器在向各节点发送数据时同时向节点颁发一个lease.每个lease具有一个有效期,和信用卡上的有效期类似,lease上的有效期通常是一个明确的时间点,例如12:00:10,一旦真实时间超过这个时间点,则lease过期失效。在lease的有效期内,中心服务器保证不会修改对应数据的值。因此,节点收到数据和lease后,将数据加入本地Cache,一旦对应的lease超时,节点将对应的本地cache数据删除。中心服务器在修改数据时,首先阻塞所有新的读请求,并等待之前为该数据发出的所有lease超时过期,然后修改数据的值。
上述机制可以保证各个节点上的cache与中心服务器上的中心始终一致。这是因为中心服务器节点在发送数据的同时授予了节点对应的lease,在lease有效期内,服务器不会修改数据,从而客户端节点可以放心的在lease有效期内cache数据。上述lease机制可以容错的关键是:服务器一旦发出数据及lease,无论客户端是否收到,也无论后续客户端是否宕机,也无论后续网络是否正常, 服务器只要等lease超时,就可以保证对应的客户端节点不会再继续cache数据,从而可以放心的修改数据而不会破坏cache的一致性。
不难发现上面的机制有性能和可用性上的问题,但也容易优化。
优化点一:服务器在修改元数据时首先要阻塞读请求并等待lease超时,这是为了防止发出新的lease从而引起活锁。优化方法很简单:进入修改流程后,我们对读请求只返回数据,不颁发lease。进一步优化,我们可以颁发lease,但有效期设置为已发出到lease的最大有效期。这样,客户端节点可以继续缓存元数据,并且不会造成活锁。不过,实际情况中,第一种优化就足够了。因为等待超时的时间会被优化点二大大减少。
优化点二:服务器在修改元数据时需要等待所有的lease过期超时,从而造成修改元数据的操作时延大大增大。优化的方法是,在等待所有的lease过期的过程中,服务器主动通知各个持有lease的节点放弃lease并清除cache中的数据,如果服务器收到客户端返回的确认放弃lease的消息, 则服务器不需要在等待该lease超时。该过程中,如果因为异常造成服务器通知失败或者客户端节点发送应答消息失败,服务器只需依照原本的流程等待lease超时即可,而不会影响协议的正确性。
优化点三:Lease机制依赖于有效期(常选择的 lease 时长是10秒级别),这就要求颁发者和接收者的时钟是同步的。一方面,如果颁发者的时钟比接收者的时钟慢,则当接收者认为lease已经过期的时候,颁发者依旧认为lease有效。接收者可以用在lease到期前申请新的lease的方式解决这个问题。另一方面,如果颁发者的时钟比接收者的时钟快,则当颁发者认为lease已经过期的时候,接收者依旧认为lease有效,颁发者可能将lease颁发给其他节点,造成承诺失效,影响系统的正确性。对于这种时钟不同步,实践中的通常做法是将颁发者的有效期设置得比接收者的略大,只需大过时钟误差就可以避免对lease的有效性的影响。
上面仅仅是lease机制的一个实例。进一步分析其本质。
由于lease是一种承诺,具体的承诺内容可以非常宽泛,可以是例子中数据的正确性;也可以是某种权限,例如当需要做并发控制时,同一时刻只给某一个节点颁发lease,只有持有lease的节点才可以修改数据;也可以是某种身份,例如在primary-secondary架构中,给节点颁发lease,只有持有lease的节点才具有primary身份。Lease的承诺的内涵还可以非常宽泛。
lease机制具有很高的容错能力。
一:通过引入有效期,Lease机制能否非常好的容错网络异常。Lease颁发过程只依赖于网络可以单向通信,即使接收方无法向颁发者发送消息,也不影响lease的颁发。
二:由于lease的有效期是一个确定的时间点,lease的语义与发送lease的具体时间无关,所以同一个lease可以被颁发者不断重复向接受方发送。即使颁发者偶尔发送 lease 失败,颁发者也可以简单的通过重发的办法解决。
三:Lease机制能较好的容错节点宕机。如果颁发者宕机,则宕机的颁发者通常无法改变之前的承诺,不会影响lease的正确性。在颁发者机恢复后,如果颁发者恢复出了之前的lease信息,颁发者可以继续遵守lease的承诺。如果颁发者无法恢复lease信息,则只需等待一个最大的lease超时时间就可以使得所有的lease都失效,从而不破坏lease机制。
下面在讲一个lease机制的具体应用--确定节点状态
在分布式系统中怎么确定一个节点是否处于正常工作状态是一个比较困难的问题。接触过Hadoop的人可能脑海中会立马想起“心跳”。我们首先要说明的就是“心跳”无法很好的解决这个问题。
在一个primary-secondary架构的系统中,有三个节点 A、B、C互为副本,其中有一个节点为primary,且同一时刻只能有一个primary节点。另有一个节点Q负责判断节点A、B、C的状态,一旦Q发现primary异常,节点Q将选择另一个节点作为primary。假设最开始时节点A为primary,B、C为secondary。节点Q需要判断节点 A、B、C 的状态是否正常。
如果某个时刻因为种种原因,Q与节点A之间的网络暂时中断,节点A与节点B、C之间的网络正常。此时节点Q认为节点A异常,重新选择节点B作为新的primary,并通知节点A、B、C新的primary是节点B。由于节点Q的通知消息到达节点 A、B、C的顺序无法确定,假如先到达B,则在这一时刻,系统中同时存在两个工作中的primary,一个是A、另一个是B。假如此时 A、B都接收外部请求并与C同步数据,会产生严重的数据错误。这便是实际情况中可能会出现的“双主”问题。
上面的例子中的分布式协议依赖于对节点状态认知的全局一致性,即一旦节点Q认为某个节点A异常,则节点A也必须认为自己异常,从而节点A停止作为primary,避免“双主”问题的出现。解决这种问题有两种思路,第一、设计的分布式协议可以容忍“双主”错误,即不依赖于对节点状态的全局一致性认识,或者全局一致性状态是全体协商后的结果;第二、利用lease机制。下面主要讨论怎么利用lease机制解决这个问题。
由中心节点向其他节点发送lease,若某个节点持有有效的lease,则认为该节点正常可以提供服务。节点 A、B、C 依然周期性的发送heart beat报告自身状态,节点Q收到heart beat后发送一个lease,表示节点Q确认了节点 A、B、C 的状态,并允许节点在 lease 有效期内正常工作。节点Q可以给 primary节点一个特殊的lease,表示节点可以作为primary工作。一旦节点Q希望切换新的primary,则只需等前一个primary的lease过期,则就可以安全的颁发新的lease给新的primary节点,而不会出现“双主”问题。
在实际系统中,若用一个中心节点发送lease也有很大的风险,一旦该中心节点宕机或网络异常,则所有的节点没有lease,从而造成系统高度不可用。为此,实际系统总是使用多个中心节点互为副本,成为一个小的集群,该小集群具有高可用性,对外颁发lease的功能。
由于有类似zookeeper的开源系统,在实际中完全可以间接使用lease。借助zookeeper,可以简单的实现高效的,无单点选主,状态监控,分布式锁,分布式消息队列等功能。实际上,这些功能背后都依靠着zookeeper与client之间的lease。
参考:分布式系统原理介绍.pdf