坚持思考,就会很酷
在 redis 中有一个 ttl 的功能。ttl 是 time to live 的缩写。在 redis 里我们可以设置 key 的 ttl ,从而指定这个 key 存活的时间,过期就会自动销毁。
在 etcd 也有一个类似的机制:租约( Lease )机制。从效果上来讲,租约机制也能做到类似的过期自动删除 key 的功能。但是两者细节大有不同。
租约( Lease )是什么?
简单讲就是一个具有一个时间期限的“对象”。
划重点:时间期限。
举个不准确的栗子:
有一个大公司(代表一个中心权威组织,比如 etcd )有个粗活,并且工作特殊明确只需要一个程序猿( worker )。
那一般怎么操作呢?
有个程序猿 A 想做这个事,来找公司申请,于是公司给了他一个 3 天期限的租约( Lease ),并承诺该权限 3 天内不会再给这个权限给别人,但是 3 天之后,公司就可以另寻他猿了( 注意:如果猿 A 在 3 天内续约了,A 就可以延续他的权限了,那就是另外一回事了 )。
这样的话,就能保证始终只有一个猿有合法权限做这件事。 如果猿 A 三天后不抗压、失联了,那 3 天之后公司也能安全的(没有违反承诺)再找一只猿。
划重点:租约就是一个带时间期限的承诺。
和 redis 不同,etcd 中把这个时间相关的概念抽离出来,命名为 Lease 对象。所以,要使用租约则先要创建这么一个 Lease 对象。然后把 key 绑定到这个 Lease 上,就相当于设置了这个 key 的生命周期。
细节来了,key 和 Lease 是怎么对应的?
划重点:key 和 Lease 是多对一的关系。一个 key 最多只能挂绑定一个 Lease ,但是一个 Lease 上能挂多个 key 。 这种设计提高 etcd 整体的性能。Lease 刷新一次就对应了一批的 key ,否则每一个 key 都独立刷新 ttl 的话,开销可不小呢。
举个 etcd 实际的栗子,怎么设置一个 60 秒有效的 key ?如下:
先创建一个 Lease 对象:
root@ubuntu:~/# etcdctl lease grant 60
lease 694d7d17eaab280f granted with TTL(60s)
再把一个 key 绑定到这个 Lease :
root@ubuntu:~/# etcdctl put hello world --lease=694d7d17eaab280f
OK
这样 hello/world 这对 key/value 就创建好了,并且 60 秒后将被自动删除。
有些童鞋可能会好奇,租约一般用来做什么呢?
就本质上来讲,租约就是一个具有生命周期的对象。怎么使用它?这依赖于用户的想象力。
曾经我在分布式的分享章节里提到过,lease 是分布式的基石技术之一,lease 就是一个有时间限定的权限(承诺)。分布式的冗余节点都可以来申请一段时间的权限(有了这个权限就可以做某件事情),租约过期之后就可以回收,租约没过期之前就维持承诺。这个租约的管理一般放在一个中心化的节点(或者集群中),比如 etcd 集群。
为什么申请的权限一定要附上时间期限呢?
因为在分布式的恶劣环境下,谁都有可能挂。挂了的话,冗余节点要能顶上来,这个权限要能安全移交。租约没过期之前,权限的移交都是不安全的。租约过期之后,权限就能安全移交。所以,租约常常用在恶劣的分布式系统中做可靠的授权管理。
还一个 etcd 最常见的场景是当作注册中心来用,worker 节点注册到 etcd 集群。每个都申请了租约,并且定期的会续约( keep-alive 保活),一旦长久失效,那么就可以剔除。这样起到一个节点的管理之用。
下面从 etcd 内部的实现原理出发,来看租约机制的核心知识点。在 etcd 中,由一个叫做 lessor 的对象来管理租约,并且关于续租等等操作都必须要是 leader 才能操作。
1 租约的创建
租约的创建必须走 raft 状态机,把 Lease 创建这个消息在集群中达到一致,达到一致之后,每个节点就可以构建 Lease 结构体,并且持久化这个结构体到 boltdb 中,存储在一个叫做 "lease" 的 bucket 中。
func (le *lessor) Grant(id LeaseID, ttl int64) (*Lease, error) {
// 构造一个 Lease 结构体
l := &Lease{
ID: id,
ttl: ttl,
// ..
}
// 设置 expire time( primary 可做)
l.refresh(0)
// Lease 在内存的 map 里也放一份,好索引呀
le.leaseMap[id] = l
// 持久化到 boltdb 里去
l.persistTo(le.b)
// 投递到一个带小堆的队列中,这个关联超时机制(primary 可做)
le.leaseExpiredNotifier.RegisterOrUpdate(item)
// 投递到 checkpoint 的队列中,这个关联 checkpoint 机制(primary 可做)
le.scheduleCheckpointIfNeeded(l)
}
租约创建很简单,最关键的是先要走 raft 机制,然后走上面的 grant 流程,传入一个 LeaseID,一个 ttl ,持久化到 boltdb 并修改内存结构,主要步骤:
1 . 构建一个 Lease 结构体;
2 . 修改 leaseMap,id => lease ;
3 . 持久化,把 Lease 这个结构体写到磁盘( boltdb );
a . 对应写到“lease” 这个桶里;
4 . 设置 Lease.expiry ,这里是设置为 now + ttl 的时间,是未来超时的那个时刻;
5 . 构建一个 LeaseWithTime 的结构体,加入到 heap 里面去管理;
a . 加到 leaseExpiredNotifier 里面,关联超时机制;
b . 加到 leaseCheckpointHeap 里面,关联 checkpoint 机制;
划重点:Lease 的创建是要持久化的,并且是先走 raft 的状态机在 etcd 集群达到一致后,才持久化到 boltdb 中。
2 租约的绑定
key 是怎么绑定到 Lease 的呢?这是一个非常关键的问题。
时机肯定在 key/value 上传的时候,也就是 put 的时候,位于 storeTxnWrite.put 方法之中:
// etcd/mvcc/kvstore_txn.go
func (tw *storeTxnWrite) put(key, value []byte, leaseID lease.LeaseID) {
// ...
// 存储到 bolt db 文件
tw.tx.UnsafeSeqPut(keyBucketName, ibytes, d)
// ...
// 如果 leaseID 有效,那么说明要绑定 Lease 了
if leaseID != lease.NoLease {
// LeaseID 和 key 关联起来
err = tw.s.le.Attach(leaseID, []lease.LeaseItem{{Key: string(key)}})
}
}
划重点:数据持久化到 boltdb 之后,再去关联对应的 Lease 结构体。 那关联是什么操作呢?很简单,就是把这个 key 加到 Lease 内部的 map 中:
// etcd/lease/lessor.go
func (le *lessor) Attach(id LeaseID, items []LeaseItem) error {
for _, it := range items {
// 把这个 key 放到 Lease 结构体里
l.itemSet[it] = struct{}{}
// 把这个 key 放到 lessor 的结构体里,这里作为一个平坦的 map
le.itemMap[it] = id
}
}
跟这个 Lease 关联的所有 key 都在 Lease.itemSet 这个 map 中。
3 租约的过期
租约的过期和销毁是 etcd 内部的流程触发。租约的过期在创建的时候就关联上了,还记得创建的时候有一个加队列的代码吗?
// 投递到一个带小堆的队列中,这个关联超时机制(primary 可做)
le.leaseExpiredNotifier.RegisterOrUpdate(item)
这行代码把 Lease 加到一个内含最小堆的结构中。每次都看小堆顶即可(因为它生命剩余最小,最有可能超时),小堆顶的 Lease 超时了,那么就取出来,直到取到没超时的 Lease ,那么本轮结束。
// 取出一个超时的 Lease 结构,它上面可能有一批的 key
func (le *lessor) expireExists() (l *Lease, ok bool, next bool) {
// 取小堆顶
item := le.leaseExpiredNotifier.Poll()
now := time.Now()
// 看是否超时
if now.UnixNano() < item.time /* expiration time */ {
return l, false, false
}
}
这样每次都处理一批超时的 Lease 结构,走销毁流程,过期销毁主要做两件事:
key 被销毁之后就相当于被自动删除了,用户就下载不到了。 销毁的流程在 lessor.Revoke :
func (le *lessor) Revoke(id LeaseID) error {
// 遍历删除这个 Lease 关联的所有 key (从 boltdb 里删除)
for _, key := range keys {
txn.DeleteRange([]byte(key), nil)
}
// 销毁内存结构
delete(le.leaseMap, l.ID)
// 把这个 Lease 从 boltdb 的 lease 桶里删除
le.b.BatchTx().UnsafeDelete(leaseBucketName, int64ToBytes(int64(l.ID)))
}
划重点:Lease 的销毁不仅是内存的,还有 boltdb 的 lease 桶里的都要清理。是设计到持久化的。
4 租约的续租
续租需要由 leader 完成,但是 etcd 的续租并没有走 raft 在集群中达到一致性。它仅仅是在内存中修改过期时间。
func (le *lessor) Renew(id LeaseID) (int64, error) {
// 必须要 leader 节点才能做这个事情
if !le.isPrimary() {
return -1, ErrNotPrimary
}
// ...
// 重置超时时间
l.refresh(0)
// 更新小堆的里面对应的元素
le.leaseExpiredNotifier.RegisterOrUpdate(item)
}
可以看到,在 leader 节点里对于续租做的事情很简单,就是刷新过期( expiry )时间,并且刷新最小堆的元素,这样它就相当于续命了,不会超时啦。
划重点:续租没啥持久化的。
5 租约的 CheckPoint 机制
在上面我们看到, Lease 创建和销毁是涉及到持久化的,对于续租则存储是内存操作。那这里在集群异常的场景可能导致一个不准确的问题。
比如 Lease 是 300 秒,已经过去 100 秒,突然切主。那起来的时候 Lease 就不知道多少了?
对于这个 etcd 有一个 checkpoint 机制,这个机制本质上就是定期让 leader 看一眼剩余的 ttl 还有多少,然后同步给集群其他节点,以此为准。
所以,CheckPoint 要走 raft 状态机。
6 租约加载恢复
在 storeTxnWrite.put 里面我们看到了 key 上传的时候会和 Lease 关联,其实还有一个时机:etcd 进程启动的时候。
func (s *store) restore() error {
// 遍历 bucket 里面所有的 key ,把所有跟 Lease 关联的 key 放到 keyToLease
// 遍历这些和有 Lease 关联的 key
for key, lid := range keyToLease {
// Lease ID 和 key 关联起来
err := s.le.Attach(lid, []lease.LeaseItem{{Key: key}})
}
}
做的事情很简单,就是在进程重启的时候,需要加载分析所有的 key ,把那些跟 Lease 关联的 key 捞出来,然后跟 Lease 关联起来。
小知识点:lessor 结构体要先于此创建出来。
租约妙 用多多,童鞋你用过哪些呢?
~完~
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/dWmzNhBxtd6MtBepqSGsfg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。