定时、周期调度、等待时间的go语言标准实现

背景


- 在平台开发过程中,当有些执行完某些操作后,可能还需要等待直到某个条件满足的时候
- 比如在CMDB删除了一个项目,CMDB需要一直等待其他系统资源回收完,才能在自身彻底释放对应的资源, 那就需要一直在哪里等待
今天主要是一个关于定时轮训、等待的工具方法实现

完整代码

package wait

import (
    "errors"
    "math/rand"
    "time"
)

// Jitter 允许时间在一定范围内的波动
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
}

// resetReuseTimer 重置timer
func resetReuseTimer(t *time.Timer, d time.Duration, sawTimeout bool) *time.Timer {
    if t == nil {
        return time.NewTimer(d)
    }
    if !t.Stop() && !sawTimeout {
        <-t.C
    }
    t.Reset(d)
    return t
}

// HandlerCrash 控制失败异常
func HandlerCrash() {
    println("crash")
}

// JitterUtil 周期调度函数执行
func JitterUtil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) {
    var t *time.Timer
    var sawTimeout bool

    for {
        select {
        case <-stopCh:
            return
        default:
        }

        jitteredPeriod := period
        if jitterFactor > 0.0 {
                   // 使用jitterFactor可以生成一个范围内随机的time.Duration
            jitteredPeriod = Jitter(jitteredPeriod, jitterFactor)
        }

        if !sliding {
            t = resetReuseTimer(t, jitteredPeriod, sawTimeout)
        }

        func() {
            defer HandlerCrash()
            f()
        }()

        if sliding {
            t = resetReuseTimer(t, jitteredPeriod, sawTimeout)
        }

        select {
        case <-stopCh:
            return
        case <-t.C:
            sawTimeout = true
        }
    }
}

// Util 是封装的JitterUtil并提供了jitterFactor和sliding的默认值
func Util(f func(), period time.Duration, stopCh <-chan struct{}) {
    JitterUtil(f, period, 0.0, true, stopCh)
}

// WaiterFunc 等待某个函数执行
type WaitFunc func(done <-chan struct{}) <-chan struct{}

// ConditionFunc 等待函数条件呗满足
type ConditionFunc func() (done bool, err error)

var ErrWaitTimeout = errors.New("timed out waiting for the condition")

// WaitFor 执行某个函数活着某个条件呗满足
func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
    c := wait(done)
    for {
        _, open := <-c
        ok, err := fn()
        if err != nil {
            return err
        }
        if ok {
            return nil
        }
        if !open {
            break
        }
    }
    return ErrWaitTimeout
}

func poller(interval, timeout time.Duration) WaitFunc {
    return WaitFunc(func(done <-chan struct{}) <-chan struct{} {
        ch := make(chan struct{})

        go func() {
            defer close(ch)

            tick := time.NewTicker(interval)
            defer tick.Stop()

            var after <-chan time.Time
            if timeout != 0 {
                timer := time.NewTimer(timeout)
                after = timer.C
                defer timer.Stop()
            }

            for {
                select {
                case <-tick.C:
                    select {
                    case ch <- struct{}{}:
                    default:
                    }
                case <-after:
                    return
                case <-done:
                    return
                }
            }
        }()
        return ch
    })
}

// pollInternal
func pollInternal(wait WaitFunc, condition ConditionFunc) error {
    done := make(chan struct{})
    defer close(done)
    return WaitFor(wait, condition, done)
}

// Poll 提供对外统一的接口
func Poll(interval, timeout time.Duration, condition ConditionFunc) error {
    return pollInternal(poller(interval, timeout), condition)
}

代码都比较简单, 但在K8s里面很多资源更新方法里面,都使用了这些函数, 比如JOB的更新里面

// UpdateJobFunc updates the job object. It retries if there is a conflict, throw out error if
// there is any other errors. name is the job name, updateFn is the function updating the
// job object.
func UpdateJobFunc(c clientset.Interface, ns, name string, updateFn func(job *batch.Job)) {
    ExpectNoError(wait.Poll(time.Millisecond*500, time.Second*30, func() (bool, error) {
        job, err := GetJob(c, ns, name)
        if err != nil {
            return false, fmt.Errorf("failed to get pod %q: %v", name, err)
        }
        updateFn(job)
        _, err = UpdateJob(c, ns, job)
        if err == nil {
            Logf("Successfully updated job %q", name)
            return true, nil
        }
        if errors.IsConflict(err) {
            Logf("Conflicting update to job %q, re-get and re-update: %v", name, err)
            return false, nil
        }
        return false, fmt.Errorf("failed to update job %q: %v", name, err)
    }))
}

今天写的比较简单,都是晚上回来看的,白天还药做平台开发,今天就这样吧

你可能感兴趣的:(定时、周期调度、等待时间的go语言标准实现)