一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
前面介绍了lease 模块架构的解析,本文将会具体介绍 Grant 申请租约,绑定和撤销租约的实现。
Grant 申请租约
Grant 方法用于申请租约,并在指定的 ttl 失效。具体实现如下:
// 位于 lease/lessor.go:258
func (le *lessor) Grant(id LeaseID, ttl int64) (*Lease, error) {
if id == NoLease {
return nil, ErrLeaseNotFound
}
if ttl > MaxLeaseTTL {
return nil, ErrLeaseTTLTooLarge
}
// TODO: when lessor is under high load, it should give out lease
// with longer TTL to reduce renew load.
l := &Lease{
ID: id,
ttl: ttl,
itemSet: make(map[LeaseItem]struct{}),
revokec: make(chan struct{}),
}
le.mu.Lock()
defer le.mu.Unlock()
if _, ok := le.leaseMap[id]; ok {
return nil, ErrLeaseExists
}
if l.ttl < le.minLeaseTTL {
l.ttl = le.minLeaseTTL
}
if le.isPrimary() {
l.refresh(0)
} else {
l.forever()
}
le.leaseMap[id] = l
item := &LeaseWithTime{id: l.ID, time: l.expiry.UnixNano()}
le.leaseExpiredNotifier.RegisterOrUpdate(item)
l.persistTo(le.b)
leaseTotalTTLs.Observe(float64(l.ttl))
leaseGranted.Inc()
if le.isPrimary() {
le.scheduleCheckpointIfNeeded(l)
}
return l, nil
}
绘制的流程图如下所示:
可以看到,当 Grant 一个租约 l 时,l 被同时放到了 LeaseMap 和 LeaseExpiredNotifier 中。
在队列头,有一个工作协程 revokeExpiredLeases 不断的查看队头的租约是否过期,如果过期就放入 expiredChan 中,不过此时不会 pop (只有 revoke 才会从队头删除)。
Attach 绑定租约
Attach 用于绑定指定的 Lease Id 到一系列的键值对上面。当租约过期,绑定的键值对会被自动移除。绑定时,如果给定的 lease 不存在,将会返回错误。
// 位于 lease/lessor.go:518
func (le *lessor) Attach(id LeaseID, items []LeaseItem) error {
le.mu.Lock()
defer le.mu.Unlock()
l := le.leaseMap[id]
if l == nil {
return ErrLeaseNotFound
}
l.mu.Lock()
for _, it := range items {
l.itemSet[it] = struct{}{}
le.itemMap[it] = id
}
l.mu.Unlock()
return nil
}
Attach 首先用 LeaseID 去 LeaseMap 中查询租约是否存在,如果没有这个租约返回错误。
租约存在则首先将 Item 保存到对应的租约下(图中没有注明),后将 Item 和 LeaseID 保存在 ItemMap 中。
Revoke 撤销租约
Revoke 方法用于撤销指定 Id 的租约,绑定到该 Lease 上的键值都会被移除。
// 位于
func (le *lessor) Revoke(id LeaseID) error {
le.mu.Lock()
l := le.leaseMap[id]
if l == nil {
le.mu.Unlock()
return ErrLeaseNotFound
}
defer close(l.revokec)
// unlock before doing external work
le.mu.Unlock()
if le.rd == nil {
return nil
}
txn := le.rd()
// sort keys so deletes are in same order among all members,
// otherwise the backend hashes will be different
keys := l.Keys()
sort.StringSlice(keys).Sort()
for _, key := range keys {
txn.DeleteRange([]byte(key), nil)
}
le.mu.Lock()
defer le.mu.Unlock()
delete(le.leaseMap, l.ID)
// lease deletion needs to be in the same backend transaction with the
// kv deletion. Or we might end up with not executing the revoke or not
// deleting the keys if etcdserver fails in between.
le.b.BatchTx().UnsafeDelete(leaseBucketName, int64ToBytes(int64(l.ID)))
txn.End()
leaseRevoked.Inc()
return nil
}
绘制的流程图如下所示。
通常会有一个协程不断消费 expiredChan,将过期的租约 Revoke。Revoke 首先根据 LeaseID 从 LeaseMap 找到对于的 Lease 并从 LeaseMap 中删除,后从 Lease 中找到绑定的 Key,从 Backend 中将 KeyValue 删除。