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的核心邏輯無關,這裏很少介紹。

相關文章
相關標籤/搜索