ETCD 源码学习--lease(二)

在 ETCD 源码学习过程,不会讲解太多的源码知识,只讲解相关的实现机制,需要关注源码细节的朋友可以自行根据文章中的提示,找到相关源码进行学习。

本文主要介绍 lessor 如果发现过期键,server 如何处理过期键的过程。

过期 lease 发现

lessor goroutine

func (le *lessor) runLoop() {
	defer close(le.doneC)
	for {
		le.revokeExpiredLeases()
		le.checkpointScheduledLeases()
        ...
	}
}

runLoop 中,调用 revokeExpiredLeases 找到过期的 lease,并推入 expiredC,等待被处理。

func (le *lessor) revokeExpiredLeases() {
	var ls []*Lease
	//最大过期元素数量
	revokeLimit := leaseRevokeRate / 2 

	le.mu.RLock()
	if le.isPrimary() { //判断是否为主 Lessor
		ls = le.findExpiredLeases(revokeLimit) //查找过期键
	}
	le.mu.RUnlock()
	if len(ls) != 0 {
		select {
		...
		case le.expiredC <- ls: //推入管道,等待上传处理
		default:
		}
	}
}
func (le *lessor) findExpiredLeases(limit int) []*Lease {
	leases := make([]*Lease, 0, 16)

	for {
		//在过期 Lease 的优先级队列中,找到过期 Lease
		l, ok, next := le.expireExists()
		...
		if l.expired() {
			leases = append(leases, l)
			if len(leases) == limit { //如果能达到收集的最大元素个数
				break
			}
		}
	}

	return leases
}
func (le *lessor) expireExists() (l *Lease, ok bool, next bool) {
	...
	item := le.leaseExpiredNotifier.Poll() //获取过期优先级队列中的第一个元素
	...
	now := time.Now()
	//第一个元素还没过期(第一个元素是最早过期元素)
	if now.UnixNano() < item.time /* expiration time */ {
		return l, false, false
	}
	//这里重新更新时间是为了让这个元素不要在顶部,以便下一次收集过期元素
	item.time = now.Add(le.expiredLeaseRetryInterval).UnixNano()
	le.leaseExpiredNotifier.RegisterOrUpdate(item) 
	return l, true, false
}

Server 处理过期 Lease

run(){
    var expiredLeaseC <-chan []*lease.Lease
	if s.lessor != nil {
		expiredLeaseC = s.lessor.ExpiredLeasesC()
	}
	for {
		select {
		...
		case leases := <-expiredLeaseC: //过期租约
			//启动一个 goroutine,处理过期租约
			s.goAttach(func() {
				c := make(chan struct{}, maxPendingRevokes)
				for _, lease := range leases {
					...
					lid := lease.ID
					s.goAttach(func() { //撤销租约
						ctx := s.authStore.WithRoot(s.ctx)
						_, lerr := s.LeaseRevoke(ctx, &pb.LeaseRevokeRequest{ID:int64(lid)})
                            ...
						<-c
					})
				}
			})
		
			return
		}
	}}

 在 s.LeaseRevoke 中,其实走的一个是提案流程,走完整个提案流程之后,才会转换为 apply 消息,被节点应用。

//server 应用提案消息
func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) {
	...
	var ar *applyResult
	needResult := s.w.IsRegistered(id) //检查请求是否需要响应
	if needResult || !noSideEffect(&raftReq) {
		...
		ar = s.applyV3.Apply(&raftReq) //应用消息
	}
	...
}
//etcdserver/apply.go
func (a *applierV3backend) Apply(r *pb.InternalRaftRequest) *applyResult {
	...
	switch {
	
	case r.LeaseGrant != nil:
		ar.resp, ar.err = a.s.applyV3.LeaseGrant(r.LeaseGrant)
	case r.LeaseRevoke != nil:
		ar.resp, ar.err = a.s.applyV3.LeaseRevoke(r.LeaseRevoke)
	case r.LeaseCheckpoint != nil:
		ar.resp, ar.err = a.s.applyV3.LeaseCheckpoint(r.LeaseCheckpoint)
	package 3
		...
	}
	return ar
}

func (a *applierV3backend) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
	l, err := a.s.lessor.Grant(lease.LeaseID(lc.ID), lc.TTL)
	resp := &pb.LeaseGrantResponse{}
	...
	return resp, err
}

func (a *applierV3backend) LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
	err := a.s.lessor.Revoke(lease.LeaseID(lc.ID))
	return &pb.LeaseRevokeResponse{Header: newHeader(a.s)}, err
}

总结

1.过期键的发现主要通过一个时间戳的优先级队列,这里主要通过最小堆这种数据结构实现,将最小的时间戳,置于顶部,每次只需要判断顶部元素即可。

2.过期键收集要在一定的范围内,不控制每次最大的过期键的处理数量,如果有大量键同时过期,会导致 server 在一定时间内忙于处理过期键,而无法响应其他请求。

3.server 处理过期键的过程是一个完整的提案过程。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值