ETCD探索-Lease

ETCD探索-Lease

梗概

租约,是ETCD的重要特性,用于实现key定时删除功能。与Redis的定时删除功能基本一致。golang

猜测

咱们一般是这么使用Lease的,首先申请一个租约:lease,而后将这个租约赋给一对KeyValue。
image.png
ETCD-Lease的实现不难,在讨论怎么实现以前,能够先猜想下。
个人直观想法:网络

func putWithLease(key string, value string, ttl int) {
    go func() {
        time.Sleep(ttl * time.Second)
        
        delete(key)
    }()
    
    put(key, value)
}

简单说明,当put一对kv时,开启一个协程用于计时。当过了ttl后,将该key删除。数据结构

这么作能够实现key的定时删除功能,但有一些问题:less

  • 不容易续租(续租:延长ttl)
  • 不容易提早删除租约

之因此说不容易,是说你能够经过添加复杂的逻辑实现这些功能,但这样作有一个没法避免的问题:spa

  • 当租约不少时,协程就会不少

虽然起一个协程成本很低,但过多的协程对资源浪费严重,还有可能被操纵系统强行kill。code

那么咱们来看下ETCD是如何实现Lease的协程

实现

结构体介绍

  • backend

在咱们对MVCC的介绍中,咱们知道ETCD的数据最终都是存在backend结构体中,因此backend掌握了对数据的增、删、改、查。租约使用了backend的删除能力。blog

  • Lease

租约,包含租约ID、ttl、过时时间等属性。队列

  • LeaseItem

只有一个属性:key。即保存了租约依附的key。说白了就是Key资源

  • LeaseQueue

租约队列,多个租约是以队列的形式保存在LeaseQueue中。

  • Lessor

对租约的封装。暴露出一系列操做租约的方法,好比建立、销毁、延长租约的方法。

如何使用租约

我若是想给key=foo绑定一个租约,而且时间过时后将key删除

func testLease() {
    le := newLessor()    // 建立一个lessor
    le.Promote(0)        // 将lessor设置为Primary,这个与raft会出现网络分区有关,不了解能够忽略
    
    go func() {          // 开启一个协程,接收过时的key,主动删除
        for {  
           expireLease := <-le.ExpiredLeasesC()  

           for _, v := range expireLease {  
              le.Revoke(v.ID)    // 经过租约ID删除租约,删除租约时会从backend中删除绑定的key
           }  
        }
    }()
    
    ttl = 5                      // 过时时间设置5s
    lease := le.Grant(id, ttl)   // 申请一个租约
    
    le.Attach(lease, "foo")      // 将租约绑定在"foo"上
    
    time.Sleep(10 * time.Second) // 阻塞10s,方便看到结果
}

以上展现了是如何使用lessor这个结构体的。不难看出,lessor提供了Grant、Revoke、Attach等一系列对租约的操做。同时有一点须要注意,lessor不会主动删除过时的租约,而是将过时的lease经过一个chan发送出来,由使用者主动删除。

首先咱们看下Grant,申请一个租约的过程
image.png

lessor中维护了三个数据结构

  • LeaseMap
    map[LeaseID]*Lease 用于根据LeaseID快速找到*Lease
  • ItemMap
    map[LeaseItem]LeaseID 用于根据LeaseItem快速找到LeaseID,从而找到*Lease
  • LeaseExpiredNotifier
    LeaseExpiredNotifier是对LeaseQueue的一层封装,他实现了快要到期的租约永远在队头

正如图中所述,LeaseQueue是一个优先级队列,每次插入都会根据过时时间插入到合适的位置。经过这个队列,咱们只须要不断检查队头的租约是否到期便可,而避免了猜测中的方法,为每个租约起一个协程。

关于优先级队列,广泛的作法都是用堆来实现,ETCD中也不例外,他用的是GO标准库中的container/heap来实现的。这里不具体说了。

从图中能够看出,当Grant一个租约l时,l被同时放到了LeaseMap和LeaseExpiredNotifier中。

在队列头,有一个工做协程revokeExpiredLeases不断的查看队头的租约是否过时,若是过时就放入expiredChan中,不过此时不会pop。(只有revoke才会从队头删除)

再看下Attach的过程
image.png

Attach首先用LeaseID去LeaseMap中查询租约是否存在,若是没有这个租约返回错误。
租约存在则首先将Item保存到对应的租约下(图中没有注明),后将Item和LeaseID保存在ItemMap中。

最后看下Revoke过程
image.png

一般会有一个协程不断消费expiredChan,将过时的租约Revoke。
Revoke首先根据LeaseID从LeaseMap找到对于的Lease并从LeaseMap中删除,后从Lease中找到绑定的Key,从Backend中将KeyValue删除。

以上即是ETCD-Lease的核心逻辑,与猜测中的方案对比,我认为最主要的是优先级队列的使用。

Lessor还有一个概念是Primary,只有ETCD集群中的Leader拥有的Lessor是Primary。也只有是Primary的Lessor能够操做租约。由于与Raft相关,并且与Lease的核心逻辑无关,这里很少介绍。

相关文章
相关标签/搜索