控制器为什么都用 informer 而不是轮询 API Server?本地缓存和 WATCH 是怎么配合的?lister 和直接 GET 差多少?resync 到底是什么,会不会增加 API Server 的压力?这篇写一个 60 行的 Go 程序,在客户端数每一个 HTTP 请求。
+ 0.0s ADD pre-0 + 0.0s ADD pre-1 + 0.0s ADD pre-2 + 0.1s HasSynced=true;客户端发出的请求: LIST=0 WATCH=1 GET单个=0 写=3 + 0.1s 第一个 WATCH 请求的查询参数:allowWatchBookmarks=true&resourceVersionMatch=NotOlderThan&sendInitialEvents=true&timeout=7m15s&timeoutSeconds=435&watch=true + 0.1s 从本地缓存(lister)读 1000 次:用时 60µs;请求数 LIST=0 WATCH=1 GET单个=0(没有增加) + 18.6s 直接向 API Server GET 100 次:用时 18.491s;GET单个=100
informer 启动后 0.1 秒 HasSynced=true:客户端只发出了 1 个 WATCH、0 个 LIST。这个 WATCH 的查询参数是 allowWatchBookmarks=true&resourceVersionMatch=NotOlderThan&sendInitialEvents=true&…&watch=true——新版本 client-go 用“带初始事件的 watch”(WatchList)把缓存灌满,先收到 3 个 ADD(pre-0、pre-1、pre-2)。之后从本地缓存(lister)读 1000 次只用了 60µs,请求数不变;直接向 API Server GET 100 次却用了 18.5 秒——client-go 默认限流约 5 QPS,量一大就成了瓶颈。控制器几乎都读缓存,原因就在这里。
+ 18.8s ADD live + 19.1s UPDATE live rv 142544 -> 142547 + 19.4s lister 里已经能读到 live:true + 19.4s DELETE live + 19.7s 事件回调累计:ADD=5 UPDATE(真实变化)=1 DELETE=1;resync 触发的 UpdateFunc(新旧 resourceVersion 相同)=4
创建、更新、删除一个 ConfigMap live:WATCH 把变化推给客户端,回调依次收到 ADD live、UPDATE live rv 142544 -> 142547(旧值和新值都带着 resourceVersion)、DELETE live;约 300 毫秒后 lister 里已经能读到 live(缓存先于回调更新,回调里读缓存就是新值)。回调累计 ADD=5(3 个 pre、live、命名空间自带的 kube-root-ca.crt)、真实 UPDATE 1 次、DELETE 1 次。
+ 44.7s 又过 25 秒(resync 间隔 10 秒,缓存里有 4 个对象):resync 触发的 UpdateFunc 累计 16 次(增加 12) + 44.7s 全程请求数:LIST=0 WATCH=1 GET单个=100 写=6
resync 间隔 10 秒:缓存里 4 个对象,即使没有变化,每 10 秒也会对每个对象调一次 UpdateFunc,且新旧 resourceVersion 相同——25 秒里增加 12 次(累计 16 次,正好 4 个对象 × 4 轮)。这不是 API Server 在推送,而是 informer 用本地缓存重放。整个过程请求总数:LIST=0、WATCH=1、GET=100(我们自己发的)、写 6 次,resync 没有产生任何请求。回调里用“新旧 resourceVersion 是否相同”区分 resync 与真实变化。