client-go之tools/leaderelection包源码分析

client-go之tools/leaderelection包源码分析

tools/leaderelection包

用于需要进行leader控制的controller等.

healthzadaptor.go

  • 函数
    // 创建一个基本的 healthz 适配器来监控领导者选举。 timeout 确定超过租约到期允许超时的时间。租约到期后的超时时间内的检查仍将正常返回。
    func NewLeaderHealthzAdaptor(timeout time.Duration) *HealthzAdaptor {
      result := &HealthzAdaptor{
      	timeout: timeout,
      }
      return result
    }
    
  • 结构体
    // 将 healthz 端点与 LeaderElection 对象相关联。它有助于处理在 LeaderElection 之前设置的 healthz 端点。
    type HealthzAdaptor struct {
      pointerLock sync.Mutex
      le          *LeaderElector
      timeout     time.Duration
    }
    
    // 返回健康检查对象的名称
    func (l *HealthzAdaptor) Name() string {
      return "leaderElection"
    }
    
    // 检查由 healthz 端点处理程序调用。如果我们拥有租约但在le.config.LeaseDuration+maxTolerableExpiredLease内无法续订,它会失败(返回错误)。
    func (l *HealthzAdaptor) Check(req *http.Request) error {
      l.pointerLock.Lock()
      defer l.pointerLock.Unlock()
      if l.le == nil {
      	return nil
      }
      return l.le.Check(l.timeout)
    }
    
    // 将领导选举对象绑定到 HealthzAdaptor
    func (l *HealthzAdaptor) SetLeaderElection(le *LeaderElector) {
      l.pointerLock.Lock()
      defer l.pointerLock.Unlock()
      l.le = le
    }
    
    

metrics.go

  • 接口
    // 该文件提供了用于设置指标提供者(例如 prometheus)的抽象。
    type leaderMetricsAdapter interface {
      leaderOn(name string)
      leaderOff(name string)
    }
    
    // GaugeMetric 表示可以任意上下浮动的单个数值。
    type SwitchMetric interface {
      On(name string)
      Off(name string)
    }
    
    // 使用领导人选举生成各种指标
    type MetricsProvider interface {
      NewLeaderMetric() SwitchMetric
    }
    
  • 结构体
    // 调用者在设置任何指标之前,应该使用lock锁定。
    type defaultLeaderMetrics struct {
      // leader 的值表示当前进程是否是名称租约的所有者
      leader SwitchMetric
    }
    
    func (m *defaultLeaderMetrics) leaderOn(name string) {
      if m == nil {
      	return
      }
      m.leader.On(name)
    }
    
    func (m *defaultLeaderMetrics) leaderOff(name string) {
      if m == nil {
      	return
      }
      m.leader.Off(name)
    }
    
    // leaderMetrics的工厂模式
    type leaderMetricsFactory struct {
      metricsProvider MetricsProvider
    
      onlyOnce sync.Once
    }
    // 注意这里只能设置一次
    func (f *leaderMetricsFactory) setProvider(mp MetricsProvider) {
      f.onlyOnce.Do(func() {
      	f.metricsProvider = mp
      })
    }
    
    // 获取leaderMetrics
    func (f *leaderMetricsFactory) newLeaderMetrics() leaderMetricsAdapter {
      mp := f.metricsProvider
      if mp == (noopMetricsProvider{}) {
      	return noMetrics{}
      }
      return &defaultLeaderMetrics{
      	leader: mp.NewLeaderMetric(),
      }
    }
    

leaderelection.go

  • 结构体
    // LeaderElector使用的各种配置
    type LeaderElectionConfig struct {
      // 客户端
      Lock rl.Interface
    
      // 获取lease的周期
      LeaseDuration time.Duration
      // 续租周期
      RenewDeadline time.Duration
      // 重试周期
      RetryPeriod time.Duration
    
      // 需要用户配置的回调函数
      Callbacks LeaderCallbacks
    
      // 看门狗  用来健康检查
      WatchDog *HealthzAdaptor
    
      // 判断在cancel的时候如果当前是leader是否需要释放
      ReleaseOnCancel bool
    
      Name string
    }
    
    // 配置leaderElector的回调函数
    type LeaderCallbacks struct {
      // 成功选举调用
      OnStartedLeading func(context.Context)
      // 停止调用
      OnStoppedLeading func()
      // 产生新的leader调用
      OnNewLeader func(identity string)
    }
    
    type LeaderElector struct {
      // 用于保存当前应用的一些配置 包括该应用的id等等
      config LeaderElectionConfig
      // 远程获取的资源 (不一定自己是leader) 所有想竞争此资源的应用获取的是同一份
      observedRecord rl.LeaderElectionRecord
      // observedRecord 的原始二进制数据
      observedRawRecord []byte
      // 获取的时间
      observedTime   time.Time
      reportedLeader string
      clock clock.Clock
      metrics leaderMetricsAdapter
      name string
    }
    
    // 在mgr开启时调用
    func (le *LeaderElector) Run(ctx context.Context) {
      defer func() {
          runtime.HandleCrash()
          le.config.Callbacks.OnStoppedLeading()
      }()
      // 如果获取失败 那就是ctx signalled done
      // 不然即使失败, 该client也会一直去尝试获得leader位置
      if !le.acquire(ctx) {
          return // ctx signalled done
      }
      // 如果获得leadership 以goroutine和回调的形式启动用户自己的逻辑方法OnStartedLeading
      ctx, cancel := context.WithCancel(ctx)
      defer cancel()
      go le.config.Callbacks.OnStartedLeading(ctx)
      // 一直去续约 这里也是一个循环操作
      // 如果失去了leadership 该方法才会返回
      // 该方法返回 整个Run方法就返回了
      le.renew(ctx)
    }
    
    // 新产生leader后的传播,可以在OnNewLeader自定义
    func (le *LeaderElector) maybeReportTransition() {
      // 如果没有变化 则不需要更新
      if le.observedRecord.HolderIdentity == le.reportedLeader {
          return
      }
      // 更新reportedLeader为最新的leader的id
      le.reportedLeader = le.observedRecord.HolderIdentity
      if le.config.Callbacks.OnNewLeader != nil {
          // 调用当前应用的回调函数OnNewLeader报告新的leader产生
          go le.config.Callbacks.OnNewLeader(le.reportedLeader)
      }
    }
    
    // 一旦获得leadership 立马返回true
    // 返回false的唯一情况是ctx signals done
    func (le *LeaderElector) acquire(ctx context.Context) bool {
      ctx, cancel := context.WithCancel(ctx)
      defer cancel()
      succeeded := false
      desc := le.config.Lock.Describe()
      klog.Infof("attempting to acquire leader lease  %v...", desc)
      wait.JitterUntil(func() {
          // 尝试获得或者更新资源
          succeeded = le.tryAcquireOrRenew()
          // 有可能会产生新的leader
          // 所以调用maybeReportTransition检查是否需要广播新产生的leader
          le.maybeReportTransition()
          if !succeeded {
              // 如果获得leadership失败 则返回后继续竞争
              klog.V(4).Infof("failed to acquire lease %v", desc)
              return
          }
          // 自己成为leader
          // 可以调用cancel方法退出JitterUntil进而从acquire中返回
          le.config.Lock.RecordEvent("became leader")
          le.metrics.leaderOn(le.config.Name)
          klog.Infof("successfully acquired lease %v", desc)
          cancel()
      }, le.config.RetryPeriod, JitterFactor, true, ctx.Done())
      return succeeded
    }
    
    // RenewDeadline=10s RetryPeriod=2s
    // 续租
    func (le *LeaderElector) renew(ctx context.Context) {
      ctx, cancel := context.WithCancel(ctx)
      defer cancel()
      // 每隔RetryPeriod会调用 除非cancel()方法被调用才会退出
      wait.Until(func() {
          timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
          defer timeoutCancel()
          // 每隔2ms调用该方法直到该方法返回true为止
          // 如果超时了也会退出该方法 并且err中有错误信息
          err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
              done := make(chan bool, 1)
              go func() {
                  defer close(done)
                  done <- le.tryAcquireOrRenew()
              }()
    
              select {
              case <-timeoutCtx.Done():
                  return false, fmt.Errorf("failed to tryAcquireOrRenew %s", timeoutCtx.Err())
              case result := <-done:
                  return result, nil
              }
          }, timeoutCtx.Done())
    
          // 有可能会产生新的leader 如果有会广播新产生的leader
          le.maybeReportTransition()
          desc := le.config.Lock.Describe()
          if err == nil {
              // 如果err == nil, 表明上面PollImmediateUntil中返回true了 续约成功 依然处于leader位置
              // 返回后 继续运行wait.Until的逻辑
              klog.V(5).Infof("successfully renewed lease %v", desc)
              return
          }
          // err != nil 表明超时了 试的总时间超过了RenewDeadline 失去了leader位置 续约失败
          // 调用cancel方法退出wait.Until
          le.config.Lock.RecordEvent("stopped leading")
          le.metrics.leaderOff(le.config.Name)
          klog.Infof("failed to renew lease %v: %v", desc, err)
          cancel()
      }, le.config.RetryPeriod, ctx.Done())
    
      // if we hold the lease, give it up
      if le.config.ReleaseOnCancel {
          le.release()
      }
    }
    
    // 竞争或者更新leadership
    // 成功返回true 失败返回false
    func (le *LeaderElector) tryAcquireOrRenew() bool {
      now := metav1.Now()
      leaderElectionRecord := rl.LeaderElectionRecord{
          HolderIdentity:       le.config.Lock.Identity(),
          LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
          RenewTime:            now,
          AcquireTime:          now,
      }
    
      // 1. obtain or create the ElectionRecord
      // 从client端中获得ElectionRecord
      oldLeaderElectionRecord, err := le.config.Lock.Get()
      if err != nil {
          if !errors.IsNotFound(err) {
              // 失败直接退出
              klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
              return false
          }
          // 因为没有获取到, 因此创建一个新的进去
          if err = le.config.Lock.Create(leaderElectionRecord); err != nil {
              klog.Errorf("error initially creating leader election record: %v", err)
              return false
          }
          // 然后设置observedRecord为刚刚加入进去的leaderElectionRecord
          le.observedRecord = leaderElectionRecord
          le.observedTime = le.clock.Now()
          return true
      }
    
      // 2. Record obtained, check the Identity & Time
      // 从远端获取到record(资源)成功存到oldLeaderElectionRecord
      // 如果oldLeaderElectionRecord与observedRecord不相同 更新observedRecord
      // 因为observedRecord代表是从远端存在Record
    
      // 需要注意的是每个client都在竞争leadership, 而leader一直在续约, leader会更新它的RenewTime字段
      // 所以一旦leader续约成功 每个non-leader候选者都需要更新其observedTime和observedRecord
      if !reflect.DeepEqual(le.observedRecord, *oldLeaderElectionRecord) {
          le.observedRecord = *oldLeaderElectionRecord
          le.observedTime = le.clock.Now()
      }
      // 如果leader已经被占有并且不是当前自己这个应用, 而且时间还没有到期
      // 那就直接返回false, 因为已经无法抢占 时间没有过期
      if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
          le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
          !le.IsLeader() {
          klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
          return false
      }
    
      // 3. We're going to try to update. The leaderElectionRecord is set to it's default
      // here. Let's correct it before updating.
      if le.IsLeader() {
          // 如果当前服务就是以前的占有者
          leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
          leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
      } else {
          // 如果当前服务不是以前的占有者 LeaderTransitions加1
          leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
      }
    
      // update the lock itself
      // 当前client占有该资源 成为leader
      if err = le.config.Lock.Update(leaderElectionRecord); err != nil {
          klog.Errorf("Failed to update lock: %v", err)
          return false
      }
      le.observedRecord = leaderElectionRecord
      le.observedTime = le.clock.Now()
      return true
    }
    
    func (le *LeaderElector) GetLeader() string {
      return le.observedRecord.HolderIdentity
    }
    
    // 很明显判断当前进程是不是leader只需要判断config中的id和observedRecord中的id是不是一致即可.
    func (le *LeaderElector) IsLeader() bool {
      return le.observedRecord.HolderIdentity == le.config.Lock.Identity()
    }
    
    
    
  • 函数
    // RunOrDie 使用提供的配置启动客户端LeaderElector,如果配置验证失败,则会出现异常。 RunOrDie 阻塞,直到 ctx 停止领导者选举循环或它已停止持有领导者租约
    func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
      le, err := NewLeaderElector(lec)
      if err != nil {
      	panic(err)
      }
      if lec.WatchDog != nil {
      	lec.WatchDog.SetLeaderElection(le)
      }
      le.Run(ctx)
    }
    
    

resourcelock包

  • interface.go 定义了各种interface和New函数

    // 提供了生成各个子类的方法
    func New(lockType string, ns string, name string, coreClient corev1.CoreV1Interface, coordinationClient coordinationv1.CoordinationV1Interface,    rlc ResourceLockConfig) (Interface, error) {
      switch lockType {
      case EndpointsResourceLock:
          return &EndpointsLock{
              EndpointsMeta: metav1.ObjectMeta{
                  Namespace: ns,
                  Name:      name,
              },
              Client:     coreClient,
              LockConfig: rlc,
          }, nil
      ...
      default:
          return nil, fmt.Errorf("Invalid lock-type %s", lockType)
      }
    
    
    
    
  • EndpointLock.go 它有三个实现类, 分别为EndpointLock, ConfigMapLock和LeaseLock分别可以操作k8s中的endpoint, configmap和lease. 也就是提供了这三种资源类型.

  // 这里只分析endpointslock.go,其他两个类似
  type EndpointsLock struct {
    // 必须包括namespace和name
    EndpointsMeta metav1.ObjectMeta
    // 访问api-server的客户端
    Client        corev1client.EndpointsGetter
    // 该EndpointsLock的分布式唯一身份id
    LockConfig    ResourceLockConfig
    // 当前操作的endpoint
    e       *v1.Endpoints
  }


  type ResourceLockConfig struct {
  // 分布式唯一id
    Identity string
    EventRecorder EventRecorder
  }

  // Create, Update, Get方法都是利用client去访问k8s的api-server. 通过这里可以看得更明白, 就是操作EndpointsLock.e.Annotations中的
  // control-plane.alpha.kubernetes.io/leader.
  // 例如 GET
  func (el *EndpointsLock) Get() (*LeaderElectionRecord, error) {
    var record LeaderElectionRecord
    var err error
    // client从etcd中获取resource
    el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(el.EndpointsMeta.Name, metav1.GetOptions{})
    if err != nil {
        return nil, err
    }
    if el.e.Annotations == nil {
        el.e.Annotations = make(map[string]string)
    }
    // 从annotations中获取对应key的value
    if recordBytes, found := el.e.Annotations[LeaderElectionRecordAnnotationKey]; found {
        if err := json.Unmarshal([]byte(recordBytes), &record); err != nil {
            return nil, err
        }
    }
    return &record, nil
  }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
这段代码是一个Shell脚本,含了一些变量的定义和注释。 具体解释如下: 1. `##! @TODO: get admin bound from openmap` - 这是一个注释,表示要从OpenMap获取管理员边界信息。 3. `##! @AUTHOR: kanchangcheng@baidu.com` - 这是一个注释,表示作者是kanchangcheng@baidu.com。 5. `##! @DATE: 20180309 14:31:29` - 这是一个注释,表示脚本的最后修改日期和时间。 7. `##! @VERSION: 1.0` - 这是一个注释,表示脚本的版本号。 6-18. `php=~/odp/php/bin/php`, `hadoop_afs=~/tools/hadoop-afs/hadoop-client/hadoop/bin/hadoop`, `hadoop=/home/map/tools/hadoop-afs/hadoop-client/hadoop/bin/hadoop`, `hdfs_block_path="afs://kunpeng.afs.baidu.com:9902/user/lbs-huiyan/warehouse/huiyan.db/map_huiyan_block_info_mid/"`, `hdfs_admin_path="afs://kunpeng.afs.baidu.com:9902/user/lbs-huiyan/warehouse/huiyan.db/map_huiyan_admin_info/"`, `queryengine=/home/map/tools/queryengine-client-2.1.7-online/queryengine/bin/queryengine`, `datax=/home/map/tools/datax_huiyan_v2/bin/datax.py`, `python=~/tools/py2714/phq` - 这些行定义了一些变量,并给它们赋予了特定的值。 19. `if [ "$1" ];then` - 这一行是一个条件语句,判断脚本是否接收到了一个参数。 综上所述,这段代码的作用是定义了一些变量,并给它们赋予了特定的值。其中还含了一些注释,用于说明脚本的功能、作者、版本和最后修改日期。最后还有一个条件语句,用于判断脚本是否接收到了一个参数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值