mirror of https://github.com/milvus-io/milvus.git
150 lines
4.3 KiB
Go
150 lines
4.3 KiB
Go
// Copyright 2019 TiKV Project Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package election
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/czs007/suvlim/errors"
|
|
"github.com/pingcap/log"
|
|
"github.com/tikv/pd/pkg/etcdutil"
|
|
"go.etcd.io/etcd/clientv3"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
revokeLeaseTimeout = time.Second
|
|
requestTimeout = etcdutil.DefaultRequestTimeout
|
|
slowRequestTime = etcdutil.DefaultSlowRequestTime
|
|
)
|
|
|
|
// lease is used as the low-level mechanism for campaigning and renewing elected leadership.
|
|
// The way to gain and maintain leadership is to update and keep the lease alive continuously.
|
|
type lease struct {
|
|
// purpose is used to show what this election for
|
|
Purpose string
|
|
// etcd client and lease
|
|
client *clientv3.Client
|
|
lease clientv3.Lease
|
|
ID clientv3.LeaseID
|
|
// leaseTimeout and expireTime are used to control the lease's lifetime
|
|
leaseTimeout time.Duration
|
|
expireTime atomic.Value
|
|
}
|
|
|
|
// Grant uses `lease.Grant` to initialize the lease and expireTime.
|
|
func (l *lease) Grant(leaseTimeout int64) error {
|
|
start := time.Now()
|
|
ctx, cancel := context.WithTimeout(l.client.Ctx(), requestTimeout)
|
|
leaseResp, err := l.lease.Grant(ctx, leaseTimeout)
|
|
cancel()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
if cost := time.Since(start); cost > slowRequestTime {
|
|
log.Warn("lease grants too slow", zap.Duration("cost", cost), zap.String("purpose", l.Purpose))
|
|
}
|
|
log.Info("lease granted", zap.Int64("lease-id", int64(leaseResp.ID)), zap.Int64("lease-timeout", leaseTimeout), zap.String("purpose", l.Purpose))
|
|
l.ID = leaseResp.ID
|
|
l.leaseTimeout = time.Duration(leaseTimeout) * time.Second
|
|
l.expireTime.Store(start.Add(time.Duration(leaseResp.TTL) * time.Second))
|
|
return nil
|
|
}
|
|
|
|
// Close releases the lease.
|
|
func (l *lease) Close() error {
|
|
// Reset expire time.
|
|
l.expireTime.Store(time.Time{})
|
|
// Try to revoke lease to make subsequent elections faster.
|
|
ctx, cancel := context.WithTimeout(l.client.Ctx(), revokeLeaseTimeout)
|
|
defer cancel()
|
|
l.lease.Revoke(ctx, l.ID)
|
|
return l.lease.Close()
|
|
}
|
|
|
|
// IsExpired checks if the lease is expired. If it returns true,
|
|
// current leader should step down and try to re-elect again.
|
|
func (l *lease) IsExpired() bool {
|
|
if l.expireTime.Load() == nil {
|
|
return false
|
|
}
|
|
return time.Now().After(l.expireTime.Load().(time.Time))
|
|
}
|
|
|
|
// KeepAlive auto renews the lease and update expireTime.
|
|
func (l *lease) KeepAlive(ctx context.Context) {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
timeCh := l.keepAliveWorker(ctx, l.leaseTimeout/3)
|
|
|
|
var maxExpire time.Time
|
|
for {
|
|
select {
|
|
case t := <-timeCh:
|
|
if t.After(maxExpire) {
|
|
maxExpire = t
|
|
l.expireTime.Store(t)
|
|
}
|
|
case <-time.After(l.leaseTimeout):
|
|
log.Info("lease timeout", zap.Time("expire", l.expireTime.Load().(time.Time)), zap.String("purpose", l.Purpose))
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Periodically call `lease.KeepAliveOnce` and post back latest received expire time into the channel.
|
|
func (l *lease) keepAliveWorker(ctx context.Context, interval time.Duration) <-chan time.Time {
|
|
ch := make(chan time.Time)
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
log.Info("start lease keep alive worker", zap.Duration("interval", interval), zap.String("purpose", l.Purpose))
|
|
defer log.Info("stop lease keep alive worker", zap.String("purpose", l.Purpose))
|
|
|
|
for {
|
|
go func() {
|
|
start := time.Now()
|
|
ctx1, cancel := context.WithTimeout(ctx, l.leaseTimeout)
|
|
defer cancel()
|
|
res, err := l.lease.KeepAliveOnce(ctx1, l.ID)
|
|
if err != nil {
|
|
log.Warn("lease keep alive failed", zap.Error(err), zap.String("purpose", l.Purpose))
|
|
return
|
|
}
|
|
if res.TTL > 0 {
|
|
expire := start.Add(time.Duration(res.TTL) * time.Second)
|
|
select {
|
|
case ch <- expire:
|
|
case <-ctx1.Done():
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ch
|
|
}
|