apollo icon indicating copy to clipboard operation
apollo copied to clipboard

灰度发布在k8s上丢失的问题

Open lzbjut opened this issue 1 month ago • 6 comments

我这边在实际场景使用时发现在k8s场景下多次灰度发布会有部分收不到最新配置,只能靠5min一次的兜底刷新获取最新配置,查看代码发现是灰度版本发布主要靠GrayReleaseRulesHolder来确定获取哪条release 我注意到这个GrayReleaseRulesHolder是靠ReleaseMessage扫描来获得通知的,这个通知靠定时任务导致各pod的进度不一,在config缓存的类ConfigServiceWithCache中我有看到靠client端传来的notificationId对比来确定是否当前pod的配置是最新的,不是则传统缓存去读库 为何GrayReleaseRulesHolder同样会影响到获取哪个配置,没使用类似的设计来保证获取的是最新的配置呢?这样不是会导致灰度发布后有部分节点无法获取最新配置么

lzbjut avatar Nov 07 '25 04:11 lzbjut

你说的现象确实存在,根本原因在于 GrayReleaseRulesHolder 的更新机制和 ConfigServiceWithCache 不一样,导致在 K8s 多副本场景下灰度规则的同步存在延迟和不一致。

GrayReleaseRulesHolder 依赖 ReleaseMessage 通知和定时任务(比如每 5 分钟全量扫描)来更新灰度规则缓存。由于 ReleaseMessage 的消费和定时任务在每个 pod 上是异步的,所以不同 pod 上的 GrayReleaseRulesHolder 状态可能不同步,部分节点就会短时间内拿不到最新的灰度规则,只有等到下次定时扫描或消息消费后才会更新,这就是你看到的“只能靠5min一次的兜底刷新获取最新配置”的原因相关代码实现

而 ConfigServiceWithCache 是通过客户端带来的 notificationId 判断本地缓存是否过期,如果发现客户端的 notificationId 比本地新,会主动失效缓存并从数据库拉取最新配置,这样可以保证只要客户端拿到新 notificationId,服务端就能及时返回最新配置机制细节

GrayReleaseRulesHolder 没有用 notificationId 机制,主要是因为灰度规则的变更频率远低于配置发布,且规则本身是服务端行为,客户端无法感知规则的 notificationId。现有设计下,确实会导致灰度发布后部分节点短时间内无法获取最新规则,属于 Apollo 的已知一致性弱点官方说明

如果你希望缩短延迟,可以调小灰度规则扫描间隔(grayReleaseRuleScanInterval),但这会增加数据库压力。或者关注 Apollo 2.5.0+ 的增量同步能力(config-service.incremental.change.enabled),虽然它主要优化配置同步,但也能一定程度提升多副本环境下的同步及时性增量同步 PR 说明

总之,灰度规则同步目前没有像配置缓存那样的强一致性机制,K8s 多副本下短暂不一致是设计上的 trade-off。如果业务对一致性要求极高,建议灰度发布窗口适当拉长,或结合实例重启等手段规避极端情况。

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Nov 07 '25 04:11 dosubot[bot]

但是灰度发布和灰度规则变更都会引发这个问题,我复现下来看触发几率不小

lzbjut avatar Nov 07 '25 04:11 lzbjut

为何不能在GrayReleaseRulesHolder中引入类似的机制,如果client端传过来的notificationid比缓存中的更大则穿透缓存

lzbjut avatar Nov 07 '25 04:11 lzbjut

GrayReleaseRulesHolder 目前没有采用类似 ConfigServiceWithCache 的 notificationId 机制,主要有以下几个技术原因:

  1. 灰度规则(GrayReleaseRule)本身没有像配置发布那样的 notificationId 体系。ConfigServiceWithCache 能用 notificationId 判断缓存是否过期,是因为每次配置发布都会生成唯一的 ReleaseMessage(带有递增 id),客户端和服务端都能感知和对比。而灰度规则的变更(比如新建、修改、删除规则)并不会生成独立的 notificationId,也没有和 ReleaseMessage 一一对应的机制,导致客户端无法感知灰度规则的“最新状态”编号,服务端也无法用 client 传来的 notificationId 做有效对比和穿透缓存代码实现

  2. GrayReleaseRulesHolder 的缓存更新完全依赖于服务端的 ReleaseMessage 通知和定时全量扫描,和客户端请求无关。即使客户端传递 notificationId,GrayReleaseRulesHolder 也无法判断这个 id 是否对应最新的灰度规则,因为灰度规则的变更没有 notificationId 追踪实现细节

  3. 要实现类似机制,需要对灰度规则的每次变更都生成唯一的“规则变更 id”并同步到客户端,客户端再带回这个 id,服务端才能判断是否需要穿透缓存。这涉及 Apollo 架构的较大改动,目前灰度规则的发布和配置发布是两套独立流程,缺乏统一的变更追踪体系。

  4. GrayReleaseRulesHolder 内部虽然有 loadVersion 字段用于本地缓存版本管理,但这个版本号并不对外暴露,也没有和客户端同步的机制GrayReleaseRuleCache 结构

综上,GrayReleaseRulesHolder 不能直接引入 notificationId 穿透缓存,主要是因为灰度规则没有 notificationId 体系,客户端也无法感知规则的变更。要实现类似机制,需要对灰度规则的变更、发布、通知链路做较大改造。

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Nov 07 '25 04:11 dosubot[bot]

为何不能在GrayReleaseRulesHolder中引入类似的机制,如果client端传过来的notificationid比缓存中的更大则穿透缓存

其实是可以这么做的,因为 GrayReleaseRulesHolder 也是通过 ReleaseMessage 触发更新的

nobodyiam avatar Nov 08 '25 05:11 nobodyiam

尝试实现了下,这个merge操作有并发问题,需要加锁,另外为了防止穿透缓存过于频繁,需要加个双重检查锁,5个service pod ,600个client的情况下能节省20倍

lzbjut avatar Nov 14 '25 02:11 lzbjut

This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days unless it is tagged "help wanted" or other activity occurs. Thank you for your contributions.

stale[bot] avatar Dec 14 '25 13:12 stale[bot]