Kubernetes定时任务的实现
1、概述
Kubernetes 的各个组件都有一定的定时任务,比如任务的定时轮询、高可用的实现、日志处理、缓存使用等,Kubernetes 中的定时任务都是通过 wait 包实现的。
注意,本文源码基于Kubernetes 1.21.5。
2、Golang 的定时任务
在讲 Kubernetes 的 wait 包之前,先看下 Golang 应该怎么实现一个定时任务。
Golang 中的 time 库包含了很多和时间相关的工具,其中包括了 Ticker 和 Timer 两个定时器。
-
Ticker 只要完成定义,从计时开始,不需要其他操作,每间隔固定时间便会触发。
-
而对于 Timer,在超时之后需要重置才能继续触发。
Golang定时任务详细语法细节请参考:Golang定时器——Timer 和 Ticker 。
3、Kubernetes 的 wait 库
3.1 常用 API
wait 库中实现了多种常用的 API,以提供定时执行函数的能力。
定期执行一个函数,永不停止
var NeverStop <-chan struct{} = make(chan struct{}) // Forever calls f every period for ever. // // Forever is syntactic sugar on top of Until. func Forever(f func(), period time.Duration) { Until(f, period, NeverStop) }
该函数支持一个函数变量参数和一个间隔时间,该函数会定期执行,不会停止。
定期执行一个函数,可以接受停止信号
// Until loops until stop channel is closed, running f every period. // // Until is syntactic sugar on top of JitterUntil with zero jitter factor and // with sliding = true (which means the timer for period starts after the f // completes). func Until(f func(), period time.Duration, stopCh <-chan struct{}) { JitterUntil(f, period, 0.0, true, stopCh) }
该函数支持提供一个函数变量参数、间隔时间和发生 stop 信号的 channel,和 Forever 类似,不过可以通过向 stopCh 发布消息来停止。
定期执行一个函数,通过context并发控制协程
func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { JitterUntilWithContext(ctx, f, period, 0.0, true) } func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) { JitterUntil(func() { f(ctx) }, period, jitterFactor, sliding, ctx.Done()) }
该函数支持提供一个context、函数变量参数和间隔时间,和 Util 类似,只不过通过context并发控制协程。
定期检查先决条件
// Poll tries a condition func until it returns true, an error, or the timeout // is reached. // // Poll always waits the interval before the run of 'condition'. // 'condition' will always be invoked at least once. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // If you want to Poll something forever, see PollInfinite. func Poll(interval, timeout time.Duration, condition ConditionFunc) error
该函数将以 interval 为间隔,定期检查 condition 是否检查成功。
3.2 核心代码
wait 包的定时任务 API 是基于 JitterUntil
实现的。
JitterUntil
的 5 个参数:
参数名 | 类型 | 作用 |
---|---|---|
f | func() |
需要定时执行的逻辑函数 |
period | time.Duration |
定时任务的时间间隔 |
jitterFactor | float64 |
如果大于 0.0,间隔时间变为 duration 到 duration + maxFactor * duration 的随机值(其中如果jitterFactor值大于0,那么maxFactor=jitterFactor)。 |
sliding | bool |
逻辑的执行时间是否不算入间隔时间,如果 sliding 为 true,则在 f() 运行之后计算周期。如果为 false,那么 period 包含 f() 的执行时间。 |
stopCh | <-chan struct{} |
接受停止信号的 channel |
func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) { BackoffUntil(f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding, stopCh) } //周期性执行函数f func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) { var t clock.Timer for { //通道关闭后,退出BackoffUtil函数(关闭通道后读取对应类型零值) select { case <-stopCh: return default: } //实例化(NewTimer)或复用(Reset)原生定时器time.Timer进行周期性任务 if !sliding { t = backoff.Backoff() } func() { defer runtime.HandleCrash() f() }() //实例化(NewTimer)或复用(Reset)原生定时器time.Timer进行周期性任务 if sliding { t = backoff.Backoff() } //每隔getNextBackoff()时间间隔触发定时器 select { case <-stopCh: return case <-t.C(): } } } type jitteredBackoffManagerImpl struct { clock clock.Clock duration time.Duration jitter float64 backoffTimer clock.Timer } // NewJitteredBackoffManager returns a BackoffManager that backoffs with given duration plus given jitter. If the jitter // is negative, backoff will not be jittered. func NewJitteredBackoffManager(duration time.Duration, jitter float64, c clock.Clock) BackoffManager { return &jitteredBackoffManagerImpl{ clock: c, duration: duration, //最少要延迟多久 jitter: jitter, //给定抖动范围 backoffTimer: nil, //退避计时器,一开始不需要初始化backoffTimer,会由使用者调用Backoff方法时由计算后再赋值 } } func (j *jitteredBackoffManagerImpl) Backoff() clock.Timer { backoff := j.getNextBackoff() //实例化原生定时器time.Timer进行周期性任务 if j.backoffTimer == nil { j.backoffTimer = j.clock.NewTimer(backoff) } else { //复用timer j.backoffTimer.Reset(backoff) } return j.backoffTimer } //计算延迟时间 func (j *jitteredBackoffManagerImpl) getNextBackoff() time.Duration { jitteredPeriod := j.duration if j.jitter > 0.0 { jitteredPeriod = Jitter(j.duration, j.jitter) } return jitteredPeriod } //计算抖动延迟时间 func Jitter(duration time.Duration, maxFactor float64) time.Duration { if maxFactor <= 0.0 { maxFactor = 1.0 } //抖动延迟时间 = 基础的延时时间 + 随机时间*抖动因子*基础的延时时间 wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) return wait } //clock包进行对time.Timer做了一层封装实现,本文只列一下time.Timer实例化方法 type RealClock struct{} // NewTimer returns a new Timer. func (RealClock) NewTimer(d time.Duration) Timer { return &realTimer{ timer: time.NewTimer(d), } }