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,以提供定时执行函数的能力。

定期执行一个函数,永不停止

1
2
3
4
5
6
7
8
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)
}

该函数支持一个函数变量参数和一个间隔时间,该函数会定期执行,不会停止。

定期执行一个函数,可以接受停止信号

1
2
3
4
5
6
7
8
// 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并发控制协程

1
2
3
4
5
6
7
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并发控制协程。

定期检查先决条件

1
2
3
4
5
6
7
8
9
10
11
// 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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),
    }
}
posted @   人艰不拆_zmc  阅读(722)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示