feat(flexlb): configure vllm block hashing - #1357
Conversation
5866740 to
fe8eacc
Compare
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1357
Status: BLOCKING
Summary: P0/0 · P1/1 · P2/12 · P3/5
Reviewed: commit fe8eaccd317f · 2026-08-31 14:45 UTC+8
Blocking Issues
P1
- 嵌套 blockHashConfig 与顶层浅合并冲突,局部推送会静默清空 type 或复位 hashSeed @
rtp_llm/flexlb/flexlb-common/src/main/java/org/flexlb/config/FlexlbConfig.java:59- 建议:建议二选一:(1)把
type保留在FlexlbConfig顶层(继续以blockHashStrategy为唯一算法来源),只把hashSeed放进blockHashConfig;(2)让mergeConfig对嵌套 ObjectNode 做递归深合并(会同时改变modelServiceConfig、flexlbSyncConsistencyConfig的既有语义,需评估)。若两者都不做,至少在type/hashSeed由非空变为缺失时打 WARN 并沿用上次解析值;同时改写06-configuration-and-observability.md的合并语义说明,明确「推送blockHashConfig必须整块提供type与hashSeed,省略等价于显式设为默认值」,并在ConfigServiceTest补一条「env 全量 + Nacos 仅 hashSeed」的合并用例把结论固化。
- 建议:建议二选一:(1)把
Non-blocking Suggestions
P2
- seed 与 type 的配置变更全程无日志无指标,非法 seed 静默回退 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/VllmBlockHashStrategy.java:104- 建议:在
updateHashSeed中比较新旧有效 seed,仅在真正变化时打 WARN(含旧值→新值与 noneHash 摘要前缀,seed 非密钥可直接打印),未变化时不打日志以避免任意无关推送刷屏;null/空白时打 WARN 并说明已回退"0"。在blockHashStrategybean 创建时log.info记录选定策略、来源(blockHashConfig.type还是回退的废弃字段)与初始 seed 作为对照基线;再对运行期type与启动选定值做比对,不一致时 WARN 提示需重启;若解析为 SGLANG 而显式配了非默认hashSeed,提示该值不生效。若已有 kmonitor 通道,可增加 seed 版本/变更计数 gauge,便于把命中率突降与推送时间点对齐。
- 建议:在
- seed 热更新未失效 Local Standby 自建索引,旧 seed 映射沦为占容量的垃圾 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/VllmBlockHashStrategy.java:53- 建议:在 seed 生效变更时联动失效 Local Standby 索引:向
LocalStandbyCacheManager暴露一个 invalidate 入口,或让索引条目携带 seed 版本号、版本不匹配即视为过期。若判断不值得做失效,至少在 seed 变更日志中明确「standby 索引将在 TTL 内逐步失效、期间容量可能被旧映射占用」,使运维对该窗口的命中率下降与容量拒绝告警有预期。
- 建议:在 seed 生效变更时联动失效 Local Standby 索引:向
- 文档删除 PYTHONHASHSEED 语义锚点,运维无从判断 hashSeed 该填什么 @
rtp_llm/flexlb/docs/architecture/04-worker-sync-and-cache.md:136- 建议:在该 bullet 与
06字段表同步补充取值约束:hashSeed必须与 vLLM 引擎进程PYTHONHASHSEED取值逐字符一致(默认0);引擎未设置该变量时 vLLM 的 NONE_HASH 随机化、FlexLB 无法匹配;并写明填错的可观测现象(前缀命中率整体归零、TTFT 退化)与回滚方式(改回原 seed,无需重启,但需等待 standby 索引 TTL 过期)。
- 建议:在该 bullet 与
- 向 VLLM 策略注入 ConfigService 的 bean 接线没有测试能拦住回退 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/BlockHashStrategyTest.java:26- 建议:在 VLLM 分支补一条
verify(configService).addUpdateListener(any()),成本极低即可锁住接线。更进一步可用真实ConfigService配合可推送的ConfigSource走一遍工厂,断言工厂产出的 bean 在 emit 新hashSeed后calculate结果随之变化,端到端覆盖「bean 创建 → 监听注册 → 热更新生效」;注意ConfigService.CONFIG_SOURCES是静态容器,需处理测试隔离。
- 建议:在 VLLM 分支补一条
- hashSeed 两个回退分支的断言不具区分度,热更完全失效也能通过 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:160- 建议:让每条回退断言的前置状态都是非默认 seed:初始 seed 用
"1"并先断言 seed-1 的 key,直接 emit{"blockHashConfig":{"hashSeed":null}}断言回落到2164874634404590027L;再 emit{"blockHashConfig":{"hashSeed":"1"}}恢复非默认态后 emit{"blockHashConfig":null},同样断言回落。这样每条回退分支都有唯一可失败路径。
- 建议:让每条回退断言的前置状态都是非默认 seed:初始 seed 用
- 非默认 seed 的期望值缺少 vLLM 溯源与字节级锚定 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:151- 建议:为 seed
"1"补一条与 seed"0"对称的断言:encode(g -> writeHashSeed(g, "1"))应为6131,并断言其 SHA-256 作为 seed-1 的 none hash;给2107465152829418342L补上「由 vLLMsha256_cbor+PYTHONHASHSEED=1生成(注明版本/脚本)」的溯源注释。建议再覆盖一个多字符 seed(如"12345")以锚定 CBOR 文本串的长度前缀编码。
- 建议:为 seed
- 并发用例未覆盖「seed 热更新与并发计算交叉」这一新增风险 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:177- 建议:新增用例:用
VllmBlockHashStrategy(configService)起多线程对长输入持续calculate,同时后台线程通过FakeConfigSource.emit在 seed"0"/"1"之间反复切换,断言每个返回结果必须完整等于两代预期结果之一,不允许出现第三种混链结果,把「单请求单 seed 代」固化为回归断言。
- 建议:新增用例:用
- 废弃键 blockHashStrategy 的 JSON 解析覆盖被删除且无等价替代 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/BlockHashStrategyTest.java:43- 建议:保留(或另起)一个对废弃键的解析断言:
{"blockHashStrategy":"SGLANG"}→getBlockHashStrategy() == SGLANG且getBlockHashConfig() == null;并补一条「blockHashConfig整体缺失 + 废弃字段为 SGLANG 时blockHashStrategy(configService)仍返回SglangBlockHashStrategy」的断言,使文档承诺的兼容路径在废弃期内持续受保护。
- 建议:保留(或另起)一个对废弃键的解析断言:
- 新增的 BLOCK_HASH_CONFIG 环境变量入口零覆盖,且 fixture 的 type 从未被断言 @
rtp_llm/flexlb/flexlb-common/src/test/java/org/flexlb/service/config/EnvironmentConfigSourceTest.java:19- 建议:补
assertThat(config.getBlockHashConfig().getType())断言;新增BLOCK_HASH_CONFIG={"type":"SGLANG"}用例,断言最终getType() == SGLANG且getHashSeed()回落"0"(即FLEXLB_CONFIG内的configured-seed被整体替换),把「整对象替换」这一易误用语义显式固化;再补一次非法 JSON 用例确认不会污染配置。
- 建议:补
- 测试改写 JVM 级静态配置源注册表,清理不在 finally / @AfterEach 内 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:145- 建议:对齐
ConfigServiceTest做法:把configService提升为字段并用@AfterEach无条件close(),或把new ConfigService()与策略构造一并纳入try;同时把EnvironmentConfigSourceTest的close()迁到@AfterEach,避免单条断言失败引发跨用例级联失败掩盖根因。
- 建议:对齐
- flexlb-cache 直接使用 CBOR 但未在自身 pom 声明该依赖 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/VllmBlockHashStrategy.java:3- 建议:在
flexlb-cache/pom.xml显式声明com.fasterxml.jackson.dataformat:jackson-dataformat-cbor(版本仍由父 pom 统一管理);如确认 flexlb-common 已不再需要,可另行评估是否从 common 移除,避免依赖声明与实际使用长期错位。
- 建议:在
- 废弃字段回退逻辑未收敛到 FlexlbConfig,与同文件既有模式不一致 @
rtp_llm/flexlb/flexlb-common/src/main/java/org/flexlb/config/FlexlbConfig.java:52- 建议:在
FlexlbConfig中增加getEffectiveBlockHashStrategyType(),把blockHashConfig == null、type == null的判断与废弃字段回退收敛在配置类内部(与getEffectiveOutstandingUncachedTokensThreshold同一模式),BlockHashStrategyConfiguration只调用该方法,并为该方法补配置类级别单测。
- 建议:在
P3
- 仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/BlockHashConfigResolver.java:12- 建议:二者择一或同时做:把配置侧类型改名为与职责匹配的唯一名称(如
BlockHashAlgorithmConfig/BlockHashSeedConfig,必要时用@JsonProperty("blockHashConfig")保持配置键兼容);把本接口改名为不含BlockHashConfig字样、且不与 flexlb-sync 实现类撞名的名字(如WorkerBlockShapeResolver),并同步RequestBlockHashService.java:19的字段名与测试 mock 声明。本接口目前只有一个实现与一个消费方,改名成本很低;改完后 javadoc 里的免责说明即可精简。
- 建议:二者择一或同时做:把配置侧类型改名为与职责匹配的唯一名称(如
- 默认 seed "0" 存在三处重复定义 @
rtp_llm/flexlb/flexlb-common/src/main/java/org/flexlb/config/BlockHashConfig.java:35- 建议:选定单一来源:或由
BlockHashConfig暴露public static final String DEFAULT_HASH_SEED供策略侧复用;或去掉字段初始值改为null、统一由策略侧兜底——后者还能区分「未配置」与「显式配置为 0」,正好为上面 P1 项的「嵌套局部推送丢字段」提供可告警的信号。文档表格中的默认值注明来源类,便于后续同步。
- 建议:选定单一来源:或由
- 文档未登记 BLOCK_HASH_CONFIG 环境入口,也未说明环境变量不支持热更新 @
rtp_llm/flexlb/docs/architecture/06-configuration-and-observability.md:60- 建议:在两行新字段处补注「可由
BLOCK_HASH_CONFIG(JSON 对象串)整体覆盖」,并在第 203 行 env 汇总中把BLOCK_HASH_CONFIG与BLOCK_HASH_STRATEGY并列、标明前者优先;同时明确两点:BLOCK_HASH_CONFIG是整体覆盖而非逐字段覆盖(与上面 P1 的嵌套替换语义表述一致);环境变量来源不支持热更新,hashSeed的运行时热更新只能来自 Nacos 推送。
- 建议:在两行新字段处补注「可由
- WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致 @
rtp_llm/flexlb/flexlb-sync/src/main/java/org/flexlb/sync/status/WorkerBlockHashConfigResolver.java:25- 建议:补一个用例:prefill 两个不一致 + PDFUSION 一致可用,断言期望结果(保留缓存值或回退 PDFUSION 二选一),并据此校正 javadoc 措辞(若实现符合预期,把「and consistent」改为仅描述「no alive Prefill worker reports a valid pair」),避免文档与行为长期漂移。
- FakeConfigSource 在两个模块测试中重复实现 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:240- 建议:把
FakeConfigSource提取为 flexlb-common 的 test-jar 共享 fixture(maven-jar-plugin的test-jargoal 或独立 test-support 模块),供两个模块复用;若判断跨模块共享 test fixture 的成本高于收益,至少在两处各加一行注释指向对方,明确「行为需与另一实现保持一致」。
- 建议:把
Checklist Findings (12 fail / 26 total)
General Principles Checklist
- [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue
flexlb-cache 直接使用 CBOR 但未在自身 pom 声明该依赖
本 PR 后com.fasterxml.jackson.dataformat.cbor的全部使用点都落在 flexlb-cache(本文件第 3-4 行的CBORFactory/CBORGenerator,以及VllmBlockHashStrategyTest.java:3),但jackson-dataformat-cbor只声明在flexlb-common/pom.xml:36;flexlb-cache/pom.xml的依赖列表中无该 artifact,完全依赖传递依赖编译。与此同时 flexlb-common 删除BlockCacheKeyCalculator后主/测试代码已无任何 CBOR 使用点,形成「声明处不用、使用处不声明」的倒挂——任何一次「清理未使用依赖」都会同时打断 flexlb-cache 的主代码与测试编译。 - [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
文档未登记 BLOCK_HASH_CONFIG 环境入口,也未说明环境变量不支持热更新
第 60-61 行两个新字段没有任何环境变量说明,只有第 62 行「已废弃」的blockHashStrategy标注了BLOCK_HASH_STRATEGY,第 203 行 env 汇总清单同样只列BLOCK_HASH_STRATEGY(相邻的modelServiceConfig则标注了MODEL_SERVICE_CONFIG)。而EnvironmentConfigSource.applyFieldOverrides的反射机制使BLOCK_HASH_CONFIG={"type":…,"hashSeed":…}事实上可用。纯环境变量部署(无 Nacos)的运维按文档从废弃字段迁移时找不到新入口,只能继续沿用旧字段;同时EnvironmentConfigSource.setUpdateListener(第 45 行)是空实现,环境变量来源不支持热更新,文档亦未说明。 - [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue
仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名
本 PR 的核心动机是拆开两个概念:record 改名WorkerBlockHashConfig,同时BlockHashConfig这个简单名被复用来表示「算法与 seed 配置」。证据是代码自身不得不反复用全限定名消歧(本文件第 10 行、WorkerBlockHashConfig.java:10、config/BlockHashConfig.java:15),并需一句「it is independent of …」来否认联想;在 flexlb-cache 内两者都可见(BlockHashStrategyConfiguration.java:3、VllmBlockHashStrategy.java:5已 import 配置侧那个),IDE 自动补全易引入错误 import。同时实现类已叫WorkerBlockHashConfigResolver,本接口却仍叫BlockHashConfigResolver且返回WorkerBlockHashConfig(第 14 行),RequestBlockHashService.java:19 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
seed 热更新未失效 Local Standby 自建索引,旧 seed 映射沦为占容量的垃圾
Local Standby 索引不是引擎上报的,而是 FlexLB 自己算出的 key 回写:LocalStandbyCacheMatchProvider.java:136调用cacheManager.addRoutedRequestBlocks(...),进而写入LocalStandbyCacheIndex(TTL 淘汰 +maximumEntries上限)。seed 热更新后,变更前写入的全部映射对新 key 永远不可命中,却继续占用索引容量直到 TTL 到期,可触发LocalStandbyCacheManager.java:162-177的拒绝分支与reportLocalStandbyCapacityRejected(),挤掉新 seed 的有效映射。而本行注册的监听器只重算noneHash,没有任何索引失效动作。 - [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue
文档删除 PYTHONHASHSEED 语义锚点,运维无从判断 hashSeed 该填什么
改动前该段写「PYTHONHASHSEED=0语义」,改动后只剩「其hashSeed仅用于 vLLM,默认0」,全仓 docs 已搜不到PYTHONHASHSEED(唯一残留在VllmBlockHashStrategyTest.java:35的注释)。而 vLLM 的init_none_hash正是用该环境变量的字符串做 seed,这也是hashSeed定义为String的原因。删掉唯一取值依据后,本 PR 新暴露的运维开关变成没有语义说明的自由字符串:运维不知道它必须与引擎进程逐字符一致,也不知道引擎未设置该变量时 vLLM 的 NONE_HASH 随机化、任何 seed 都无法匹配。06-configuration-and-observability.md:61同样只写「vLLMsha256_cborseed」。 - [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue
测试改写 JVM 级静态配置源注册表,清理不在 finally / @AfterEach 内
ConfigService.register写入静态CONFIG_SOURCES(ConfigService.java:23、35),仅close()会清空全局所有已注册来源(:117)。用例把register、new ConfigService()、new VllmBlockHashStrategy(configService)(第 145-147 行)全放在try(第 149 行)之外,而策略构造会立即回调监听器:一旦抛异常finally不执行,FakeConfigSource 永久残留在静态表中,污染同 surefire JVM 内后续用例。EnvironmentConfigSourceTest:47的close()同样位于 11 条断言之后;而ConfigServiceTest:22-27已用字段 +@AfterEach兜底,三处策略不一致。 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致
本 PR 把 javadoc 改写为「PD Fusion workers are used only when no alive Prefill worker reports a valid and consistent pair」(第 24-26 行),但findPreferredBlockHashConfigs(第 107-112 行)只在 prefill 集合为空时回退 PDFUSION;prefill 不一致(size > 1)时走第 93-102 行保留缓存值、不回退。现有用例keepsLastValidConfigWhenPrefillWorkersAreInconsistent场景里没有 PDFUSION worker,ignoresPdFusionConfigWhenPrefillConfigIsAvailable的 prefill 又是一致的,因此这条差异没有任何用例把「哪种才是预期」钉死。 - [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue
FakeConfigSource 在两个模块测试中重复实现
ConfigServiceTest.java:244(flexlb-common test)已存在功能更完整的FakeConfigSource(含loaded、closed、loadException等),本用例在 flexlb-cache 又实现了一份精简版(第 240-275 行)。两份实现对setUpdateListener/load/emit的语义假设需要人工保持一致;后续若ConfigSource接口演进(新增方法或改变 listener 注册契约),两处都要改,容易只改一处而让另一处的测试假设失真。 - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
测试改写 JVM 级静态配置源注册表,清理不在 finally / @AfterEach 内
ConfigService.register写入静态CONFIG_SOURCES(ConfigService.java:23、35),仅close()会清空全局所有已注册来源(:117)。用例把register、new ConfigService()、new VllmBlockHashStrategy(configService)(第 145-147 行)全放在try(第 149 行)之外,而策略构造会立即回调监听器:一旦抛异常finally不执行,FakeConfigSource 永久残留在静态表中,污染同 surefire JVM 内后续用例。EnvironmentConfigSourceTest:47的close()同样位于 11 条断言之后;而ConfigServiceTest:22-27已用字段 +@AfterEach兜底,三处策略不一致。 - [6.1] Tests — 被删除测试有等价替代覆盖 → issue
废弃键 blockHashStrategy 的 JSON 解析覆盖被删除且无等价替代
原用例parsesSglangStrategyFromFlexlbConfigJson断言{"blockHashStrategy":"SGLANG"}能反序列化为getBlockHashStrategy() == SGLANG,本 PR 将其整体改写为只断言新的blockHashConfig。全仓搜索确认blockHashStrategy这个 JSON key 已无任何解析断言,只有第 36 行通过 setter 覆盖了选择顺序;而文档 06 第 62 行与第 203 行仍声明支持blockHashStrategy与BLOCK_HASH_STRATEGY环境变量覆盖。一旦其序列化行为回归,存量 SGLANG 部署会静默回落 VLLM、block key 与引擎全量不匹配,却没有回归保护。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致
本 PR 把 javadoc 改写为「PD Fusion workers are used only when no alive Prefill worker reports a valid and consistent pair」(第 24-26 行),但findPreferredBlockHashConfigs(第 107-112 行)只在 prefill 集合为空时回退 PDFUSION;prefill 不一致(size > 1)时走第 93-102 行保留缓存值、不回退。现有用例keepsLastValidConfigWhenPrefillWorkersAreInconsistent场景里没有 PDFUSION worker,ignoresPdFusionConfigWhenPrefillConfigIsAvailable的 prefill 又是一致的,因此这条差异没有任何用例把「哪种才是预期」钉死。
RTP-LLM Checklist
- [I] 代码质量 — 同一功能用统一工具函数 → issue
FakeConfigSource 在两个模块测试中重复实现
ConfigServiceTest.java:244(flexlb-common test)已存在功能更完整的FakeConfigSource(含loaded、closed、loadException等),本用例在 flexlb-cache 又实现了一份精简版(第 240-275 行)。两份实现对setUpdateListener/load/emit的语义假设需要人工保持一致;后续若ConfigSource接口演进(新增方法或改变 listener 注册契约),两处都要改,容易只改一处而让另一处的测试假设失真。
Strengths
- 算法搬迁是保真的等价迁移:CBOR 编码、
WRITE_MINIMAL_INTS、digest 链与低 64 位取值一致,全部外部锚定向量(4e1195df…、c9d58ba6…、ddfc07d6…、2164874634404590027L)未改动,重构与功能新增没有混在一起改写算法。 - 单请求内 seed 原子性由结构保证:
VllmBlockHashStrategy.java:80把volatile noneHash读入局部parentHash,seed 中途切换不会撕裂同一次调用的哈希链;MessageDigest保持ThreadLocal,配置线程执行calculateNoneHash不会污染 hash 线程池实例。 - 两个同名概念的边界被显式文档化并三处交叉引用:
WorkerBlockHashConfig.java:10、BlockHashConfigResolver.java:8-10、config/BlockHashConfig.java:15-17互相点名,明确blockSize/lookaheadTokens来自 worker status、不可由配置侧设置。 - 删除面收口彻底:全仓搜索
BlockCacheKeyCalculator与旧cache.domain.BlockHashConfig零命中;VllmBlockHashStrategyTest覆盖了 seed CBOR 字节、整数边界、单块/链式全 digest、EAGLE lookahead、尾部不满块丢弃、非法入参、20480 token × 16 线程并发,符合 R.I.2 的替代覆盖要求。 - 向后兼容有明确降级路径且默认值一致:
hashSeed默认"0"与改动前硬编码 seed 相同,blockHashConfig默认null时回退blockHashStrategy,存量FLEXLB_CONFIG/BLOCK_HASH_STRATEGY部署无需变更。 - seed 变更的影响面被现有结构限制住:
LocalStandbyHashService.java:78/:138的 Caffeine 以 requestId 为键,不会跨请求复用旧 seed 的哈希结果。 - 文档与代码同 PR 更新:
04-worker-sync-and-cache.md:134-137明确「策略 bean 启动时创建、type变更需重启、hashSeed可热更新」,00-overview.md同步移除已删除的类,新配置项登记进06字段表。
| * lookahead tokens, which are obtained from worker status as | ||
| * {@code org.flexlb.cache.domain.WorkerBlockHashConfig}. | ||
| */ | ||
| private BlockHashConfig blockHashConfig; |
There was a problem hiding this comment.
[P1] 嵌套 blockHashConfig 与顶层浅合并冲突,局部推送会静默清空 type 或复位 hashSeed
ConfigService.mergeConfig 用 merged.setAll(overrides)(ConfigService.java:102)做顶层 key 级替换,嵌套 object 整块被换、缺失子字段回落 POJO 默认值。本 PR 自带用例推送的正是 {"blockHashConfig":{"hashSeed":"1"}}(VllmBlockHashStrategyTest.java:144):该载荷令 type=null,BlockHashStrategyConfiguration.java:23 随即回退已废弃的 blockHashStrategy(默认 VLLM);Nacos priority 2 高于 env priority 1,env 配好的 type=SGLANG 被静默掩盖,重启后算法由 SGLANG 变 VLLM。反向只推 type 会让运行中的 hashSeed 当场复位 "0"。两个方向都表现为 block key 命名空间静默切换、命中率归零,而文档 06:21-22 承诺「省略字段保留当前内存值」。
建议: 建议二选一:(1)把 type 保留在 FlexlbConfig 顶层(继续以 blockHashStrategy 为唯一算法来源),只把 hashSeed 放进 blockHashConfig;(2)让 mergeConfig 对嵌套 ObjectNode 做递归深合并(会同时改变 modelServiceConfig、flexlbSyncConsistencyConfig 的既有语义,需评估)。若两者都不做,至少在 type/hashSeed 由非空变为缺失时打 WARN 并沿用上次解析值;同时改写 06-configuration-and-observability.md 的合并语义说明,明确「推送 blockHashConfig 必须整块提供 type 与 hashSeed,省略等价于显式设为默认值」,并在 ConfigServiceTest 补一条「env 全量 + Nacos 仅 hashSeed」的合并用例把结论固化。
| return blockCacheKeys; | ||
| } | ||
|
|
||
| private void updateHashSeed(String hashSeed) { |
There was a problem hiding this comment.
[P2] seed 与 type 的配置变更全程无日志无指标,非法 seed 静默回退
updateHashSeed 是 seed 落地的唯一入口,只做 noneHash = calculateNoneHash(...),整个类没有 logger;同行 hashSeed == null ? DEFAULT_HASH_SEED : hashSeed 把 null 静默降级,BlockHashConfig.hashSeed 也无任何校验,空串或拼错值会被编码成合法但错误的 noneHash。BlockHashStrategyConfiguration 亦不记录最终选定策略与来源;type 在运行期被改动时只会看到通用 INFO Applied FlexLB configuration update(ConfigService.java:78),无「需重启才生效」提示。seed 决定全局 cache key 空间,一次错误推送只表现为命中率归零、TTFT 退化。对比同 PR 的 WorkerBlockHashConfigResolver.java:134-138(首次 INFO、变更 WARN)与 `EnvironmentConfigSou...
建议: 在 updateHashSeed 中比较新旧有效 seed,仅在真正变化时打 WARN(含旧值→新值与 noneHash 摘要前缀,seed 非密钥可直接打印),未变化时不打日志以避免任意无关推送刷屏;null/空白时打 WARN 并说明已回退 "0"。在 blockHashStrategy bean 创建时 log.info 记录选定策略、来源(blockHashConfig.type 还是回退的废弃字段)与初始 seed 作为对照基线;再对运行期 type 与启动选定值做比对,不一致时 WARN 提示需重启;若解析为 SGLANG 而显式配了非默认 hashSeed,提示该值不生效。若已有 kmonitor 通道,可增加 seed 版本/变更计数 gauge,便于把命中率突降与推送时间点对齐。
| */ | ||
| public VllmBlockHashStrategy(ConfigService configService) { | ||
| this(); | ||
| configService.addUpdateListener(config -> updateHashSeed(hashSeed(config))); |
There was a problem hiding this comment.
[P2] seed 热更新未失效 Local Standby 自建索引,旧 seed 映射沦为占容量的垃圾
Local Standby 索引不是引擎上报的,而是 FlexLB 自己算出的 key 回写:LocalStandbyCacheMatchProvider.java:136 调用 cacheManager.addRoutedRequestBlocks(...),进而写入 LocalStandbyCacheIndex(TTL 淘汰 + maximumEntries 上限)。seed 热更新后,变更前写入的全部映射对新 key 永远不可命中,却继续占用索引容量直到 TTL 到期,可触发 LocalStandbyCacheManager.java:162-177 的拒绝分支与 reportLocalStandbyCapacityRejected(),挤掉新 seed 的有效映射。而本行注册的监听器只重算 noneHash,没有任何索引失效动作。
建议: 在 seed 生效变更时联动失效 Local Standby 索引:向 LocalStandbyCacheManager 暴露一个 invalidate 入口,或让索引条目携带 seed 版本号、版本不匹配即视为过期。若判断不值得做失效,至少在 seed 变更日志中明确「standby 索引将在 TTL 内逐步失效、期间容量可能被旧映射占用」,使运维对该窗口的命中率下降与容量拒绝告警有预期。
Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声
| `[parentHash, tokens, null]` → SHA-256 → 取低 64 位为 Long key;末尾不满块丢弃。 | ||
| - 配置侧:`org.flexlb.config.BlockHashConfig` 位于 `FlexlbConfig.blockHashConfig`。其 `type` | ||
| 选择 `VLLM` / `SGLANG`;缺失时才回退到已废弃的 `FlexlbConfig.blockHashStrategy`。策略 bean | ||
| 在启动时创建,因此修改 `type` 需重启。其 `hashSeed` 仅用于 vLLM,默认 `0`;`ConfigService` |
There was a problem hiding this comment.
[P2] 文档删除 PYTHONHASHSEED 语义锚点,运维无从判断 hashSeed 该填什么
改动前该段写「PYTHONHASHSEED=0 语义」,改动后只剩「其 hashSeed 仅用于 vLLM,默认 0」,全仓 docs 已搜不到 PYTHONHASHSEED(唯一残留在 VllmBlockHashStrategyTest.java:35 的注释)。而 vLLM 的 init_none_hash 正是用该环境变量的字符串做 seed,这也是 hashSeed 定义为 String 的原因。删掉唯一取值依据后,本 PR 新暴露的运维开关变成没有语义说明的自由字符串:运维不知道它必须与引擎进程逐字符一致,也不知道引擎未设置该变量时 vLLM 的 NONE_HASH 随机化、任何 seed 都无法匹配。06-configuration-and-observability.md:61 同样只写「vLLM sha256_cbor seed」。
建议: 在该 bullet 与 06 字段表同步补充取值约束:hashSeed 必须与 vLLM 引擎进程 PYTHONHASHSEED 取值逐字符一致(默认 0);引擎未设置该变量时 vLLM 的 NONE_HASH 随机化、FlexLB 无法匹配;并写明填错的可观测现象(前缀命中率整体归零、TTFT 退化)与回滚方式(改回原 seed,无需重启,但需等待 standby 索引 TTL 过期)。
Checklist: [6.1] 回滚路径:风险行为存在运维回滚手段
| @@ -24,18 +25,27 @@ void defaultsToVllmAndSelectsSglangFromFlexlbConfig() { | |||
| VllmBlockHashStrategy.class, | |||
| configuration.blockHashStrategy(configService)); | |||
There was a problem hiding this comment.
[P2] 向 VLLM 策略注入 ConfigService 的 bean 接线没有测试能拦住回退
生产路径是 BlockHashStrategyConfiguration.java:16 的 new VllmBlockHashStrategy(configService),热更新完全依赖该构造参数注册监听。但本用例用 mock(ConfigService.class)(第 20 行),Mockito 对 void 的 addUpdateListener 是空实现且无 verify,VLLM 分支只有 assertInstanceOf。若日后有人把工厂改回无参 new VllmBlockHashStrategy()(该无参构造仍是 public,seed 固定 "0"、热更失效),本用例仍通过;唯一覆盖热更的 VllmBlockHashStrategyTest 是直接 new 策略、绕过工厂,同样不会失败。本 PR 核心特性所依赖的这一行接线零保护。
建议: 在 VLLM 分支补一条 verify(configService).addUpdateListener(any()),成本极低即可锁住接线。更进一步可用真实 ConfigService 配合可推送的 ConfigSource 走一遍工厂,断言工厂产出的 bean 在 emit 新 hashSeed 后 calculate 结果随之变化,端到端覆盖「bean 创建 → 监听注册 → 热更新生效」;注意 ConfigService.CONFIG_SOURCES 是静态容器,需处理测试隔离。
| List.of(2164874634404590027L), | ||
| dynamicStrategy.calculate(new int[]{1, 2, 3, 4}, 4, 0)); | ||
|
|
||
| source.emit("{\"blockHashConfig\":{\"hashSeed\":null}}"); |
There was a problem hiding this comment.
[P2] hashSeed 两个回退分支的断言不具区分度,热更完全失效也能通过
用例 emit 顺序为初始 seed "1" → {"hashSeed":"0"}(第 154 行)→ {"hashSeed":null}(第 160 行)→ {"blockHashConfig":null}(第 166 行),后三次断言期望值都是同一个 seed "0" 的结果 2164874634404590027L。执行到第 3、4 次 emit 时 noneHash 已等于 seed "0" 的值,因此「回退到默认 "0"」与「监听器根本没被回调 / 保留旧值」两种实现给出完全相同结果。而 updateHashSeed 的 hashSeed == null(:105)与 hashSeed(config) 的 blockHashConfig == null(:110)这两个分支唯一存在目的就是回落默认值,恰是本用例声称要覆盖的行为。
建议: 让每条回退断言的前置状态都是非默认 seed:初始 seed 用 "1" 并先断言 seed-1 的 key,直接 emit {"blockHashConfig":{"hashSeed":null}} 断言回落到 2164874634404590027L;再 emit {"blockHashConfig":{"hashSeed":"1"}} 恢复非默认态后 emit {"blockHashConfig":null},同样断言回落。这样每条回退分支都有唯一可失败路径。
| * {@code lookaheadTokens}; it is independent of the configurable algorithm type and hash seed in | ||
| * {@link org.flexlb.config.BlockHashConfig}. | ||
| */ | ||
| public interface BlockHashConfigResolver { |
There was a problem hiding this comment.
[P3] 仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名
本 PR 的核心动机是拆开两个概念:record 改名 WorkerBlockHashConfig,同时 BlockHashConfig 这个简单名被复用来表示「算法与 seed 配置」。证据是代码自身不得不反复用全限定名消歧(本文件第 10 行、WorkerBlockHashConfig.java:10、config/BlockHashConfig.java:15),并需一句「it is independent of …」来否认联想;在 flexlb-cache 内两者都可见(BlockHashStrategyConfiguration.java:3、VllmBlockHashStrategy.java:5 已 import 配置侧那个),IDE 自动补全易引入错误 import。同时实现类已叫 WorkerBlockHashConfigResolver,本接口却仍叫 BlockHashConfigResolver 且返回 WorkerBlockHashConfig(第 14 行),`RequestBlockHashService.java:...
建议: 二者择一或同时做:把配置侧类型改名为与职责匹配的唯一名称(如 BlockHashAlgorithmConfig / BlockHashSeedConfig,必要时用 @JsonProperty("blockHashConfig") 保持配置键兼容);把本接口改名为不含 BlockHashConfig 字样、且不与 flexlb-sync 实现类撞名的名字(如 WorkerBlockShapeResolver),并同步 RequestBlockHashService.java:19 的字段名与测试 mock 声明。本接口目前只有一个实现与一个消费方,改名成本很低;改完后 javadoc 里的免责说明即可精简。
Checklist: [6.1] 分层边界:新概念在正确层级,不泄漏内部
| * updated at runtime for an active vLLM strategy through the {@link ConfigService} listener. | ||
| * It has no effect when the resolved strategy is SGLang. | ||
| */ | ||
| private String hashSeed = "0"; |
There was a problem hiding this comment.
[P3] 默认 seed "0" 存在三处重复定义
默认 seed 同时写在三处:本行 hashSeed = "0"(配置模型字段初始值)、VllmBlockHashStrategy.java:27 的 DEFAULT_HASH_SEED = "0"(blockHashConfig 或 hashSeed 为 null 时兜底)、以及文档 06-configuration-and-observability.md:61 的字段表默认值。任一处被改动而其他未同步,会让「配置缺省」与「显式给出对象但缺 hashSeed」两条路径得到不同 seed,而这种偏差在结果上只表现为 cache 不命中,很难被测试发现。
建议: 选定单一来源:或由 BlockHashConfig 暴露 public static final String DEFAULT_HASH_SEED 供策略侧复用;或去掉字段初始值改为 null、统一由策略侧兜底——后者还能区分「未配置」与「显式配置为 0」,正好为上面 P1 项的「嵌套局部推送丢字段」提供可告警的信号。文档表格中的默认值注明来源类,便于后续同步。
| |---|---|---| | ||
| | `modelServiceConfig` | 无(缺失则启动失败) | 模型路由、服务发现、KVCM 与 Optimizer 配置;可由 `MODEL_SERVICE_CONFIG` 覆盖,更新后重启生效 | | ||
| | `blockHashStrategy` | `VLLM` | cache block hash 策略:`VLLM` / `SGLANG`;可由 `BLOCK_HASH_STRATEGY` 覆盖 | | ||
| | `blockHashConfig.type` | 未设置 | 优先的 cache block hash 策略:`VLLM` / `SGLANG`;策略 bean 在启动时创建,变更需重启 | |
There was a problem hiding this comment.
[P3] 文档未登记 BLOCK_HASH_CONFIG 环境入口,也未说明环境变量不支持热更新
第 60-61 行两个新字段没有任何环境变量说明,只有第 62 行「已废弃」的 blockHashStrategy 标注了 BLOCK_HASH_STRATEGY,第 203 行 env 汇总清单同样只列 BLOCK_HASH_STRATEGY(相邻的 modelServiceConfig 则标注了 MODEL_SERVICE_CONFIG)。而 EnvironmentConfigSource.applyFieldOverrides 的反射机制使 BLOCK_HASH_CONFIG={"type":…,"hashSeed":…} 事实上可用。纯环境变量部署(无 Nacos)的运维按文档从废弃字段迁移时找不到新入口,只能继续沿用旧字段;同时 EnvironmentConfigSource.setUpdateListener(第 45 行)是空实现,环境变量来源不支持热更新,文档亦未说明。
建议: 在两行新字段处补注「可由 BLOCK_HASH_CONFIG(JSON 对象串)整体覆盖」,并在第 203 行 env 汇总中把 BLOCK_HASH_CONFIG 与 BLOCK_HASH_STRATEGY 并列、标明前者优先;同时明确两点:BLOCK_HASH_CONFIG 是整体覆盖而非逐字段覆盖(与上面 P1 的嵌套替换语义表述一致);环境变量来源不支持热更新,hashSeed 的运行时热更新只能来自 Nacos 推送。
Checklist: [6.1] 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全
| * temporarily unavailable or report inconsistent values. | ||
| * <p>For every alive worker, {@code CacheStatus.blockSize} supplies the block size and | ||
| * {@code WorkerStatus.blockHashLookaheadTokens} supplies lookahead. PD Fusion workers are used | ||
| * only when no alive Prefill worker reports a valid and consistent pair. The last valid pair |
There was a problem hiding this comment.
[P3] WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致
本 PR 把 javadoc 改写为「PD Fusion workers are used only when no alive Prefill worker reports a valid and consistent pair」(第 24-26 行),但 findPreferredBlockHashConfigs(第 107-112 行)只在 prefill 集合为空时回退 PDFUSION;prefill 不一致(size > 1)时走第 93-102 行保留缓存值、不回退。现有用例 keepsLastValidConfigWhenPrefillWorkersAreInconsistent 场景里没有 PDFUSION worker,ignoresPdFusionConfigWhenPrefillConfigIsAvailable 的 prefill 又是一致的,因此这条差异没有任何用例把「哪种才是预期」钉死。
建议: 补一个用例:prefill 两个不一致 + PDFUSION 一致可用,断言期望结果(保留缓存值或回退 PDFUSION 二选一),并据此校正 javadoc 措辞(若实现符合预期,把「and consistent」改为仅描述「no alive Prefill worker reports a valid pair」),避免文档与行为长期漂移。
Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式;[6.1] 边界 case 覆盖(空、单元素、最大值)
| void write(CBORGenerator generator) throws IOException; | ||
| } | ||
|
|
||
| private static final class FakeConfigSource implements ConfigSource { |
There was a problem hiding this comment.
[P3] FakeConfigSource 在两个模块测试中重复实现
ConfigServiceTest.java:244(flexlb-common test)已存在功能更完整的 FakeConfigSource(含 loaded、closed、loadException 等),本用例在 flexlb-cache 又实现了一份精简版(第 240-275 行)。两份实现对 setUpdateListener/load/emit 的语义假设需要人工保持一致;后续若 ConfigSource 接口演进(新增方法或改变 listener 注册契约),两处都要改,容易只改一处而让另一处的测试假设失真。
建议: 把 FakeConfigSource 提取为 flexlb-common 的 test-jar 共享 fixture(maven-jar-plugin 的 test-jar goal 或独立 test-support 模块),供两个模块复用;若判断跨模块共享 test fixture 的成本高于收益,至少在两处各加一行注释指向对方,明确「行为需与另一实现保持一致」。
Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[I] 同一功能用统一工具函数
LLLLKKKK
left a comment
There was a problem hiding this comment.
AI Code Review - PR #1357
Status: BLOCKING
Summary: P0/0 · P1/1 · P2/9 · P3/10
Reviewed: commit 357bc9e52f54 · 2026-08-31 17:59 UTC+8
Blocking Issues
P1
- 嵌套 blockHashConfig 与顶层浅合并冲突,局部推送会静默清空 type 或复位 hashSeed @
rtp_llm/flexlb/flexlb-common/src/main/java/org/flexlb/config/FlexlbConfig.java:59- 建议:优选把热生效的
hashSeed提为FlexlbConfig顶层字段(与blockHashStrategy同级),嵌套对象只保留重启生效的type,即可直接复用现有逐字段浅合并语义;或让mergeConfig对嵌套 ObjectNode 递归深合并(modelServiceConfig/flexlbSyncConsistencyConfig同样受益)。若必须保留现结构,则让hashSeed默认值改为null且 listener 收到 null 时保留当前生效值,并在「blockHashConfig非 null 但type为 null」时log.warn明示正在回退到废弃字段,同时在 javadoc 与 04/06 文档写明「该对象必须整体推送」。请补两条回归测试:先让 seed 生效为非默认值再只推type,断言 seed 不变;只推hashSeed后断言type不变。另建议顺手给BlockHashStrategyConfiguration:15的 switch 补 null 兜底(显式推"blockHashStrategy":null会以 NPE 形式启动失败,该风险改动前已存在)。
- 建议:优选把热生效的
Non-blocking Suggestions
P2
- seed 与 type 的配置变更全程无日志无指标,非法 seed 静默回退 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/VllmBlockHashStrategy.java:104- 建议:在 seed 实际变化(含回落默认
"0")时打印 old→new 并标注是否为默认回落,风格与WorkerBlockHashConfigResolver对齐(seed 是配置值非凭据,仅变化时打印不构成热路径噪声),并上报 seed 版本 gauge / 变更 counter 以便与命中率指标做时间对齐;拒绝 blank seed 并保留上次有效值(记 error)。在blockHashStrategybean 创建时log.info打印最终生效的策略类型及其来源(blockHashConfig.type还是废弃字段),并在同一 listener 中比较配置type与当前活动策略,不一致时log.warn提示「算法类型变更需重启才能生效」。建议在既有 cache match 状态查询接口暴露当前生效 type 与 seed。
- 建议:在 seed 实际变化(含回落默认
- seed 热更新未失效 Local Standby 自建索引,旧 seed 映射挤占有界容量 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/VllmBlockHashStrategy.java:53- 建议:在 seed 变更时联动清理 Local Standby 索引(例如由策略暴露 seed 版本号,
LocalStandbyCacheManager观察到版本变化即清空或按代次隔离 key),使新旧 seed 的映射不共享同一配额。若认为依赖现有 TTL 缩短与 high-watermark 全扫自愈可接受,请在LocalStandbyCacheIndex与 docs/04 显式记录该权衡与恢复时间量级(最坏为一个 TTL 窗口),并说明 seed 变更后短期LOCAL_STANDBY容量拒绝与命中率下降属预期,便于运维在告警时不误判为故障。
- 建议:在 seed 变更时联动清理 Local Standby 索引(例如由策略暴露 seed 版本号,
- flexlb-cache 直接使用 CBOR 但未在自身 pom 声明该依赖 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/VllmBlockHashStrategy.java:3- 建议:在
flexlb-cache/pom.xml显式声明com.fasterxml.jackson.dataformat:jackson-dataformat-cbor(版本沿用父 pom 的 dependencyManagement,与 flexlb-common 保持一致),并确认 flexlb-common 是否仍需保留该依赖,若已无使用者则一并移除,使依赖声明与实际 import 对齐。
- 建议:在
- hashSeed 两个 null 回退分支的断言不具判别力,热更完全失效也能通过 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:160- 建议:调整 emit 顺序让每步期望值唯一可辨:初始
"1"→ emithashSeed:null→ 断言变为 seed"0"的 2164874634404590027L → 再 emit"1"→ 断言回到 2107465152829418342L → emitblockHashConfig:null→ 再次断言 2164874634404590027L。这样任何一步 listener 未触发或回退逻辑抛错都会导致断言失败;如需防止异常被吞,可对 listener 回调做单元级直接调用而不经过ConfigService的 try/catch。
- 建议:调整 emit 顺序让每步期望值唯一可辨:初始
- 并发用例未覆盖「seed 热更新与并发计算交叉」这一新增风险 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:177- 建议:新增并发变体:N 个线程持续对同一长输入调用
calculate,另一线程在"0"与"1"之间反复source.emit,断言每次返回结果都严格等于两个 seed 之一的完整期望链(不出现第三种取值或混合链),并断言两个期望值在运行过程中都被实际观察到,以确认用例真的触发了热更新而非空跑;同时锁定配置回调线程复用静态SHA_256不干扰计算线程。
- 建议:新增并发变体:N 个线程持续对同一长输入调用
- 新增的 BLOCK_HASH_CONFIG 环境变量入口零覆盖,且 fixture 的 type 从未被断言 @
rtp_llm/flexlb/flexlb-common/src/test/java/org/flexlb/service/config/EnvironmentConfigSourceTest.java:19- 建议:补断言
config.getBlockHashConfig().getType()为VLLM(或从 fixture 移除type);新增用例覆盖BLOCK_HASH_CONFIG={"type":"SGLANG"}对FLEXLB_CONFIG.blockHashConfig的整体替换语义(含hashSeed回落"0");再补一条「只设BLOCK_HASH_STRATEGY时旧字段仍生效,两者并存时新字段胜出」的用例,避免运维通过环境变量配置 seed 的路径缺少回归保护。
- 建议:补断言
- 测试改写 JVM 级静态配置源注册表,清理不在 try / @AfterEach 内 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:145- 建议:改用与
ConfigServiceTest一致的模式:把register与ConfigService/strategy 的构造放进@BeforeEach、close()放进@AfterEach(判空后调用);或至少把register之后的所有构造语句一并纳入 try 块,确保任何路径都会清空静态注册表。同一问题也存在于EnvironmentConfigSourceTest:47(configService.close()在全部断言之后且不在 finally 中,本次新增断言提高了中途失败概率),建议一并处理。
- 建议:改用与
- 废弃键 blockHashStrategy 的 JSON 契约覆盖被删除且无等价替代 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/BlockHashStrategyTest.java:58- 建议:在
parsesBlockHashConfigFromFlexlbConfigJson追加一次{"blockHashStrategy":"SGLANG"}的解析断言(或补独立用例),在字段真正移除前保留其 JSON 契约;并补一个type=SGLANG用例,同时断言assertInstanceOf(SglangBlockHashStrategy.class, ...)与verify(configService, never()).addUpdateListener(any()),把「hashSeed 对 SGLANG 无效」固化为测试。
- 建议:在
- 文档删除 PYTHONHASHSEED 语义锚点,运维无从判断 hashSeed 该填什么 @
rtp_llm/flexlb/docs/architecture/04-worker-sync-and-cache.md:136- 建议:在 04、06 文档及
BlockHashConfig.hashSeed的 javadoc 中恢复说明:该值必须与目标引擎进程的PYTHONHASHSEED字符串完全一致(对应 vLLMinit_none_hash语义);引擎未设置该变量时其初始 hash 为随机值、FlexLB 无论取何值都无法命中;变更 seed 后既有 block key 全部失效、命中率会短时下降,回滚方式为改回原 seed 且无需重启;推送blockHashConfig时必须同时带全type与hashSeed。同时在VllmBlockHashStrategy类注释保留与kv_cache_utils.py: init_none_hash的对应关系。
- 建议:在 04、06 文档及
P3
- 非默认 seed 的期望值缺少 vLLM 溯源与字节级锚定 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:151- 建议:补一条与默认 seed 对称的断言:
encode(g -> VllmBlockHashStrategy.writeHashSeed(g, "1"))应为6131,其 SHA-256 即 seed="1"的 noneHash;并为2107465152829418342L加上来源注释(由 vLLMsha256_cbor+PYTHONHASHSEED=1生成),使 golden 值可追溯、可复算。
- 建议:补一条与默认 seed 对称的断言:
- seed 热更新瞬间主 hash 与 Local Standby hash 可能跨 seed @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/RequestBlockHashService.java:75- 建议:在
prepareRequest入口一次性取得 seed(或noneHash引用)快照并随主/standby 两条路径下传,使同一请求的两条链必然同源;或让BlockHashStrategy暴露 seed 版本号,standby 结果携带版本,版本不一致时丢弃而不参与匹配与指标统计。若认为该瞬时不一致可接受,请在RequestBlockHashService或 docs/04 显式记录该权衡。
- 建议:在
- 默认 seed "0" 存在三处重复定义 @
rtp_llm/flexlb/flexlb-common/src/main/java/org/flexlb/config/BlockHashConfig.java:35- 建议:收敛到单一权威来源:由配置模型持有唯一常量(如
BlockHashConfig.DEFAULT_HASH_SEED)供字段初始化与消费方兜底同时引用;或配置层不设默认值(保持null表示未配置),统一由VllmBlockHashStrategy常量兜底,并让文档引用同一处说明。
- 建议:收敛到单一权威来源:由配置模型持有唯一常量(如
- 废弃字段回退逻辑未收敛到 FlexlbConfig,且新消费点缺 @SuppressWarnings @
rtp_llm/flexlb/flexlb-common/src/main/java/org/flexlb/config/FlexlbConfig.java:52- 建议:把回退判定收敛为
FlexlbConfig上的getEffectiveBlockHashStrategyType(),与同文件既有模式保持一致,BlockHashStrategyConfiguration只做 bean 选择,同时把废弃字段读取集中到一处;并按既有约定给读取废弃字段的位置补@SuppressWarnings("deprecation")(回退分支本身是有意读取废弃字段),保持编译输出无新增警告。
- 建议:把回退判定收敛为
- 仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名 @
rtp_llm/flexlb/flexlb-cache/src/main/java/org/flexlb/cache/hash/BlockHashConfigResolver.java:12- 建议:把接口改名为与
WorkerBlockHashConfigResolver对称且不重名的形式(如WorkerBlockHashConfigSupplier/WorkerBlockShapeResolver),或把配置侧类改为更具区分度的名称(如BlockHashAlgorithmConfig),使两组概念名称互不重叠;若决定保留现名,请在两个类与接口 javadoc 中都显式标注对端全限定名(目前已部分具备)。
- 建议:把接口改名为与
- 文档未登记 BLOCK_HASH_CONFIG 环境入口及其整体替换语义 @
rtp_llm/flexlb/docs/architecture/06-configuration-and-observability.md:60- 建议:在表格两行补「可由
BLOCK_HASH_CONFIG(JSON,形如{"type":"VLLM","hashSeed":"0"})覆盖」,在「其他 env」清单同步补上该变量,并说明:它是整体替换语义而非字段级合并;env 源不支持热更新,改 env 需重启;blockHashConfig.type存在时BLOCK_HASH_STRATEGY失效。可同时给BlockHashConfig加@ToString,使EnvironmentConfigSource:69-73的覆盖日志不再打印对象引用。
- 建议:在表格两行补「可由
- WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致 @
rtp_llm/flexlb/flexlb-sync/src/main/java/org/flexlb/sync/status/WorkerBlockHashConfigResolver.java:25- 建议:按实现修正 javadoc(去掉 "and consistent",改为「仅当没有存活 Prefill worker 上报有效 blockSize 时才使用 PD Fusion;Prefill 上报不一致时保留上次有效值而不降级」);若期望的是 javadoc 描述的语义,则改为「Prefill 不一致时也尝试 PDFUSION」并补对应用例,二者取其一。
- WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置 @
rtp_llm/flexlb/flexlb-sync/src/test/java/org/flexlb/sync/status/WorkerBlockHashConfigResolverTest.java:88- 建议:补一条
worker(64, -1)的 resolver 用例,断言异常被吞掉后 last-known-good 配置仍然保留、resolve()行为符合预期;并为WorkerBlockHashConfig的两个守卫补直接的构造器单测,把「非法 worker status 不污染缓存配置」这一边界语义固化下来。
- 建议:补一条
- FakeConfigSource 在两个模块测试中重复实现 @
rtp_llm/flexlb/flexlb-cache/src/test/java/org/flexlb/cache/hash/VllmBlockHashStrategyTest.java:240- 建议:若确定要跨模块复用该夹具,建议给 flexlb-common 发布 test-jar(或抽一个共享 test fixture 模块)后统一引用;否则在新副本上加一行注释说明「跨模块无法复用、有意复制」,避免后续被误当作可随意删改的重复代码。
- 环境变量用例夹带与本功能无关的格式化改动 @
rtp_llm/flexlb/flexlb-common/src/test/java/org/flexlb/service/config/EnvironmentConfigSourceTest.java:23- 建议:回退
MODEL_SERVICE_CONFIG的无关格式化,仅保留功能相关改动,使该文件的 diff 只反映 blockHashConfig 的新增。
- 建议:回退
Checklist Findings (15 fail / 26 total)
General Principles Checklist
- [6.1] Architecture — 依赖方向:无循环依赖/跨层惊喜 → issue
flexlb-cache 直接使用 CBOR 但未在自身 pom 声明该依赖
CBOR 编解码随BlockCacheKeyCalculator删除迁入 flexlb-cache:主类 :3-4 与VllmBlockHashStrategyTest:3都直接 importcom.fasterxml.jackson.dataformat.cbor.*,但flexlb-cache/pom.xml(已逐行核对,dependencies 见 :22-84)没有jackson-dataformat-cbor,仅靠 flexlb-common 的 compile 传递依赖编译通过。全仓 grep 确认dataformat.cbor现在只出现在这两个 flexlb-cache 文件中,flexlb-common 主源码已无任何 CBOR 引用(只剩 javadoc 里的sha256_cbor字样),其pom.xml:36的该依赖变为「声明未使用」。一旦清理 flexlb-common 的 pom 或改其 scope,flexlb-cache 主代码与测试会直接编译失败。 - [6.1] Architecture — 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全 → issue
文档未登记 BLOCK_HASH_CONFIG 环境入口及其整体替换语义
EnvironmentConfigSource.applyFieldOverrides反射遍历FlexlbConfig.class.getDeclaredFields()(:53)并把字段名转 UPPER_SNAKE 作为环境变量名,parseValue:121兜底JsonUtils.toObject,因此新字段blockHashConfig自动获得BLOCK_HASH_CONFIG(JSON)覆盖能力。但 06 文档表格 :60-61 的两个新条目未标注对应 env,:203 的「其他 env」清单仍只列已废弃的BLOCK_HASH_STRATEGY。运维照文档操作只能用到废弃字段,无法用 env 设置首选项或应急设定 seed;也不知道 env 覆盖是整体替换(会连带把type或hashSeed复位)且不支持热更新(setUpdateListener为空实现,:45)。 - [6.1] Architecture — 分层边界:新概念在正确层级,不泄漏内部 → issue
仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名
本 PR 把org.flexlb.cache.domain.BlockHashConfig(worker 上报的 blockSize/lookaheadTokens)改名为WorkerBlockHashConfig,同时把BlockHashConfig这个简单名复用给语义完全不同的org.flexlb.config.BlockHashConfig(算法 type + seed)。改名传播本身完整(唯一实现WorkerBlockHashConfigResolver、唯一消费方RequestBlockHashService及全部测试已同步,全仓无残留引用),但接口BlockHashConfigResolver仍保留旧词而返回类型已是WorkerBlockHashConfig(:14),其 javadoc 还需专门声明「与org.flexlb.config.BlockHashConfig无关」来消歧;同名不同义会让历史 commit、review diff 与 IDE 自动导入产生歧义,需同时引用两者时只能写全限定名。 - [6.1] Architecture — 可观测性:日志/指标/超时可操作、非噪声 → issue
seed 热更新未失效 Local Standby 自建索引,旧 seed 映射挤占有界容量
LocalStandbyCacheIndex是 FlexLB 自建的 blockHash→worker 反向索引,key 来自 FlexLB 自己算出的 block key(LocalStandbyCacheManager.addRoutedRequestBlocks:161),且容量有界:incrementMappingCountIfBelowLimit:259-265在mappingCount >= maximumEntries时直接拒绝新映射,由LocalStandbyCacheManager:167上报reportLocalStandbyCapacityRejected。seed 热更新后整个 key 空间平移,旧 seed 映射既不再被命中也不会被主动失效,只能等 TTL(:237-249)或压力期的 TTL 缩短与 high-watermark 全扫回收,期间新 seed 映射可能被拒,standby 命中率与容量指标同时失真。改动前 seed 为编译期常量,该路径不存在。 - [6.1] Architecture — 回滚路径:风险行为存在运维回滚手段 → issue
文档删除 PYTHONHASHSEED 语义锚点,运维无从判断 hashSeed 该填什么
改动前 04 文档与BlockCacheKeyCalculator类注释都写明「PYTHONHASHSEED=0语义」(diff :47),改后 :136 仅剩「其hashSeed仅用于 vLLM,默认0」,VllmBlockHashStrategy类注释与 06 文档 :61 也不再提及。全仓 grep 后PYTHONHASHSEED只残留在VllmBlockHashStrategyTest:35的一行测试注释里。而这是本 PR 唯一可热更新的知识点,必须与目标引擎进程实际的PYTHONHASHSEED字符串完全一致才能匹配,填错的唯一后果是静默的全量 cache miss。文档已无法告诉运维该填什么值、从哪里取值,也未说明引擎未设置该变量时无法对齐、变更瞬间既有 key 全失效及如何回滚。 - [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue
seed 热更新瞬间主 hash 与 Local Standby hash 可能跨 seed
当 standby 块大小与主请求不同(reusePrimaryHash=false)时,:75 向localStandbyHashService异步提交 standby 计算、:80 异步执行主 hash,两者共用同一个BlockHashStrategybean(LocalStandbyHashService:152与BlockHashExecutor:92都调用同一实例),而calculateBlockCacheKeys每次调用现场读取volatile noneHash(VllmBlockHashStrategy.java:80)。若 seed 热更新落在两次执行之间,该请求的主 keys 与 standby keys 基于不同 seed,standby 前缀匹配静默全 miss、命中率指标失真;队列积压时窗口可达秒级。影响有界(不造成错误路由),但属请求内不变量被破坏。 - [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue
WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致
javadoc 写「PD Fusion workers are used only when no alive Prefill worker reports a valid and consistent pair」,暗示 Prefill 上报不一致时会退到 PD Fusion。实现findPreferredBlockHashConfigs:107-112只判断prefillConfigs.isEmpty():Prefill 上报了多组不一致值时集合非空,PDFUSION 根本不会被尝试,refreshCachedConfig:93-102只记录 error 并保留 cached 值(首次启动则resolve():68抛IllegalStateException)。文档承诺的降级路径与实际错误语义不符,会误导排障。 - [6.1] Quality — 逻辑变更未混入无关格式化 → issue
环境变量用例夹带与本功能无关的格式化改动
本次仅需为FLEXLB_CONFIG增加blockHashConfig与一条断言,但 diff 第 1196-1198 行同时把MODEL_SERVICE_CONFIG的两行写法压成一行(当前 :23),与本功能无关,会稀释 diff 信号。 - [6.1] Software Engineering — DRY:重复非平凡逻辑被抽取或显式复用 → issue
FakeConfigSource 在两个模块测试中重复实现
flexlb-common 的ConfigServiceTest已有一个近乎相同的FakeConfigSource(name/priority/initialContent/listener/emit全部重复),新副本仅少了loadException/loaded/closed追踪。两份副本会随ConfigSource接口演进而漂移;因分属不同 Maven 模块且未发布 test-jar,当前确实无法直接复用。 - [6.1] Software Engineering — OCP:本地扩展点优先于修改中心逻辑 → issue
废弃字段回退逻辑未收敛到 FlexlbConfig,且新消费点缺 @SuppressWarnings
同文件已有把废弃字段回退封装进配置对象的既有模式(getEffectiveOutstandingUncachedTokensThreshold,:270-278),而本次blockHashConfig.type→ 废弃blockHashStrategy的回退判定放在了 flexlb-cache 的BlockHashStrategyConfiguration:21-26,与同文件模式不一致,未来第二个消费方需复制该判定。另外 Lombok 会把字段上的@Deprecated复制到生成的 getter/setter,因此BlockHashStrategyConfiguration:25与BlockHashStrategyTest:38会新增编译期 deprecation 警告;仓库既有约定是在这类类上加@SuppressWarnings("deprecation")(FlexlbConfigTest:8、CacheAffinityFirstStrategyTest:34、`ShortestTTFTStrategyTe... - [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue
WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置
recordWorkerBlockHashConfig的紧凑构造器对blockSize <= 0与lookaheadTokens < 0抛IllegalArgumentException(WorkerBlockHashConfig.java:20-27),但测试中的worker(0, 1)(:88)会先被WorkerBlockHashConfigResolver:121的cacheStatus.getBlockSize() <= 0过滤掉,构造器守卫永不触达;也没有lookaheadTokens为负的用例。而负 lookahead 会在findBlockHashConfigsFromAliveWorkers:124内抛出,被refresh():76-78的 catch-all 吞掉,导致该轮所有 worker 的配置一起被丢弃。 - [6.1] Tests — 被删除测试有等价替代覆盖 → issue
废弃键 blockHashStrategy 的 JSON 契约覆盖被删除且无等价替代
diff :488-511 显示原用例parsesSglangStrategyFromFlexlbConfigJson断言{"blockHashStrategy":"SGLANG"}可被反序列化为config.getBlockHashStrategy(),本 PR 整体改写为只断言{"blockHashConfig":{...}}。全仓 grep 后该键的 JSON 断言已归零(FlexlbConfigTest亦无任何 blockHash 相关覆盖),仅剩 :38 不经 Jackson 的 setter 调用。该字段虽已@Deprecated,仍是 04/06 文档承诺的回退路径与BLOCK_HASH_STRATEGYenv 的落点。此外 :45 只验证 VLLM 分支注册了监听器,未对称断言 SGLANG 分支不注册,而「hashSeed 对 SGLang 无效」是 BlockHashConfig.java:33 与文档对外声明的运维可见契约。 - [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue
WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置
recordWorkerBlockHashConfig的紧凑构造器对blockSize <= 0与lookaheadTokens < 0抛IllegalArgumentException(WorkerBlockHashConfig.java:20-27),但测试中的worker(0, 1)(:88)会先被WorkerBlockHashConfigResolver:121的cacheStatus.getBlockSize() <= 0过滤掉,构造器守卫永不触达;也没有lookaheadTokens为负的用例。而负 lookahead 会在findBlockHashConfigsFromAliveWorkers:124内抛出,被refresh():76-78的 catch-all 吞掉,导致该轮所有 worker 的配置一起被丢弃。
RTP-LLM Checklist
- [I] 代码质量 — 删除或重命名内部 file、registry entry、model name、metric enum、op binding、plugin symbol 时,必须全仓搜索消费者,并提供替代实现、迁移说明或 smoke 覆盖;只有暴露到 HTTP/RPC/config/persisted format 时才按外部兼容性处理 → issue
仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名
本 PR 把org.flexlb.cache.domain.BlockHashConfig(worker 上报的 blockSize/lookaheadTokens)改名为WorkerBlockHashConfig,同时把BlockHashConfig这个简单名复用给语义完全不同的org.flexlb.config.BlockHashConfig(算法 type + seed)。改名传播本身完整(唯一实现WorkerBlockHashConfigResolver、唯一消费方RequestBlockHashService及全部测试已同步,全仓无残留引用),但接口BlockHashConfigResolver仍保留旧词而返回类型已是WorkerBlockHashConfig(:14),其 javadoc 还需专门声明「与org.flexlb.config.BlockHashConfig无关」来消歧;同名不同义会让历史 commit、review diff 与 IDE 自动导入产生歧义,需同时引用两者时只能写全限定名。 - [I] 代码质量 — 同一功能用统一工具函数 → issue
FakeConfigSource 在两个模块测试中重复实现
flexlb-common 的ConfigServiceTest已有一个近乎相同的FakeConfigSource(name/priority/initialContent/listener/emit全部重复),新副本仅少了loadException/loaded/closed追踪。两份副本会随ConfigSource接口演进而漂移;因分属不同 Maven 模块且未发布 test-jar,当前确实无法直接复用。
Strengths
- 算法搬迁字节级等价:
writeHashSeed/writeBlock/low64Bits/WRITE_MINIMAL_INTS与被删除工具逐行一致,仅把NONE_HASH静态常量改为实例级volatile noneHash;测试保留与 vLLM 交叉验证的 golden(CBOR 编码6130、逐块中间 digest、有符号低 64 位 key),而非只断言最终 Long。 - 领域模型拆分到位:
WorkerBlockHashConfig(worker 上报的blockSize/lookaheadTokens)与org.flexlb.config.BlockHashConfig(配置侧type/hashSeed)职责分离,三处 javadoc 互相点明「不要把对方字段搬过来」,消除原同名类的语义混淆。 - 依赖方向正确:flexlb-common 的
BlockHashConfig:15仅以{@code org.flexlb.cache.domain.WorkerBlockHashConfig}文本提及 flexlb-cache 的WorkerBlockHashConfig,未引入 common → cache 反向模块依赖。 - 回退判定用
blockHashConfig != null && getType() != null(BlockHashStrategyConfiguration:23-25)而非只判对象非空,因此只配hashSeed不会把策略从SGLANG意外拉回VLLM;默认 seed"0"与改动前硬编码一致,未配新字段的存量部署行为不变。 hashSeed选String而非数值,与 vLLM 直接对PYTHONHASHSEED原始字符串做 CBOR text string 编码的语义一致,避免数值化往返导致 seed 失配。- 单次计算内自洽:
noneHash为volatile,calculateBlockCacheKeys:80在循环外只读一次并沿父子链复用,保证同一次calculate的哈希链不会半新半旧;MessageDigest仍走ThreadLocal,未引入共享可变状态。 - 删除迁移干净:被删的 9 个
BlockCacheKeyCalculator*Test用例(seed CBOR 编码、canonical 整数边界、链式 full digest、EAGLE lookahead、丢弃尾部不满块、非法入参、16 线程 320 请求并发)全部等价落位到VllmBlockHashStrategyTest,三篇架构文档与代码同步更新。
| * lookahead tokens, which are obtained from worker status as | ||
| * {@code org.flexlb.cache.domain.WorkerBlockHashConfig}. | ||
| */ | ||
| private BlockHashConfig blockHashConfig; |
There was a problem hiding this comment.
[P1] 嵌套 blockHashConfig 与顶层浅合并冲突,局部推送会静默清空 type 或复位 hashSeed
mergeConfig 对顶层键整体替换(merged.setAll(overrides),ConfigService.java:101-103),嵌套对象不深合并,且显式允许「至少一个字段」的部分推送(:97-99)。两个方向都静默出错:推 {"blockHashConfig":{"hashSeed":"1"}}(正是 docs/06:61 推荐的热更动作、也是 VllmBlockHashStrategyTest:144 的形状)使 type 变 null,下次重启时 BlockHashStrategyConfiguration:23 回落到废弃字段(默认 VLLM),SGLANG 集群被静默切成 vLLM;推 {"blockHashConfig":{"type":...}} 使 seed 复位 "0"(BlockHashConfig.java:35 非空默认值),:53 的 listener 立即重算 noneHash,全集群 block key 与引擎失配、前缀命中率归零。均无异常无日志,与 ConfigServiceTest:148 断言的「缺省字...
建议: 优选把热生效的 hashSeed 提为 FlexlbConfig 顶层字段(与 blockHashStrategy 同级),嵌套对象只保留重启生效的 type,即可直接复用现有逐字段浅合并语义;或让 mergeConfig 对嵌套 ObjectNode 递归深合并(modelServiceConfig/flexlbSyncConsistencyConfig 同样受益)。若必须保留现结构,则让 hashSeed 默认值改为 null 且 listener 收到 null 时保留当前生效值,并在「blockHashConfig 非 null 但 type 为 null」时 log.warn 明示正在回退到废弃字段,同时在 javadoc 与 04/06 文档写明「该对象必须整体推送」。请补两条回归测试:先让 seed 生效为非默认值再只推 type,断言 seed 不变;只推 hashSeed 后断言 type 不变。另建议顺手给 BlockHashStrategyConfiguration:15 的 switch 补 null 兜底(显式推 "blockHashStrategy":null 会以 NPE 形式启动失败,该风险改动前已存在)。
| return blockCacheKeys; | ||
| } | ||
|
|
||
| private void updateHashSeed(String hashSeed) { |
There was a problem hiding this comment.
[P2] seed 与 type 的配置变更全程无日志无指标,非法 seed 静默回退
updateHashSeed 直接覆盖 volatile noneHash,不比较新旧值、不校验空串("" 会原样用作 seed)、不打日志;BlockHashStrategyConfiguration 全类无日志;ConfigService.receiveConfigUpdate 只打印 "Applied FlexLB configuration update from {} source"(:78),不含字段值。seed 与引擎 PYTHONHASHSEED 不一致的唯一表现是命中率静默归零(纯 miss、不报错)与 TTFT 抬升,运维既无法确认当前生效 seed,也无法确认回滚推送是否生效。此外 type 只在 bean 构造时读取一次(:15),运行期修改被完全丢弃且无任何提示,易被误认为「已切换算法」。同 PR 的 WorkerBlockHashConfigResolver:134-138 恰是对照实现:首次 INFO、变化 WARN 打印新旧值。
建议: 在 seed 实际变化(含回落默认 "0")时打印 old→new 并标注是否为默认回落,风格与 WorkerBlockHashConfigResolver 对齐(seed 是配置值非凭据,仅变化时打印不构成热路径噪声),并上报 seed 版本 gauge / 变更 counter 以便与命中率指标做时间对齐;拒绝 blank seed 并保留上次有效值(记 error)。在 blockHashStrategy bean 创建时 log.info 打印最终生效的策略类型及其来源(blockHashConfig.type 还是废弃字段),并在同一 listener 中比较配置 type 与当前活动策略,不一致时 log.warn 提示「算法类型变更需重启才能生效」。建议在既有 cache match 状态查询接口暴露当前生效 type 与 seed。
| */ | ||
| public VllmBlockHashStrategy(ConfigService configService) { | ||
| this(); | ||
| configService.addUpdateListener(config -> updateHashSeed(hashSeed(config))); |
There was a problem hiding this comment.
[P2] seed 热更新未失效 Local Standby 自建索引,旧 seed 映射挤占有界容量
LocalStandbyCacheIndex 是 FlexLB 自建的 blockHash→worker 反向索引,key 来自 FlexLB 自己算出的 block key(LocalStandbyCacheManager.addRoutedRequestBlocks:161),且容量有界:incrementMappingCountIfBelowLimit:259-265 在 mappingCount >= maximumEntries 时直接拒绝新映射,由 LocalStandbyCacheManager:167 上报 reportLocalStandbyCapacityRejected。seed 热更新后整个 key 空间平移,旧 seed 映射既不再被命中也不会被主动失效,只能等 TTL(:237-249)或压力期的 TTL 缩短与 high-watermark 全扫回收,期间新 seed 映射可能被拒,standby 命中率与容量指标同时失真。改动前 seed 为编译期常量,该路径不存在。
建议: 在 seed 变更时联动清理 Local Standby 索引(例如由策略暴露 seed 版本号,LocalStandbyCacheManager 观察到版本变化即清空或按代次隔离 key),使新旧 seed 的映射不共享同一配额。若认为依赖现有 TTL 缩短与 high-watermark 全扫自愈可接受,请在 LocalStandbyCacheIndex 与 docs/04 显式记录该权衡与恢复时间量级(最坏为一个 TTL 窗口),并说明 seed 变更后短期 LOCAL_STANDBY 容量拒绝与命中率下降属预期,便于运维在告警时不误判为故障。
Checklist: [6.1] 可观测性:日志/指标/超时可操作、非噪声
| package org.flexlb.cache.hash; | ||
|
|
||
| import org.flexlb.util.BlockCacheKeyCalculator; | ||
| import com.fasterxml.jackson.dataformat.cbor.CBORFactory; |
There was a problem hiding this comment.
[P2] flexlb-cache 直接使用 CBOR 但未在自身 pom 声明该依赖
CBOR 编解码随 BlockCacheKeyCalculator 删除迁入 flexlb-cache:主类 :3-4 与 VllmBlockHashStrategyTest:3 都直接 import com.fasterxml.jackson.dataformat.cbor.*,但 flexlb-cache/pom.xml(已逐行核对,dependencies 见 :22-84)没有 jackson-dataformat-cbor,仅靠 flexlb-common 的 compile 传递依赖编译通过。全仓 grep 确认 dataformat.cbor 现在只出现在这两个 flexlb-cache 文件中,flexlb-common 主源码已无任何 CBOR 引用(只剩 javadoc 里的 sha256_cbor 字样),其 pom.xml:36 的该依赖变为「声明未使用」。一旦清理 flexlb-common 的 pom 或改其 scope,flexlb-cache 主代码与测试会直接编译失败。
建议: 在 flexlb-cache/pom.xml 显式声明 com.fasterxml.jackson.dataformat:jackson-dataformat-cbor(版本沿用父 pom 的 dependencyManagement,与 flexlb-common 保持一致),并确认 flexlb-common 是否仍需保留该依赖,若已无使用者则一并移除,使依赖声明与实际 import 对齐。
Checklist: [6.1] 依赖方向:无循环依赖/跨层惊喜
| List.of(2164874634404590027L), | ||
| dynamicStrategy.calculate(new int[]{1, 2, 3, 4}, 4, 0)); | ||
|
|
||
| source.emit("{\"blockHashConfig\":{\"hashSeed\":null}}"); |
There was a problem hiding this comment.
[P2] hashSeed 两个 null 回退分支的断言不具判别力,热更完全失效也能通过
断言序列为:初始 seed "1"(:151,期望 2107465152829418342L)→ emit hashSeed:"0"(:154,期望 2164874634404590027L)→ emit hashSeed:null(:160)→ emit blockHashConfig:null(:166),后三步期望值完全相同。第二步已把生效 seed 变成 "0",因此即使后两个 null 场景下 updateHashSeed/hashSeed(config) 压根没被调用、退化为空操作甚至抛异常(receiveConfigUpdate 对 listener 异常仅 log.error 后吞掉,ConfigService.java:79-84),测试同样绿色。这恰是本 PR 最需要被验证、也是 P1 所在的缺省回退语义,当前几乎没有判别力。
建议: 调整 emit 顺序让每步期望值唯一可辨:初始 "1" → emit hashSeed:null → 断言变为 seed "0" 的 2164874634404590027L → 再 emit "1" → 断言回到 2107465152829418342L → emit blockHashConfig:null → 再次断言 2164874634404590027L。这样任何一步 listener 未触发或回退逻辑抛错都会导致断言失败;如需防止异常被吞,可对 listener 回调做单元级直接调用而不经过 ConfigService 的 try/catch。
| } | ||
|
|
||
| @Test | ||
| void producesStableResultsUnderConcurrentLoad() throws Exception { |
There was a problem hiding this comment.
[P2] 并发用例未覆盖「seed 热更新与并发计算交叉」这一新增风险
producesStableResultsUnderConcurrentLoad 使用 :39 无参构造的 strategy(无 ConfigService、seed 恒为默认值),覆盖面与被删除的 BlockCacheKeyCalculatorConcurrencyTest 完全等同,只验证了 ThreadLocal<MessageDigest> 这一既有安全性;appliesInitialSeedThenUpdatesAndFallsBackForMissingConfig 是单线程顺序 emit。本 PR 真正新增的可变状态是 volatile noneHash(配置线程写、hash 线程池并发读),关键不变量「单次 calculate 内 :80 只快照一次 seed、不出现半新半旧的混合哈希链」没有任何用例固定,后续若把 seed 读取移入循环也不会被发现。
建议: 新增并发变体:N 个线程持续对同一长输入调用 calculate,另一线程在 "0" 与 "1" 之间反复 source.emit,断言每次返回结果都严格等于两个 seed 之一的完整期望链(不出现第三种取值或混合链),并断言两个期望值在运行过程中都被实际观察到,以确认用例真的触发了热更新而非空跑;同时锁定配置回调线程复用静态 SHA_256 不干扰计算线程。
| |---|---|---| | ||
| | `modelServiceConfig` | 无(缺失则启动失败) | 模型路由、服务发现、KVCM 与 Optimizer 配置;可由 `MODEL_SERVICE_CONFIG` 覆盖,更新后重启生效 | | ||
| | `blockHashStrategy` | `VLLM` | cache block hash 策略:`VLLM` / `SGLANG`;可由 `BLOCK_HASH_STRATEGY` 覆盖 | | ||
| | `blockHashConfig.type` | 未设置 | 优先的 cache block hash 策略:`VLLM` / `SGLANG`;策略 bean 在启动时创建,变更需重启 | |
There was a problem hiding this comment.
[P3] 文档未登记 BLOCK_HASH_CONFIG 环境入口及其整体替换语义
EnvironmentConfigSource.applyFieldOverrides 反射遍历 FlexlbConfig.class.getDeclaredFields()(:53)并把字段名转 UPPER_SNAKE 作为环境变量名,parseValue:121 兜底 JsonUtils.toObject,因此新字段 blockHashConfig 自动获得 BLOCK_HASH_CONFIG(JSON)覆盖能力。但 06 文档表格 :60-61 的两个新条目未标注对应 env,:203 的「其他 env」清单仍只列已废弃的 BLOCK_HASH_STRATEGY。运维照文档操作只能用到废弃字段,无法用 env 设置首选项或应急设定 seed;也不知道 env 覆盖是整体替换(会连带把 type 或 hashSeed 复位)且不支持热更新(setUpdateListener 为空实现,:45)。
建议: 在表格两行补「可由 BLOCK_HASH_CONFIG(JSON,形如 {"type":"VLLM","hashSeed":"0"})覆盖」,在「其他 env」清单同步补上该变量,并说明:它是整体替换语义而非字段级合并;env 源不支持热更新,改 env 需重启;blockHashConfig.type 存在时 BLOCK_HASH_STRATEGY 失效。可同时给 BlockHashConfig 加 @ToString,使 EnvironmentConfigSource:69-73 的覆盖日志不再打印对象引用。
Checklist: [6.1] 兼容性:外部 HTTP/RPC API、持久数据、配置、环境迁移安全
| * temporarily unavailable or report inconsistent values. | ||
| * <p>For every alive worker, {@code CacheStatus.blockSize} supplies the block size and | ||
| * {@code WorkerStatus.blockHashLookaheadTokens} supplies lookahead. PD Fusion workers are used | ||
| * only when no alive Prefill worker reports a valid and consistent pair. The last valid pair |
There was a problem hiding this comment.
[P3] WorkerBlockHashConfigResolver 的 javadoc 承诺与实现回退条件不一致
javadoc 写「PD Fusion workers are used only when no alive Prefill worker reports a valid and consistent pair」,暗示 Prefill 上报不一致时会退到 PD Fusion。实现 findPreferredBlockHashConfigs:107-112 只判断 prefillConfigs.isEmpty():Prefill 上报了多组不一致值时集合非空,PDFUSION 根本不会被尝试,refreshCachedConfig:93-102 只记录 error 并保留 cached 值(首次启动则 resolve():68 抛 IllegalStateException)。文档承诺的降级路径与实际错误语义不符,会误导排障。
建议: 按实现修正 javadoc(去掉 "and consistent",改为「仅当没有存活 Prefill worker 上报有效 blockSize 时才使用 PD Fusion;Prefill 上报不一致时保留上次有效值而不降级」);若期望的是 javadoc 描述的语义,则改为「Prefill 不一致时也尝试 PDFUSION」并补对应用例,二者取其一。
Checklist: [6.1] 错误语义:fail-fast/retry/fallback/silent 行为显式
| @@ -107,8 +107,8 @@ private void clearWorkerStatuses() { | |||
| modelWorkerStatus.getVitStatusMap().clear(); | |||
There was a problem hiding this comment.
📍 实际位置 rtp_llm/flexlb/flexlb-sync/src/test/java/org/flexlb/sync/status/WorkerBlockHashConfigResolverTest.java:88(不在 diff 展示范围内,就近挂载)
[P3] WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置
record WorkerBlockHashConfig 的紧凑构造器对 blockSize <= 0 与 lookaheadTokens < 0 抛 IllegalArgumentException(WorkerBlockHashConfig.java:20-27),但测试中的 worker(0, 1)(:88)会先被 WorkerBlockHashConfigResolver:121 的 cacheStatus.getBlockSize() <= 0 过滤掉,构造器守卫永不触达;也没有 lookaheadTokens 为负的用例。而负 lookahead 会在 findBlockHashConfigsFromAliveWorkers:124 内抛出,被 refresh():76-78 的 catch-all 吞掉,导致该轮所有 worker 的配置一起被丢弃。
建议: 补一条 worker(64, -1) 的 resolver 用例,断言异常被吞掉后 last-known-good 配置仍然保留、resolve() 行为符合预期;并为 WorkerBlockHashConfig 的两个守卫补直接的构造器单测,把「非法 worker status 不污染缓存配置」这一边界语义固化下来。
Checklist: [6.1] 新逻辑有聚焦单测 + 相关集成/smoke 测试;[6.1] 边界 case 覆盖(空、单元素、最大值)
| void write(CBORGenerator generator) throws IOException; | ||
| } | ||
|
|
||
| private static final class FakeConfigSource implements ConfigSource { |
There was a problem hiding this comment.
[P3] FakeConfigSource 在两个模块测试中重复实现
flexlb-common 的 ConfigServiceTest 已有一个近乎相同的 FakeConfigSource(name/priority/initialContent/listener/emit 全部重复),新副本仅少了 loadException/loaded/closed 追踪。两份副本会随 ConfigSource 接口演进而漂移;因分属不同 Maven 模块且未发布 test-jar,当前确实无法直接复用。
建议: 若确定要跨模块复用该夹具,建议给 flexlb-common 发布 test-jar(或抽一个共享 test fixture 模块)后统一引用;否则在新副本上加一行注释说明「跨模块无法复用、有意复制」,避免后续被误当作可随意删改的重复代码。
Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[I] 同一功能用统一工具函数
| "{\"needConsistency\":true,\"zookeeperConfig\":{\"zkHost\":\"zk:2181\",\"zkTimeoutMs\":10000}}", | ||
| "MODEL_SERVICE_CONFIG", | ||
| "{\"service_id\":\"test-service\",\"role_endpoints\":[]}", | ||
| "MODEL_SERVICE_CONFIG", "{\"service_id\":\"test-service\",\"role_endpoints\":[]}", |
There was a problem hiding this comment.
[P3] 环境变量用例夹带与本功能无关的格式化改动
本次仅需为 FLEXLB_CONFIG 增加 blockHashConfig 与一条断言,但 diff 第 1196-1198 行同时把 MODEL_SERVICE_CONFIG 的两行写法压成一行(当前 :23),与本功能无关,会稀释 diff 信号。
建议: 回退 MODEL_SERVICE_CONFIG 的无关格式化,仅保留功能相关改动,使该文件的 diff 只反映 blockHashConfig 的新增。
Checklist: [6.1] 逻辑变更未混入无关格式化
Background
FlexLB keeps vLLM-compatible block hashing outside the strategy boundary and cannot configure or hot-update the vLLM hash seed. Align the ownership with the SGLang block-hash strategy while preserving legacy configuration compatibility.
Implementation
BlockCacheKeyCalculatorintoVllmBlockHashStrategy.BlockHashConfigwithtypeandhashSeed;blockHashConfig.typetakes precedence and the deprecatedblockHashStrategyremains the fallback.ConfigServicesohashSeeddefaults to0and later config updates atomically apply to subsequent hash calculations.BlockHashConfigtoWorkerBlockHashConfig; document thatblockSizeandlookaheadTokenscome from alive worker status rather than configuration.Compatibility and rollout
blockHashConfigcontinue to select VLLM through the deprecated top-level strategy field.blockHashConfig.typeselects the strategy during bean creation and requires restart to change.blockHashConfig.hashSeedupdates without restart; absent or null nested config falls back to seed0.Verification
./mvnw spotless:check -Pspotless-check./mvnw testPYTHONHASHSEED=1, tokens[1,2,3,4], block size4was read from the vLLM test deployment:2107465152829418342.Explicitly deferred
Rollback
Revert this PR. Legacy
blockHashStrategyremains available as the compatibility fallback.