超大规模 Java 平台:从联邦 GraphQL 到 JVM 默认项#
Netflix 的流媒体业务把「发现内容」与「播放内容」拆成两条技术路径:前者以联邦 GraphQL 为对外数据面,后者走 Open Connect 等专用栈(演讲者观点,未在本文展开)。对 Java 工程师更有参照价值的是前者——数千个 Spring Boot 服务、开源 DGS 框架、以及把 GC、虚拟线程、框架升级做成「平台默认项」而非团队各自摸索的实践。
本文不复述会议流程,而是把可迁移的决策点拆开:协议如何分工、平台如何在 Boot 上收敛行为、测试与迁移如何控制上下文体积、JVM 如何把尾延迟与调用超时联动、以及 Gen AI 如何嵌进已有 Spring 服务而不交出编排权。标为演讲者观点的条目(应用规模、内部 starter 坐标、Gutenberg/Hollow 运行时、IPC 毫秒级配置等)无法在公开环境复现;标为可核对的条目附官方文档、JEP 或开源仓库。

图:开场幻灯片标题「How Netflix uses Java」与「2026 edition」字样。
联邦 GraphQL:网关、DGS 与 gRPC 的分层#
为什么#
客户端需要一次请求拉齐首页(Lolomo)、影片元数据、图片等多域数据;各域团队又要独立发布。联邦构图把多个 subgraph 合成统一 supergraph,由网关做查询计划与扇出(Apollo Federation 说明)。演讲者观点:Netflix 在网关与各 DGS(Domain Graph Service) 之间用 GraphQL,DGS 再经 gRPC 扇出到订阅、推荐、观看历史等后端;播放路径不在此栈。
机制与约束#
- 对外契约:GraphQL Over HTTP 要求服务器接受 POST,客户端可用
Accept协商application/graphql-response+json与application/json。 - 域边界:每个 DGS 拥有自己的 schema 片段;联邦通过
_entities(representations: …)等机制拼接字段(幻灯片示例)。 - 服务间:gRPC 以
.proto的service/rpc建模,客户端可设 deadline(gRPC Core concepts)。

怎么做(最小示例)#
POST /graphql HTTP/1.1
Content-Type: application/json
Accept: application/graphql-response+json, application/json;q=0.9
{"query":"query Home($id: ID!) { lolomo(profileId: $id) { rails { id } } }","variables":{"id":"..."}}
@DgsComponent
public class LolomoDatafetcher {
@DgsQuery
public List<Rail> lolomo(@InputArgument String profileId) {
return catalogClient.railsForProfile(profileId);
}
}
常见误区#
把联邦网关当成「万能 BFF」而在网关里写业务逻辑;或让 DGS 直接访问过多下游导致扇出爆炸。联邦的价值在 schema 所有权 与 可组合的查询计划,不在单点聚合所有 SQL。另一个隐蔽问题是:客户端查询深度不受控时,网关生成的查询计划可能在多个 subgraph 上重复拉取同一实体——需要在 schema 设计与 @key / @requires 使用上约束 N+1,而不是仅靠加大线程池硬扛。

图:架构示意含「GraphQL Federated Gateway」、LOLOMO / Movie / Images DGS 及 gRPC 下游。
Paved Road:在 Spring Boot 上叠加平台能力#
为什么#
演讲者观点:约 3000–4000 个 Java 应用共用开源 Spring Boot,再叠加内部 Spring Boot Netflix 模块。若每个团队自行集成安全、动态配置、可观测、gRPC 客户端,平台无法统一升级,事故面也分散。
机制与约束#
Spring Boot Starters 约定 spring-boot-starter-* 命名;第三方应使用 thirdparty-spring-boot-starter 等形式。平台侧通过 auto-configuration 注入行为;复杂场景辅以 EnvironmentPostProcessor、BeanFactoryPostProcessor(演讲者观点,如 gRPC 客户端注册)。幻灯片中的 Paved Road 指共享的产品、实践与标准集合,供提供方与消费方对齐。
怎么做(示意)#
dependencies {
// 内部坐标未公开;结构对齐官方 starter 模式
implementation("com.netflix.spring:netflix-spring-boot-starter-security")
implementation("com.netflix.spring:netflix-spring-boot-starter-observability")
}
spring:
application:
name: my-service
常见误区#
在业务模块里复制平台 starter 已提供的 Bean;或绕过 paved road 引入未审计的 HTTP/安全栈,导致升级时无法批量迁移。平台团队也常遇到反模式:把「可选扩展点」做成静默覆盖默认 Bean,使应用在升级 minor 版本时行为突变——对外应像官方 starter 一样,用条件注解与配置属性明确生效边界。

图:幻灯片「The paved road」及对共享标准与工程杠杆的说明。
集成测试:全栈真实性与启动成本的平衡#
为什么#
GraphQL DataFetcher 到数据库的路径若只用 mock,回归会漏掉序列化、事务与权限组合问题。演讲者观点:主流做法是以 @SpringBootTest 做集成测试,并显式缩小加载范围。
机制与约束#
classes属性:只加载列出的@Configuration/ 组件(Javadoc)。- 官方 test slice(如
@WebMvcTest)只引导 Web 层子集。 - Netflix 侧:
@EnableDgsTest、@EnableJooqTest(后者在公开仓库未检索到,视为内部切片)、Testcontainers 管外部依赖(演讲者观点)。
@SpringBootTest(classes = {IncidentsDatafetcher.class, JooqRepository.class})
@EnableDgsTest
@EnableJooqTest
class IncidentsDatafetcherTest {
@Autowired DgsQueryExecutor queryExecutor;
}
常见误区#
每个用例 @SpringBootTest 却不写 classes,导致全量上下文缓存仍慢;或在 slice 测试里断言了未加载的安全过滤器行为。

图:「Testing with Spring Boot」— @SpringBootTest(classes=…) 与 @EnableDgsTest 示例。
Boot 2→3:依赖解析期的 Jakarta 改写#
为什么#
Spring Boot 3.0 迁移到 Jakarta EE(如 Servlet 6.0)。生态里仍有仅含 javax.* 的旧 JAR,形成「库等应用、应用等库」僵局。
机制与约束#
Gradle Artifact Transforms 在依赖解析末段改写产物。Netflix 开源的 Nebula Jakarta EE Migration Plugin(插件 ID:com.netflix.nebula.jakartaee-migration,可核对)在解析时对 legacy JAR 做包名迁移,底层使用 tomcat-jakartaee-migration。源码与构建脚本侧用 OpenRewrite UpgradeSpringBoot_3_0。演讲者观点:历时约两年基本完成全员 Boot 3;幻灯片写明「Fully on Spring Boot 3 now」。
plugins {
id("com.netflix.nebula.jakartaee-migration") version "2.0.1" // 版本以 Portal 为准
}
核实说明:公开插件 ID 为
com.netflix.nebula.jakartaee-migration,与部分口述中的com.netflix.jakarta-transform不一致,以 Gradle Plugin Portal 为准。
常见误区#
把字节码改写当成「可以永远不改源码」;API 语义变更、构建脚本、测试容器镜像仍须 OpenRewrite 或人工处理。仅改包名、实现未变的 JAR 才适合 transform。

图:「Spring Boot 2 -> 3」— Gradle transform、OpenRewrite 与「Fully on Spring Boot 3 now」。
API 形态:数据、方法与边缘 REST#
为什么#
同一组织要同时服务「灵活取数的客户端」与「固定契约、低延迟的服务间调用」。一种协议包打天下会在某一侧过度妥协。
机制与约束(演讲者观点 + 公开技术对照)#
| 场景 | 选型 | 心智模型 |
|---|---|---|
| 对外 / BFF 类 | 联邦 GraphQL + DGS | Think in data |
| 服务间 RPC | gRPC | Think methods |
| 极简、短期暴露 | REST | 幻灯片以墓碑图示「REST IN PEACE」— 组织策略,非规范结论 |
service Catalog {
rpc GetShow(GetShowRequest) returns (Show);
}
常见误区#
在 gRPC 服务上再包一层 GraphQL「方便调试」却不做 schema 治理;或用 REST 承载长期演进的领域 API 导致版本碎片。

图:「DGS - GraphQL」对比 GraphQL 与 gRPC 心智模型,REST 标注为淘汰方向(演讲者观点)。
DGS 与 Spring for GraphQL:执行管线合一#
为什么#
维护两套 GraphQL 执行栈会增加迁移成本与测试分歧。DGS 文档写明已与 Spring for GraphQL 内部整合:查询执行由 ExecutionGraphQLService 承担,DgsQueryExecutor 作为代理(DGS Spring GraphQL Integration)。
机制与约束#
- 生产:仍可用
@DgsQuery、@InputArgument等注解(可核对 + 幻灯片 OCR)。 - 测试:
@EnableDgsTest+DgsQueryExecutor,可executeAndExtractJsonPath(Query Execution Testing)。演讲者观点:秒级反馈,无需完整 Boot 启动。
@DgsQuery
public List<Show> search(@InputArgument SearchFilter filter) {
return showsRepository.allShows().stream()
.filter(s -> s.getTitle().toLowerCase()
.startsWith(filter.getTitle().toLowerCase()))
.toList();
}
@SpringBootTest(classes = {LolomoDatafetcher.class, ShowsRepository.class, ArtworkService.class})
@EnableDgsTest
class LolomoDatafetcherTest {
@Autowired DgsQueryExecutor dgsQueryExecutor;
}
常见误区#
混用 Spring GraphQL 与 DGS 两套编程模型(官方文档明确不建议);或在 @EnableDgsTest 中仍加载全应用却声称「轻量测试」。

图:幻灯片「Now fully integrated with Spring for GraphQL」与 @EnableDgsTest / DgsQueryExecutor 代码。
JDK 水位与 Generational ZGC#
为什么#
演讲者观点:公司最低 JDK 17,多数服务跑 21 或 25(具体比例未公开)。在大型堆 + 极短 IPC 超时 下,G1 可能出现约 1.5s 级 STW,触发超时、重试与集群负载放大;切换 Generational ZGC 后 pause 与 IPC client errors 显著下降,并已成为默认 GC 策略(演讲者观点;与 OpenJDK 默认化方向一致,见下)。
机制与约束(可核对)#
- JEP 439(JDK 21):
-XX:+UseZGC -XX:+ZGenerational。 - JEP 474(JDK 23):Generational ZGC 为 ZGC 默认模式。
- JEP 490(JDK 24):移除非分代 ZGC。
# JDK 21
java -XX:+UseZGC -XX:+ZGenerational -jar app.jar
# JDK 23+ 仅 -XX:+UseZGC 即默认分代(见 JEP 474)
java -XX:+UseZGC -jar app.jar
无法核对:幻灯片中的毫秒级 IPC 配置、Netflix 是否全量默认 ZGC。JEP 439 对比 G1 时指出 G1 pause 可从毫秒到秒级——这与「尾延迟敏感 + 短超时」叙事方向一致,但不能替代 Netflix 内部监控曲线的因果证明。
常见误区#
仅看平均 pause 而忽略 P99;或在未调整超时与重试策略时单独换 GC,仍把重试风暴归因于「下游不稳定」。换 ZGC 后 CPU 占用与分配速率可能变化,容量规划应连同堆大小与 -XX:SoftMaxHeapSize 等参数一并回归,而不是只盯 pause 曲线。

图:「Generational ZGC: The new default」— G1 约 1.5s pause 与 IPC 超时、重试的关联(演讲者观点)。

图:「More predictable GC -> lower error rates」— Max GC pause 与 IPC errors by endpoint 时间序列。
虚拟线程:框架开关与 pinning 回滚#
为什么#
阻塞式 I/O 占主导时,平台线程池容易成为瓶颈。JEP 444 虚拟线程 用轻量线程映射大量阻塞调用。演讲者观点:曾因 thread pinning 在较早 JDK 上引发生产死锁而大规模回滚;修复后再通过 Tomcat、Spring 默认执行器、DGS DataFetcher 等框架层开启,业务代码零改动(尚无 rollout 后量化性能数据)。
机制与约束#
- Spring Boot 3.2+:
spring.threads.virtual.enabled=true(DGS Virtual Threads)。 - DGS:
dgs.graphql.virtualthreads.enabled=true使用户 DataFetcher 跑在新虚拟线程。 - Pinning:JEP 491 在 JDK 24 交付,减轻
synchronized导致的 pinning(可核对)。幻灯片写「JDK 25」— 与 JEP 491 的 Release 字段不一致,实施应以目标 JDK 发行说明为准。
常见误区#
在 pinning 未缓解的 JDK 上全量打开虚拟线程;或忽视 DGS 文档 对 ThreadLocal(追踪、MDC、安全上下文)的警告。

图:「Virtual Threads (JDK 25)」— Tomcat connectors、@DefaultExecutor、DGS data fetchers 与「Needs context propagation!」。
结构化并发与上下文传播#
为什么#
虚拟线程之上若用 StructuredTaskScope 并行 fork,子任务默认不复制 Spring Security、Micrometer 等 ThreadLocal 依赖的上下文(演讲者观点)。代码看起来正确,鉴权与追踪却在子任务中丢失。
机制与约束#
- JEP 505(JDK 25 预览):
StructuredTaskScope.open()等 API 需--enable-preview。 - JDK API 支持
Configuration.withThreadFactory(ThreadFactory);Scoped Values 理想但难迁移存量框架(演讲者观点)。 - 可行方向:Micrometer Context Propagation 的
ThreadLocalAccessor等在自定义ThreadFactory中复制上下文(工程方向可核对;Netflix 具体实现未公开)。
// JEP 505 风格示例;inheritContext 为演讲伪代码,非 JDK 标准 API
try (var scope = StructuredTaskScope.open(
StructuredTaskScope.Configuration.newBuilder()
.withThreadFactory(ctxAwareFactory)
.build())) {
scope.fork(() -> downstreamClient.call());
scope.join();
}
常见误区#
把 fork 当成普通 ExecutorService.submit 而不处理失败传播;或假设 Scoped Values 会自动替代所有 ThreadLocal 库。

生产内嵌 Gen AI:受控的 agentic workflow#
为什么#
「用 AI 写代码」与「线上 Java 服务调用 LLM」是不同问题。后者需要可审计、可限流、步骤可预期的编排(演讲者观点)。幻灯片 Agentic Workflows 强调:工作流由工程师掌控,LLM 只参与部分步骤,在引入 AI 的同时保持确定性与可控性(对照 Anthropic — Building effective agents)。
机制与约束#
Spring AI 提供 ChatClient、Tool Calling、Model Context Protocol (MCP) 等构件;「agentic workflow」本身不是 Spring AI 定义的术语。
@RestController
class OpsAiController {
private final ChatClient chatClient;
@PostMapping("/internal/ai/summarize")
String summarize(@RequestBody Incident incident) {
return chatClient.prompt()
.user("Summarize mitigations for: " + incident)
.call().content();
}
}
常见误区#
把 coding agent 直接连生产写路径;或未对 tool call 做权限边界与超时,把 LLM 不确定性扩散到数据面。

图:「Agentic Workflows」—「You keep control of the workflow」与「Adding AI while keeping control and determinism」。
启动剖析:从慢 Bean 到可执行的改造建议#
为什么#
Spring 启动慢时,仅列出耗时 Bean 对第三方库往往不够 actionable。演讲者观点:内部启动分析服务在剖析数据上叠加 agentic workflow,结合库源码给出「问题类 / Maven 坐标 / 是否应异步化」等建议;实现基于 Spring + Spring AI。
机制与约束(演讲者观点 + 幻灯片 OCR)#
控制台 UI(OCR 出现 com.netflix.gusto.Gusto,口述未明确产品名)展示 Startup Analysis Report 与 Top Recurring Bean Bottlenecks (Application Starts)。典型条目:
evoMetadataCachedClient— Evolution Metadata Client;gutenbergSubscriber.startSubscriber()在构造器中同步执行(演讲者观点:连接 Gutenberg 拉元数据)。AssetArrivalDatesClientImpl— 对 Netflix Hollow / Cinder 消费者buildAsync().join()阻塞加载并建索引。
Hollow 公开定位为高性能只读内存数据集传播(com.netflix.hollow:hollow)。典型耗时 12s–58s(演讲者观点,outlier 更高)。
@Bean
@Lazy
EvoMetadataCachedClient evoMetadataClient(EvoMetadataProperties p) {
return new EvoMetadataCachedClient(p); // 避免构造器同步拉全量
}
常见误区#
对所有慢 Bean 一律 @Lazy 而不改库内同步初始化;或忽略启动剖析与运行时热路径的差异。

图:「Top Recurring Bean Bottlenecks (Application Starts)」— 排名第 1 的 evoMetadataCachedClient — Evolution Metadata Client。
Boot 4 与平台侧升级编排(边界说明)#
演讲者观点:下一跳 Spring Boot 4 计划由平台集中 headless 运行 Claude Code,自建工作流引擎(多段 prompt、每步独立 subagent、checkpointing 保留 git 与对话状态)。Claude Code CLI 的 -p(print / 非交互)与 Checkpointing 文档支持「非交互」概念,不等于 Netflix 内部 netflix-upgrade-runner 等实现(未核实)。幻灯片背景可见「Boot 4.0 / Spring Fra…」字样,细节待官方发布说明为准。
未展开:演讲仅预告 Project Leyden / AOT 由同事另场介绍,本文不臆测 Netflix 落地。
参考与延伸阅读#
- GraphQL 入门
- GraphQL Over HTTP 草案(POST 与媒体类型)
- Apollo Federation 仓库说明(subgraph 与网关)
- gRPC 核心概念(service/rpc/deadline)
- Netflix DGS 文档
- DGS 与 Spring for GraphQL 整合
- Spring Boot 应用测试(含 test slice)
- Spring Boot 3.0 发布说明(Jakarta EE)
- Nebula Jakarta EE Migration Gradle 插件
- OpenRewrite:升级到 Spring Boot 3.0 配方
- JEP 439:Generational ZGC
- JEP 444:Virtual Threads
- JEP 491:消除虚拟线程 pinning(JDK 24)
- DGS:虚拟线程与 ThreadLocal 注意事项
- Spring AI 参考手册(ChatClient / MCP)



