一个关心的对象很少变化,集群却一直在写——客户端断线重连时拿着一个又旧又没用的 resourceVersion,就会收到 410,只能把整个集合重新 list 一遍。bookmark 就是服务端定期告诉客户端“我现在到哪了”,这篇做一个能稳定复现 410 的对比实验。
持续在 noise 上写入,共 7000 次,87 秒;quiet 自己一次都没有变过
不带 allowWatchBookmarks 的 watcher:收到 0 个事件(它所知道的最后版本一直是 41857)
带 allowWatchBookmarks=true 的 watcher:收到 1 个事件,全是 BOOKMARK:
第 59.7 秒 BOOKMARK resourceVersion=46796
一个 BOOKMARK 事件的原文:{"type": "BOOKMARK", "object": {"kind": "ConfigMap", "apiVersion": "v1", "metadata": {"resourceVersion": "46796"}}}实验里的 quiet 从不被修改,集群却在持续写别的对象。不带 allowWatchBookmarks 的 watcher 一个事件都收不到,它能记住的最后版本一直是创建时的 41857——集群的版本早就走远了。带 allowWatchBookmarks=true 的 watcher 在第 59.7 秒收到一个 BOOKMARK 事件:里面只有 kind 和 metadata.resourceVersion(46796),没有对象内容。它的含义是“到 46796 为止你没有漏掉任何事件”。
写到第 7000 次(85 秒)时,从 quiet 最后一次变化的版本 rv=41857 重连:ERROR 410,too old resource version: 41857 (42466) 同一时刻,从最近收到的 BOOKMARK(rv=46796,是 27 秒前收到的)重连:成功,没有 410 没有 bookmark 的客户端遇到 410 只能重新 list:得到 resourceVersion=48998
写到第 7000 次(85 秒)时模拟断线重连。从 quiet 最后一次变化的版本 41857 重连得到 ERROR 410: too old resource version: 41857 (42466)(括号里是 apiserver 缓存里最老的版本),客户端只能重新 list 整个集合再 watch;从 27 秒前收到的 BOOKMARK 版本 46796 重连则直接成功。对只关心少数对象、又运行在写入频繁的集群里的客户端,bookmark 能避免反复全量 list。client-go 的 reflector 默认就会带上 allowWatchBookmarks=true。
要注意旧版本多久会失效不是固定值:同一个脚本的另一次运行里,写了 10000 次、105 秒之后从旧版本重连仍然成功(缓存窗口随写入速率变化),所以不要依赖“某个时间点一定 410”。这里的对比是用循环等到它第一次出现 410 为止。