bigywater.tech
Kubernetes · 96

workqueue:限速重试和去重

控制器里反复入队同一个对象会不会处理很多次?两个 worker 会不会同时处理同一个对象?失败了多久后重试,一直失败会怎样?这篇用 workqueue 包写 60 行代码,把去重、串行、退避的数字逐个打出来。

Len()=2
对同一个 key Add 100 次再加一个别的 key 后的队列长度
同 key 最多 1
4 个 worker 并发处理时,同一个 key 同时被处理的最大个数
5ms × 2ⁿ
默认控制器限速器对同一个 key 失败重试的等待时间(12 次到 10.24s)

去重与 dirty

实测 · 本机执行输出
  入队 101 次(100 次同一个 key + 1 个别的 key)后,队列长度 Len()=2
  依次取出:default/web, default/db;取出后队列长度 Len()=0
  default/web 还在处理中(没有 Done)时再 Add:Len()=0(不会马上入队,先记为 dirty)
  default/web 调用 Done 之后:Len()=1(之前的 Add 此刻才进入队列)

对同一个 key default/web 连续 Add 100 次,再 Add 一个 default/db:队列长度 Len()=2,重复的被合并;取出后 Len()=0。default/web 还在处理(没有 Done)时再 Add:Len() 仍是 0,先记为 dirty;调用 Done 之后 Len()=1,之前的 Add 此刻才进入队列。所以控制器队列里放的是 key(namespace/name),不是对象,处理时再去读缓存拿最新值。

同一个 key 串行

实测 · 本机执行输出
  4 个 worker 在 0.5 秒里不停地 Add "hot-key" 和另外 3 个 key:共处理 367 次;同一个 key 同时被处理的最大个数=1;所有 key 同时被处理的最大个数=4

4 个 worker 在 0.5 秒里不停地 Add "hot-key" 和另外 3 个 key:共处理 367 次,同一个 key 同时被处理的最大个数是 1,不同 key 同时被处理的最大个数是 4。控制器因此可以放心写 reconcile:同一个对象不会并发被处理,不同对象可以并行。

退避

实测 · 本机执行输出
  DefaultControllerRateLimiter 对同一个 key 连续失败 12 次的等待时间:[5ms 10ms 20ms 40ms 80ms 160ms 320ms 640ms 1.28s 2.56s 5.12s 10.24s];NumRequeues=12
  Forget 之后再失败一次的等待时间:5ms;NumRequeues=1
  单纯的指数退避(5ms 起,上限 1000s):[第10次=2.56s 第20次=16m40s 第30次=16m40s]
  不同 key 互不影响:5 个新 key 的第一次等待时间:[5ms 5ms 5ms 5ms 5ms]

DefaultControllerRateLimiter 对同一个 key 连续失败 12 次的等待时间是 5ms、10ms、20ms、40ms、80ms、160ms、320ms、640ms、1.28s、2.56s、5.12s、10.24s(每次翻倍),NumRequeues=12;成功后调 Forget 把计数清零,下一次失败又回到 5ms。单纯的指数退避(5ms 起、上限 1000s):第 10 次 2.56s,第 20 次和第 30 次都是 16m40s,已经封顶。不同 key 各自计数,互不影响。

延迟入队

实测 · 本机执行输出
  基础延迟 50ms 的限速队列:第 1/2/3 次 AddRateLimited 后 Get 阻塞了 50ms / 102ms / 202ms;NumRequeues=3
  AddAfter(200ms):Get 阻塞了 200ms 才拿到 later;Forget 之后 NumRequeues=0

基础延迟 50ms 的限速队列:对同一个 key 第 1/2/3 次 AddRateLimited 后 Get 分别阻塞了 50ms / 102ms / 202ms,NumRequeues=3;AddAfter(200ms) 精确延迟 200ms;Forget 之后 NumRequeues=0。

分步演示

workqueue
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。本机 Go 1.27.1 + k8s.io/client-go v0.36.1 的 workqueue 包(Typed 版本),程序在 tools/articles/ktmp/workq/main.go,不需要 Kubernetes 集群;时间由 time.Since 测得,数字略有抖动(例如 102ms / 202ms 会在 ±3ms 范围内);并发实验的 367 次处理数在多次运行中会有变化;每种情形各一次
    • 没有验证:DefaultControllerRateLimiter 里的令牌桶(10 qps、100 burst)与指数退避取最大值的行为;ItemFastSlowRateLimiter;MaxOf 组合限速器;队列的 metrics(depth、adds、latency);ShutDownWithDrain;在真实控制器里 informer 事件回调如何入队
    京ICP备2026067502号-1京公网安备11010502063487号