Skip to content

feat(flexlb): configure vllm block hashing - #1357

Open
Martin7-1 wants to merge 2 commits into
feature/flexlb-mu-devfrom
feature/flexlb-vllm-hash-seed-hot-update
Open

feat(flexlb): configure vllm block hashing#1357
Martin7-1 wants to merge 2 commits into
feature/flexlb-mu-devfrom
feature/flexlb-vllm-hash-seed-hot-update

Conversation

@Martin7-1

Copy link
Copy Markdown
Collaborator

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

  • Move the vLLM CBOR/SHA-256 block-hash implementation and its golden/concurrency tests from BlockCacheKeyCalculator into VllmBlockHashStrategy.
  • Add configuration-side BlockHashConfig with type and hashSeed; blockHashConfig.type takes precedence and the deprecated blockHashStrategy remains the fallback.
  • Register the VLLM strategy with ConfigService so hashSeed defaults to 0 and later config updates atomically apply to subsequent hash calculations.
  • Rename worker-status-derived BlockHashConfig to WorkerBlockHashConfig; document that blockSize and lookaheadTokens come from alive worker status rather than configuration.

Compatibility and rollout

  • Existing deployments without blockHashConfig continue to select VLLM through the deprecated top-level strategy field.
  • blockHashConfig.type selects the strategy during bean creation and requires restart to change.
  • Running VLLM strategies apply blockHashConfig.hashSeed updates without restart; absent or null nested config falls back to seed 0.

Verification

  • ./mvnw spotless:check -Pspotless-check
  • ./mvnw test
  • Focused VLLM hash tests cover initial non-default seed, runtime seed update, null seed fallback, and null block-hash config fallback.
  • Golden vector for PYTHONHASHSEED=1, tokens [1,2,3,4], block size 4 was read from the vLLM test deployment: 2107465152829418342.

Explicitly deferred

  • Dynamic switching between VLLM and SGLang strategies remains out of scope.

Rollback

Revert this PR. Legacy blockHashStrategy remains available as the compatibility fallback.

@Martin7-1
Martin7-1 removed the request for review from jianglan89 August 31, 2026 05:55
@Martin7-1
Martin7-1 force-pushed the feature/flexlb-vllm-hash-seed-hot-update branch from 5866740 to fe8eacc Compare August 31, 2026 05:57

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 做递归深合并(会同时改变 modelServiceConfigflexlbSyncConsistencyConfig 的既有语义,需评估)。若两者都不做,至少在 type/hashSeed 由非空变为缺失时打 WARN 并沿用上次解析值;同时改写 06-configuration-and-observability.md 的合并语义说明,明确「推送 blockHashConfig 必须整块提供 typehashSeed,省略等价于显式设为默认值」,并在 ConfigServiceTest 补一条「env 全量 + Nacos 仅 hashSeed」的合并用例把结论固化。

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"。在 blockHashStrategy bean 创建时 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 内逐步失效、期间容量可能被旧映射占用」,使运维对该窗口的命中率下降与容量拒绝告警有预期。
  • 文档删除 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 过期)。
  • 向 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 新 hashSeedcalculate 结果随之变化,端到端覆盖「bean 创建 → 监听注册 → 热更新生效」;注意 ConfigService.CONFIG_SOURCES 是静态容器,需处理测试隔离。
  • 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 的期望值缺少 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 补上「由 vLLM sha256_cbor + PYTHONHASHSEED=1 生成(注明版本/脚本)」的溯源注释。建议再覆盖一个多字符 seed(如 "12345")以锚定 CBOR 文本串的长度前缀编码。
  • 并发用例未覆盖「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() == SGLANGgetBlockHashConfig() == 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() == SGLANGgetHashSeed() 回落 "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;同时把 EnvironmentConfigSourceTestclose() 迁到 @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 == nulltype == 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_CONFIGBLOCK_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-plugintest-jar goal 或独立 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:36flexlb-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:10config/BlockHashConfig.java:15),并需一句「it is independent of …」来否认联想;在 flexlb-cache 内两者都可见(BlockHashStrategyConfiguration.java:3VllmBlockHashStrategy.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 同样只写「vLLM sha256_cbor seed」。
  • [6.1] Architecture — 状态不变量:创建/更新/失败/重试/回滚路径有效 → issue 测试改写 JVM 级静态配置源注册表,清理不在 finally / @AfterEach 内
    ConfigService.register 写入静态 CONFIG_SOURCES(ConfigService.java:23、35),仅 close() 会清空全局所有已注册来源(:117)。用例把 registernew ConfigService()new VllmBlockHashStrategy(configService)(第 145-147 行)全放在 try(第 149 行)之外,而策略构造会立即回调监听器:一旦抛异常 finally 不执行,FakeConfigSource 永久残留在静态表中,污染同 surefire JVM 内后续用例。EnvironmentConfigSourceTest:47close() 同样位于 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(含 loadedclosedloadException 等),本用例在 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)。用例把 registernew ConfigService()new VllmBlockHashStrategy(configService)(第 145-147 行)全放在 try(第 149 行)之外,而策略构造会立即回调监听器:一旦抛异常 finally 不执行,FakeConfigSource 永久残留在静态表中,污染同 surefire JVM 内后续用例。EnvironmentConfigSourceTest:47close() 同样位于 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 行仍声明支持 blockHashStrategyBLOCK_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(含 loadedclosedloadException 等),本用例在 flexlb-cache 又实现了一份精简版(第 240-275 行)。两份实现对 setUpdateListener/load/emit 的语义假设需要人工保持一致;后续若 ConfigSource 接口演进(新增方法或改变 listener 注册契约),两处都要改,容易只改一处而让另一处的测试假设失真。

Strengths

  • 算法搬迁是保真的等价迁移:CBOR 编码、WRITE_MINIMAL_INTS、digest 链与低 64 位取值一致,全部外部锚定向量(4e1195df…c9d58ba6…ddfc07d6…2164874634404590027L)未改动,重构与功能新增没有混在一起改写算法。
  • 单请求内 seed 原子性由结构保证:VllmBlockHashStrategy.java:80volatile noneHash 读入局部 parentHash,seed 中途切换不会撕裂同一次调用的哈希链;MessageDigest 保持 ThreadLocal,配置线程执行 calculateNoneHash 不会污染 hash 线程池实例。
  • 两个同名概念的边界被显式文档化并三处交叉引用:WorkerBlockHashConfig.java:10BlockHashConfigResolver.java:8-10config/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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 嵌套 blockHashConfig 与顶层浅合并冲突,局部推送会静默清空 type 或复位 hashSeed

ConfigService.mergeConfigmerged.setAll(overrides)(ConfigService.java:102)做顶层 key 级替换,嵌套 object 整块被换、缺失子字段回落 POJO 默认值。本 PR 自带用例推送的正是 {"blockHashConfig":{"hashSeed":"1"}}(VllmBlockHashStrategyTest.java:144):该载荷令 type=nullBlockHashStrategyConfiguration.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 做递归深合并(会同时改变 modelServiceConfigflexlbSyncConsistencyConfig 的既有语义,需评估)。若两者都不做,至少在 type/hashSeed 由非空变为缺失时打 WARN 并沿用上次解析值;同时改写 06-configuration-and-observability.md 的合并语义说明,明确「推送 blockHashConfig 必须整块提供 typehashSeed,省略等价于显式设为默认值」,并在 ConfigServiceTest 补一条「env 全量 + Nacos 仅 hashSeed」的合并用例把结论固化。

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review - PR #1357 (non-blocking suggestions)

17 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

return blockCacheKeys;
}

private void updateHashSeed(String hashSeed) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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)));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 向 VLLM 策略注入 ConfigService 的 bean 接线没有测试能拦住回退

生产路径是 BlockHashStrategyConfiguration.java:16new 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 新 hashSeedcalculate 结果随之变化,端到端覆盖「bean 创建 → 监听注册 → 热更新生效」;注意 ConfigService.CONFIG_SOURCES 是静态容器,需处理测试隔离。

List.of(2164874634404590027L),
dynamicStrategy.calculate(new int[]{1, 2, 3, 4}, 4, 0));

source.emit("{\"blockHashConfig\":{\"hashSeed\":null}}");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] hashSeed 两个回退分支的断言不具区分度,热更完全失效也能通过

用例 emit 顺序为初始 seed "1"{"hashSeed":"0"}(第 154 行)→ {"hashSeed":null}(第 160 行)→ {"blockHashConfig":null}(第 166 行),后三次断言期望值都是同一个 seed "0" 的结果 2164874634404590027L。执行到第 3、4 次 emit 时 noneHash 已等于 seed "0" 的值,因此「回退到默认 "0"」与「监听器根本没被回调 / 保留旧值」两种实现给出完全相同结果。而 updateHashSeedhashSeed == 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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] 仓库内出现两个同名 BlockHashConfig,且 Resolver 接口名未跟随重命名

本 PR 的核心动机是拆开两个概念:record 改名 WorkerBlockHashConfig,同时 BlockHashConfig 这个简单名被复用来表示「算法与 seed 配置」。证据是代码自身不得不反复用全限定名消歧(本文件第 10 行、WorkerBlockHashConfig.java:10config/BlockHashConfig.java:15),并需一句「it is independent of …」来否认联想;在 flexlb-cache 内两者都可见(BlockHashStrategyConfiguration.java:3VllmBlockHashStrategy.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";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] 默认 seed "0" 存在三处重复定义

默认 seed 同时写在三处:本行 hashSeed = "0"(配置模型字段初始值)、VllmBlockHashStrategy.java:27DEFAULT_HASH_SEED = "0"blockHashConfighashSeed 为 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 在启动时创建,变更需重启 |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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_CONFIGBLOCK_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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] FakeConfigSource 在两个模块测试中重复实现

ConfigServiceTest.java:244(flexlb-common test)已存在功能更完整的 FakeConfigSource(含 loadedclosedloadException 等),本用例在 flexlb-cache 又实现了一份精简版(第 240-275 行)。两份实现对 setUpdateListener/load/emit 的语义假设需要人工保持一致;后续若 ConfigSource 接口演进(新增方法或改变 listener 注册契约),两处都要改,容易只改一处而让另一处的测试假设失真。

建议:FakeConfigSource 提取为 flexlb-common 的 test-jar 共享 fixture(maven-jar-plugintest-jar goal 或独立 test-support 模块),供两个模块复用;若判断跨模块共享 test fixture 的成本高于收益,至少在两处各加一行注释指向对方,明确「行为需与另一实现保持一致」。

Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用;[I] 同一功能用统一工具函数

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)。在 blockHashStrategy bean 创建时 log.info 打印最终生效的策略类型及其来源(blockHashConfig.type 还是废弃字段),并在同一 listener 中比较配置 type 与当前活动策略,不一致时 log.warn 提示「算法类型变更需重启才能生效」。建议在既有 cache match 状态查询接口暴露当前生效 type 与 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 容量拒绝与命中率下降属预期,便于运维在告警时不误判为故障。
  • 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" → emit hashSeed:null → 断言变为 seed "0" 的 2164874634404590027L → 再 emit "1" → 断言回到 2107465152829418342L → emit blockHashConfig:null → 再次断言 2164874634404590027L。这样任何一步 listener 未触发或回退逻辑抛错都会导致断言失败;如需防止异常被吞,可对 listener 回调做单元级直接调用而不经过 ConfigService 的 try/catch。
  • 并发用例未覆盖「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 不干扰计算线程。
  • 新增的 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 一致的模式:把 registerConfigService/strategy 的构造放进 @BeforeEachclose() 放进 @AfterEach(判空后调用);或至少把 register 之后的所有构造语句一并纳入 try 块,确保任何路径都会清空静态注册表。同一问题也存在于 EnvironmentConfigSourceTest:47configService.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 字符串完全一致(对应 vLLM init_none_hash 语义);引擎未设置该变量时其初始 hash 为随机值、FlexLB 无论取何值都无法命中;变更 seed 后既有 block key 全部失效、命中率会短时下降,回滚方式为改回原 seed 且无需重启;推送 blockHashConfig 时必须同时带全 typehashSeed。同时在 VllmBlockHashStrategy 类注释保留与 kv_cache_utils.py: init_none_hash 的对应关系。

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 加上来源注释(由 vLLM sha256_cbor + PYTHONHASHSEED=1 生成),使 golden 值可追溯、可复算。
  • 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 都直接 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 主代码与测试会直接编译失败。
  • [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 覆盖是整体替换(会连带把 typehashSeed 复位)且不支持热更新(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-265mappingCount >= 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,两者共用同一个 BlockHashStrategy bean(LocalStandbyHashService:152BlockHashExecutor: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():68IllegalStateException)。文档承诺的降级路径与实际错误语义不符,会误导排障。
  • [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 已有一个近乎相同的 FakeConfigSourcename/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:25BlockHashStrategyTest:38 会新增编译期 deprecation 警告;仓库既有约定是在这类类上加 @SuppressWarnings("deprecation")FlexlbConfigTest:8CacheAffinityFirstStrategyTest:34、`ShortestTTFTStrategyTe...
  • [6.1] Tests — 新逻辑有聚焦单测 + 相关集成/smoke 测试 → issue WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置
    record WorkerBlockHashConfig 的紧凑构造器对 blockSize <= 0lookaheadTokens < 0IllegalArgumentException(WorkerBlockHashConfig.java:20-27),但测试中的 worker(0, 1)(:88)会先被 WorkerBlockHashConfigResolver:121cacheStatus.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_STRATEGY env 的落点。此外 :45 只验证 VLLM 分支注册了监听器,未对称断言 SGLANG 分支不注册,而「hashSeed 对 SGLang 无效」是 BlockHashConfig.java:33 与文档对外声明的运维可见契约。
  • [6.1] Tests — 边界 case 覆盖(空、单元素、最大值) → issue WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置
    record WorkerBlockHashConfig 的紧凑构造器对 blockSize <= 0lookaheadTokens < 0IllegalArgumentException(WorkerBlockHashConfig.java:20-27),但测试中的 worker(0, 1)(:88)会先被 WorkerBlockHashConfigResolver:121cacheStatus.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 已有一个近乎相同的 FakeConfigSourcename/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" 与改动前硬编码一致,未配新字段的存量部署行为不变。
  • hashSeedString 而非数值,与 vLLM 直接对 PYTHONHASHSEED 原始字符串做 CBOR text string 编码的语义一致,避免数值化往返导致 seed 失配。
  • 单次计算内自洽:noneHashvolatilecalculateBlockCacheKeys: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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 形式启动失败,该风险改动前已存在)。

@LLLLKKKK LLLLKKKK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review - PR #1357 (non-blocking suggestions)

19 条 P2/P3 建议,不阻塞合并。阻塞判定与完整摘要见上一条 review。

return blockCacheKeys;
}

private void updateHashSeed(String hashSeed) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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)));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] seed 热更新未失效 Local Standby 自建索引,旧 seed 映射挤占有界容量

LocalStandbyCacheIndex 是 FlexLB 自建的 blockHash→worker 反向索引,key 来自 FlexLB 自己算出的 block key(LocalStandbyCacheManager.addRoutedRequestBlocks:161),且容量有界:incrementMappingCountIfBelowLimit:259-265mappingCount >= 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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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}}");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 在启动时创建,变更需重启 |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 覆盖是整体替换(会连带把 typehashSeed 复位)且不支持热更新(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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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():68IllegalStateException)。文档承诺的降级路径与实际错误语义不符,会误导排障。

建议: 按实现修正 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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 实际位置 rtp_llm/flexlb/flexlb-sync/src/test/java/org/flexlb/sync/status/WorkerBlockHashConfigResolverTest.java:88(不在 diff 展示范围内,就近挂载)

[P3] WorkerBlockHashConfig 的参数校验分支在测试中不可达,负 lookahead 会丢弃整轮配置

record WorkerBlockHashConfig 的紧凑构造器对 blockSize <= 0lookaheadTokens < 0IllegalArgumentException(WorkerBlockHashConfig.java:20-27),但测试中的 worker(0, 1)(:88)会先被 WorkerBlockHashConfigResolver:121cacheStatus.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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] FakeConfigSource 在两个模块测试中重复实现

flexlb-common 的 ConfigServiceTest 已有一个近乎相同的 FakeConfigSourcename/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\":[]}",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] 环境变量用例夹带与本功能无关的格式化改动

本次仅需为 FLEXLB_CONFIG 增加 blockHashConfig 与一条断言,但 diff 第 1196-1198 行同时把 MODEL_SERVICE_CONFIG 的两行写法压成一行(当前 :23),与本功能无关,会稀释 diff 信号。

建议: 回退 MODEL_SERVICE_CONFIG 的无关格式化,仅保留功能相关改动,使该文件的 diff 只反映 blockHashConfig 的新增。

Checklist: [6.1] 逻辑变更未混入无关格式化

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants