- Lease 机制 * 无 Lease 的数据缓存 * 带 Lease 的缓存系统 * Lease 设置时间长短的讲究 * 基于 Lease 机制确定节点状态 * Lease 的有效期时间选择
Lease 机制
Lease 中文名称叫做租约,Lease 机制式分布式系统中非常非常重要的机制,通常最重要的应用:判定节点的状态。
Lease 的关键在于:一个给定的时间内,对一个承诺的坚守。Lease 过期之前,一定要坚守你的承诺。这个承诺的内容可以宽泛,可以是数据的正确性,也可以是某种授权。
以一个数据缓存例子,我们用 Lease 解决缓存一致性,感受下 Lease 机制到底是怎么回事。
无 Lease 的数据缓存
场景一:Client A 11:20 01 缓存数据
- 用户 11:20: 01 请求
key_A
的 value - 客户端 Cache Miss,本地没有数据缓存,所以向后端 Server 请求
key_A
- 后端返回 value_A(假设当前
key_A => value_A
) Client A
先 Cache 一份到本地( 我们再做一个假设,假设设置了数据过期时间为30s ),然后向用户返回
场景二:Client B 11:20 20 更新了数据
接着另外一个 Client B 更新 key_A 的值为 value_A_1
那么这个时候,就已经出现了数据的不一致,Server 存储的是key_A => value_A_1
而,Client A
缓存的是 key_A => value_A
,这个时候数据还没有过期。如果用户请求发送到了 Client A
那就会出现数据不一致的情况,因为 Server 存储的是 key_A => value_A_1
,而 Client A 返回的是 key_A => value_A
。
带 Lease 的缓存系统
接下来我们考虑怎么使用 Lease 解决数据一致性问题,这里要引入一个承诺:Server 给 Client 一个承诺,在我给定的时间内,我不会更新这个 key 的数据。
场景一:Client A 11:20 01 缓存数据
- 用户 11:20: 01 请求
key_A
的 value - 客户端 Cache Miss,本地没有数据缓存,所以向后端 Server 请求
key_A
- 后端返回 value_A(假设当前
key_A => value_A
,并且设置了一个 Lease,代表一个承诺,就是 11:20 31 以前,我不会更新这个 key_A ) Client A
先 Cache 一份到本地( 带 Lease 的 11:20 31 ),然后向用户返回
场景二:Client B 11:20 20 请求更新数据
接着另外一个 Client B 请求 Server 更新 key_A 的值为 value_A_1,这个时候 Server 发现自己还有一个承诺在,这个承诺需要保持到 11:20 31,所以就会硬等,硬等到 11:20 31 之后才会更新 key_A。
- Client B 发更新
key_A
的请求到 Server - Server 发现 Lease 需要等到 11:20 31 才失效;所以就直接 sleep 11s(我这里简单假设哈,真正的实现不会这么粗暴)
- Server 等到 11:20 32,发现承诺已经可以失效了,这样更新这个
key_A
的值了(等到这个时间更新是安全的,数据失踪是一致的)- 这样和 Client A 的理解就是一致了,因为 Client A对数据的理解也是 11:20 31 过期
Lease 设置时间长短的讲究
Lease 机制由于是依赖于有效期的,这就要求颁发者和接收者的时钟是同步的。分为两个场景点讨论:
- 如果颁发者的时钟比接收者的时钟慢,则当接收者认为 lease 已经过期的时候,颁发者已经认为 Lease 有效,这种场景没有什么大问题,最多就是接收者重新申请lease而已;
- 在上面的例子:颁发者是 Server,接受者是 Client A
- 如果颁发者的时钟比接收者的快,这种会导致,颁发者认为过期了,但是接收者还依旧认为有效。这个可能导致同一时间多个 Lease 被发出去了。这个会影响到系统的正确性,对于这种时钟不同步,实践中的通常做法是将颁发者的有效期设置得比接收者略大,只需要大过时钟得误差就可以避免lease的有效性的影响。
- 这个场景就是:Server 认为承诺已经到期了,就接受了 Client B 的更新,这样就导致 Client A 的数据和 Server 不一致了
基于 Lease 机制确定节点状态
在分布式系统中确定一个节点是否处于正常工作状态是一个困难的问题,由于可能存在网络分化,节点的状态是无法通过网络通信来确定的。
首先,纯基于“心跳”(Heartbeat)的方法也无法解决这个问题。
比如 Arbiter 作为一个监控节点,A,B,C 作为三个副本节点,Arbiter 通过与 A,B,C 的心跳来判断节点状态,从而选主,但如果只通过心跳判断,是有问题的。
Arbiter,A 之间的网络可能存在问题。如果只通过这个心跳选主,那么就可能出现双主的问题,因为 A 不一定认为自己异常了,其问题的本质是由于网络分化造成的系统对于“节点状态”认知不一致。
上面的例子中,如果要保证系统的正确性,依赖于对节点状态认知的全局一致的,即一旦节点Arbiter 认为某个节点 A 异常,则节点A也必须认为自己异常,从而节点 A 停止作为 Primary,避免出现“双主”的问题。
那么利用 Lease 机制,可以怎么改造?
- A,B,C 依然周期行的发送 heart beat 报告自身状态,节点Q 收到 heart beat 后发送一个 Lease,表示 Arbiter 确认了节点 A,B,C的状态,并允许节点在 Lease 有效期内正常工作
- 节点 Arbiter 可以给 Primary 节点一个特殊的 Lease,表示节点可以作为Primary 工作,一旦节点 Arbiter 希望切换新的 Primary,则只需要等前一个Primary 的 Lease过期,则就可以安全的颁发新的 Lease给新的 Primary节点,而不会出现双主的问题
常见的承诺:
- lease 这段时间内,我们不更新数据
- lease 这段时间内,不要选别人(比如 paxos)
- lease 这段时间内,我不 kill 你
Lease 的有效期时间选择
Lease的时间选择:
- 太短,则太容易回收承诺,抖动频繁
- 太长,则太不容易回收承诺,可能导致可用性问题
工程中,常选择 lease 时长是 10 秒级别,这是一个经验值。
坚持思考,方向比努力更重要。微信公众号关注我:奇伢云存储