租約,是ETCD的重要特性,用於實現key定時刪除功能。與Redis的定時刪除功能基本一致。golang
咱們一般是這麼使用Lease的,首先申請一個租約:lease,而後將這個租約賦給一對KeyValue。
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
之因此說不容易
,是說你能夠經過添加複雜的邏輯實現這些功能,但這樣作有一個沒法避免的問題:spa
雖然起一個協程成本很低,但過多的協程對資源浪費嚴重,還有可能被操縱系統強行kill。code
那麼咱們來看下ETCD是如何實現Lease的協程
在咱們對MVCC的介紹中,咱們知道ETCD的數據最終都是存在backend結構體中,因此backend掌握了對數據的增、刪、改、查。租約使用了backend的刪除能力。blog
租約,包含租約ID、ttl、過時時間等屬性。隊列
只有一個屬性:key。即保存了租約依附的key。說白了就是Key資源
租約隊列,多個租約是以隊列的形式保存在LeaseQueue中。
對租約的封裝。暴露出一系列操做租約的方法,好比建立、銷燬、延長租約的方法。
我若是想給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,申請一個租約的過程
lessor中維護了三個數據結構
map[LeaseID]*Lease
用於根據LeaseID快速找到*Leasemap[LeaseItem]LeaseID
用於根據LeaseItem快速找到LeaseID,從而找到*Lease快要到期的租約永遠在隊頭
。正如圖中所述,LeaseQueue是一個優先級隊列,每次插入都會根據過時時間插入到合適的位置。經過這個隊列,咱們只須要不斷檢查隊頭的租約是否到期便可,而避免了猜測
中的方法,爲每個租約起一個協程。
關於優先級隊列,廣泛的作法都是用堆來實現,ETCD中也不例外,他用的是GO標準庫中的container/heap
來實現的。這裏不具體說了。
從圖中能夠看出,當Grant一個租約l時,l被同時放到了LeaseMap和LeaseExpiredNotifier中。
在隊列頭,有一個工做協程revokeExpiredLeases不斷的查看隊頭的租約是否過時,若是過時就放入expiredChan中,不過此時不會pop。(只有revoke纔會從隊頭刪除)
再看下Attach的過程
Attach首先用LeaseID去LeaseMap中查詢租約是否存在,若是沒有這個租約返回錯誤。
租約存在則首先將Item保存到對應的租約下(圖中沒有註明),後將Item和LeaseID保存在ItemMap中。
最後看下Revoke過程
一般會有一個協程不斷消費expiredChan,將過時的租約Revoke。
Revoke首先根據LeaseID從LeaseMap找到對於的Lease並從LeaseMap中刪除,後從Lease中找到綁定的Key,從Backend中將KeyValue刪除。
以上即是ETCD-Lease的核心邏輯,與猜測
中的方案對比,我認爲最主要的是優先級隊列的使用。
Lessor還有一個概念是Primary,只有ETCD集羣中的Leader擁有的Lessor是Primary。也只有是Primary的Lessor能夠操做租約。由於與Raft相關,並且與Lease的核心邏輯無關,這裏很少介紹。