WorkQueue

工作队列(WorkQueue)在控制器编写中使用得非常频繁,通常使用informer.AddEventHandler函数监听资源对象的事件,并设置事件触发时的回调函数。在回调函数中,将资源对象的key放入WorkQueue,控制器会不断地从WorkQueue中获取数据并按照期望的状态处理。在处理的过程中如果发生任何错误,会重新将资源对象的key放入WorkQueue中并限速,等待控制器取出后再次处理,达到状态收敛的目的。

WorkQueue与普通的先进先出队列FIFO相比,多了很多特性,实现原理非常复杂,除了在Kubernetes中使用以外,也可以用于开发其他项目。下面列举一些它的特性。

·有序:这是一个FIFO的基本特性。

·并发性:支持多生产和多消费者。

·去重:同一个元素在相同时间内不会被重复处理,即使添加了多次,也只会被处理一次。

·监控:支持Prometheus的metric监控。

·信号通知:可以通过信号SIGTEARM通知队列不再存入新的元素,并且通知metric的goroutine退出。

·限速:支持队列的存入限速,并支持多种限速的算法。

WorkQueue中包含FIFO队列、限速队列、延迟队列,在自定义控制器的编写过程中使用的是限速队列,FIFO队列和延迟队列的实现非常有用,可以去源码路径k8s.io/client-go/util/workqueue/queue.go和k8s.io/client-go/util/workqueue/delaying_queue.go进行阅读。

限速队列的实现原理是利用延迟队列的特性延迟数据的插入时间从而限速。限速队列是基于延迟队列的接口封装并增加了AddRateLimited、Forget、NumRequeues方法。它的数据结构示例如下。

type RateLimiter interface {
    When(item interface{}) time.Duration
    Forget(item interface{})
    NumRequeues(item interface{}) int
}
type RateLimitingInterface interface {
    DelayingInterface
    AddRateLimited(item interface{})
    Forget(item interface{})
    NumRequeues(item interface{}) int
}

限速队列提供了3种限速算法。

·排队指数算法

·令牌桶算法

·计数器算法

3种算法在实际开发过程中会经常使用,下面进行详细介绍。

1.排队指数算法

当有相同的元素重复进入队列时,排队指数算法会将这个相同元素的排队数作为指数,这样速率会呈现指数级增长。Pod的重启策略如果设置为Always,那么重启的周期就是使用的排队指数算法。

需要注意在WorkQueue中排队指数算法是有一个限速周期的,这个周期是从AddRateLimited方法开始到Forget方法结束。

2.令牌桶算法

令牌桶算法比较著名,在很多开源软件的限速模块中都有使用,Nginx的限速模块、Envoy的Ratelimit和Sentinel的限速功能中都有它的身影。

WorkQueue的令牌桶算法使用Go语言的第三方库golang.org/x/time/rate实现。令牌桶算法会有一个存放token(令牌)的桶,通常是一个固定长度的队列,token会以用户自定义的速率填充到队列中,桶满则停止填充,但之后还会按固定的速率填充,每一个被限速的元素在重新放入WorkQueue时都会获得一个token,只有得到token的元素才可以进入WorkQueue中等待处理。没有获得token的元素则需要继续等待。

3.计数器算法

计数器算法是在定义的时间内允许通过的元素数量,例如30s内只允许通过100个元素,那么每一个元素的插入计数器就会加1,当计数器在定义的30s内增加到100个元素时,后面的元素就不再允许插入了。

WorkQueue在计数器算法之外增加了fastDely和slowDely字段,可以定义fast和slow两个插入速率(2个元素插入的间隔时间)。通过定义fast速率在一个限速周期内允许通过的元素数量maxFastAttempts,可以实现前maxFastAttempts个元素使用fast间隔插入,后面的元素使用slow间隔插入。

posted @ 2023-02-26 14:28  muzinan110  阅读(57)  评论(0编辑  收藏  举报