etcd lease 模块架构解析

2,150 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

前面介绍了# etcd 如何实现租约?,本文将会具体介绍 lease 模块架构的解析。

lease 模块架构

lease 模块对外提供了 Lessor 接口,其中定义了包括 Grant、Revoke、Attach 和 Renew 等常用的方法,lessor 结构体实现了 Lessor 接口。lessor 依赖 Lease 对象,而与 BoltDB 的交互也是通过 Lease 实现。lease 模块涉及到的主要对象和接口。

除此之外,lessor 还启动了两个异步 goroutine:RevokeExpiredLease 和 CheckpointScheduledLease,分别用于撤销过期的租约和更新 Lease 的剩余到期时间。

Lessor 接口

Lessor 接口定义了创建、销毁、延长租约的方法:

// 位于 lease/lessor.go:82
type Lessor interface {
  //...省略部分接口

	// Grant 创建了一个在指定时间过期的 lease 对象
	Grant(id LeaseID, ttl int64) (*Lease, error)
	// Revoke 撤销指定 Id 的 lease,绑定到其上的键值对将会被移除,如果该 Id 对应的 lease 不存在,则会返回错误
	Revoke(id LeaseID) error

	// Attach 绑定给定的 leaseItem 到 LeaseID,如果该租约不存在,将会返回错误
	Attach(id LeaseID, items []LeaseItem) error

	// GetLease 返回 LeaseItem 对应的 LeaseID
	GetLease(item LeaseItem) LeaseID

	// Detach 将 leaseItem 从给定的 LeaseID 解绑。如果租约不存在,则会返回错误
	Detach(id LeaseID, items []LeaseItem) error

	// Renew 刷新指定 id 的 lease,结果将会返回刷新后的 TTL
	Renew(id LeaseID) (int64, error)

	// Lookup 查找指定的 LeaseID,返回对应的 Lease
	Lookup(id LeaseID) *Lease

	// Leases 列出所有的 leases
	Leases() []*Lease

	// ExpiredLeasesC 用于返回接收过期 lease 的 channel
	ExpiredLeasesC() <-chan []*Lease

}

Lessor 接口定义了很多方法,租约相关的方法都在这里面。常用的方法有:

  • Grant 创建一个在指定时间过期的 lease 对象
  • Revoke 撤销指定 Id 的 lease,绑定到其上的键值对将会被移除
  • Attach 绑定给定的 leaseItem 到 LeaseID
  • Renew 刷新指定 id 的 lease,结果将会返回刷新后的 TTL

Lease 与 lessor 结构体

我们接着看下租约相关的 Lease 结构体:

// 位于 lease/lessor.go:800
type Lease struct {
	ID           LeaseID
	ttl          int64 // time to live of the lease in seconds
	remainingTTL int64 // remaining time to live in seconds, if zero valued it is considered unset and the full ttl should be used
	// expiryMu protects concurrent accesses to expiry
	expiryMu sync.RWMutex
	// expiry is time when lease should expire. no expiration when expiry.IsZero() is true
	expiry time.Time

	// mu protects concurrent accesses to itemSet
	mu      sync.RWMutex
	itemSet map[LeaseItem]struct{}
	revokec chan struct{}
}

租约包含租约 ID、ttl、过期时间等属性。lessor 则是对租约的封装。暴露出一系列操作租约的方法,比如创建、销毁、延长租约的方法。

type lessor struct {
	mu sync.RWMutex

	// demotec is set when the lessor is the primary.
	// demotec will be closed if the lessor is demoted.
	demotec chan struct{}

	leaseMap             map[LeaseID]*Lease
	leaseExpiredNotifier *LeaseExpiredNotifier
	leaseCheckpointHeap  LeaseQueue
	itemMap              map[LeaseItem]LeaseID

	// When a lease expires, the lessor will delete the
	// leased range (or key) by the RangeDeleter.
	rd RangeDeleter

	// When a lease's deadline should be persisted to preserve the remaining TTL across leader
	// elections and restarts, the lessor will checkpoint the lease by the Checkpointer.
	cp Checkpointer

	// backend to persist leases. We only persist lease ID and expiry for now.
	// The leased items can be recovered by iterating all the keys in kv.
	b backend.Backend

	// minLeaseTTL is the minimum lease TTL that can be granted for a lease. Any
	// requests for shorter TTLs are extended to the minimum TTL.
	minLeaseTTL int64

	expiredC chan []*Lease
	// stopC is a channel whose closure indicates that the lessor should be stopped.
	stopC chan struct{}
	// doneC is a channel whose closure indicates that the lessor is stopped.
	doneC chan struct{}

	lg *zap.Logger

	// Wait duration between lease checkpoints.
	checkpointInterval time.Duration
	// the interval to check if the expired lease is revoked
	expiredLeaseRetryInterval time.Duration
}

lessor 实现了 Lessor 接口,lessor 中维护了三个数据结构:

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

LeaseQueue 是一个优先级队列,每次插入都会根据过期时间插入到合适的位置。通过这个队列,我们只需要不断检查队头的租约是否到期即可,而避免为每一个租约起一个协程。

关于优先级队列,普遍的做法都是用堆来实现,etcd 中也不例外,他用的是 Go 标准库中的 container/heap 来实现的。可以看到,里边有 leaseMap 和 leaseHeap ,为什么要有两个呢?此处的 leaseHeap 实现是最小堆,比较的关键是 Lease 失效的时间。

怎么保证 lease 失效呢?我们每次从最小堆里判断堆顶元素是否失效,失效就 Pop 就可以了。那为什么又要有 leaseMap 呢?因为 这样可以加速查找,毕竟,哈希表的时间复杂度是 O(1) 。

阅读最新文章,关注公众号:aoho求索