client-go和golang源码中的技巧( 四 )

<-chan struct{}) func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) Until函数每period会调度f函数 , 如果stopCh中有停止信号 , 则退出 。 当程序运行时间超过period时 , 也不会退出调度循环 , 该特性和Ticker相同 。 底层使用Timer实现 。
Until和NonSlidingUntil为一对 , UntilWithContext和NonSlidingUntilWithContext为一对 , 区别只是定时器启动时间点不同 , 可以简单用下图表示:
这两种(带“NonSliding”前缀的)函数在处理正常程序时没有什么区别 , 但在一些场景下会有不同的地方 。 下面例子中使用wait.NonSlidingUntil处理的程序中sleep了2s , 这可以表示程序因为某种原因导致超出正常处理时间 。 此时可以看到结果中的“num 1”和“num 2”是同时调用的
func main(){first := truenum := 0stopCh:=make(chan struct{} )go func() {time.Sleep(time.Second*10)close(stopCh)fmt.Println("done")}()go wait.NonSlidingUntil(func(){if true == first{time.Sleep(time.Second*2)first=false}num = num + 1fmt.Println("num:",num,"time",time.Now())},time.Second*1,stopCh)time.Sleep(time.Second*100)}结果:num: 1 time 2019-07-04 21:05:59.5298524 +0800 CST m=+26.277103101num: 2 time 2019-07-04 21:05:59.554999 +0800 CST m=+26.302249701num: 3 time 2019-07-04 21:06:00.5559679 +0800 CST m=+27.303218601num: 4 time 2019-07-04 21:06:01.5566608 +0800 CST m=+28.303911501将上述程序的wait.NonSlidingUntil替换为wait.Until , 得到如下结果 , 可以看到首次(异常)和第二次(正常)的间隔正好是wait.Until中设置的调度周期 , 即1s 。
ps:大部分场景下两者使用上并没有什么不同 , 毕竟正常情况下程序运行时间必然小于程序调度周期 。 如果需要在程序处理延时的情况下尽快进行下一次调度 , 则选择带”NonSliding“前缀的函数
结果:num: 1 time 2019-07-04 21:09:14.9643889 +0800 CST m=+2.010865201num: 2 time 2019-07-04 21:09:15.9935285 +0800 CST m=+3.040004801num: 3 time 2019-07-04 21:09:16.9956846 +0800 CST m=+4.042160901

  • func Forever(f func(), period time.Duration)
该函数比较简单 , 就是取消了用于控制Until停止的stopCh 。 以永远不停止的方式周期性执行f函数
  • func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error
ExponentialBackoff可以实现在函数执行错误后实现以指数退避方式的延时重试 。 ExponentialBackoff内部使用的是time.Sleep
ExponentialBackoff的首个入参Backoff如下:
  • Duration:表示初始的延时时间
  • Factor:指数退避的因子
  • Jitter:可以看作是偏差因子 , 该值越大 , 每次重试的延时的可选区间越大
  • Steps:指数退避的步数 , 可以看作程序的最大重试次数
  • Cap:用于在Factor非0时限制最大延时时间和最大重试次数 , 为0表示不限制最大延时时间
type Backoff struct {// The initial duration.Duration time.Duration// Duration is multiplied by factor each iteration. Must be greater// than or equal to zero.Factor float64// The amount of jitter applied each iteration. Jitter is applied after// cap.Jitter float64// The number of steps before duration stops changing. If zero, initial// duration is always used. Used for exponential backoff in combination// with Factor.Steps int// The returned duration will never be greater than cap *before* jitter// is applied. The actual maximum cap is `cap * (1.0 + jitter)`.Cap time.Duration}第二个参数ConditionFunc表示运行的函数 , 返回的bool值表示该函数是否执行成功 , 如果执行成功则会退出指数退避
type ConditionFunc func() (done bool, err error)下面做几组测试:
=> 当Factor和Jitter都为0时 , 可以看到调度周期是相同的 , 即Duration的值(1s) 。
import ("fmt""k8s.io/apimachinery/pkg/util/wait""time")func main(){var DefaultRetry = wait.Backoff{Steps:5,Duration: 1 * time.Second,Factor:0,Jitter:0,}fmt.Println(wait.ExponentialBackoff(DefaultRetry,func() (bool, error){fmt.Println(time.Now())return false,nil}))}结果:2019-07-05 10:17:33.9610108 +0800 CST m=+0.0798311012019-07-05 10:17:34.961132 +0800 CST m=+1.0799523012019-07-05 10:17:35.961512 +0800 CST m=+2.0803323012019-07-05 10:17:36.9625144 +0800 CST m=+3.0813347012019-07-05 10:17:37.9636334 +0800 CST m=+4.082453701timed out waiting for the condition=> 先看Jitter对duration的影响 , Jitter(duration, b.Jitter)的计算方式如下 , 如果入参的Factor为0 , 而Jitter非0 , 则将Factor调整为1 。 rand.Float64()为[0.0,1.0)的伪随机数 。
将Jitter调整为0.5 , 根据下面计算方式预期duration为[1s,1.5s) 。 运行程序得出如下结果 , 观察可以发现 , duration大概是1.4s
if maxFactor <= 0.0 {maxFactor = 1.0}wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration))