K8s client-go中,源生自带了一个leader库,便于实现分布式时间锁。
以K8s原生的controller-manager组件为例,当有三台master机器时,默认会运行三个controller-manager实例,但只有一个在干活,另外两个处于备用状态。而这个功能的实现,就依赖于分布式时间锁。
所有相关配置如下图所示:
锁的持有者,会每隔retryPeriod更新锁的有效期,表示它一直在持有这把锁。
特别说明下两个参数:
一. leaseTimeout
举个例子:现在有个房间,我要求当有人进入房间时,下一个人至少等待1小时才可进入房间。这时,我们可以将leaseTimeout设置为1小时,每当有人进入房间,则将房门上的时候改为当前时间。下一个人准备进入时,必须检查房门上的时间距离当前时间超过leaseTimeout。
之所以要这样设计,是因为在分布式情况下,只有程序活着的时候才可以要求它干什么,而一旦它异常了,它就失控了。而为了防止在它异常时,其它活着的程序可以正常接替它,所以就约定了leaseTimeout,一旦超过这个时间,则直接认定它异常,可以接管。
二. renewDeadline
上面的约定,无法防止脑裂。因为锁持有者在leaseTimeout中未更新锁,并不代表它已经挂了,它可能只是因为其它原因无法更新锁,或者程序夯住了,之后它可能再恢复。而如果它在别人接替它后,原持有者再恢复运行,则会导致脑裂,为了防止这种情况发生,针对锁持有者就设置了renewDeadline
如果锁持有者如果无法在renewDeadline时间内完成锁的更新,则要求锁持有者强制释放锁,程序退出。
所以renewDeadline必须比leaseTimeout小
上面流程很清晰,下面单独详细讲下:
尝试获取锁并更新锁
从上面获取锁流程,除了第一次创建锁之外,选举的关键就是观察时间: observedTime
id1异常情况
id1网络异常无法更新锁
从时序图中可看出来,监听时间的必要性。所有的flower(待接替者)都必须更新本地监听时间,必需保证在renewDeadline时间中,锁未发生任何变化,否则就需要再重新选举。
当然,还有一种极端情况:两个flower同时发现锁未发生任何变化,同时尝试去获取锁,这个时候就需要用到etcd的resourceVersion机制:在update时需要上送查询时的resourceVersion,表示在这过程中该资源未发生过其它更新。
原理就类似sql的select for update –> 查询时锁定记录。在这种情况下,etcd会保证先更新的能更新成功,后更新的会失败。这样就保证这种极端情况不会脑裂。
服务电话: 400-678-1800 (周⼀⾄周五 09:00-18:00)
商务合作: 0571-87770835
市场反馈: marketing@woqutech.com
地址: 杭州市滨江区滨安路1190号智汇中⼼A座1101室