写在前面
上一篇文章中,我们讲解了网飞当前的架构,但网飞的架构并不是一开始就是这样的,而是不断演进发展才是当前的样子。
这篇文章我们就来讲讲网飞架构的演进过程。
第一阶段:Zuul Gateway + REST API
- 使用 Zuul 作为API网关,
单一前门
- 设备调用网关,网关扇出到多个微服务
而在这个架构下,会遇到一些问题:
不同设备有不同的数据需求
,Web端、App端、TV端所需要的数据结构不一样单一REST API无法满足所有设备
,导致api变多且难以维护,要么获取过多数据,要么需要多次调用- 设备特定的优化需求难以进行
第二阶段:BFF(Backend For Frontend)模式
为了处理不同设备的差异化数据需求和减少网络调用,网飞采用了Backend For Frontend (BFF)
模式。每个前端都有专属的后端。
- UI开发者编写和维护 Groovy脚本
定制化获取数据。
- Groovy 脚本部署在API服务器上,通过 Java 调用gRPC/REST服务,并使用 RxJava 和容错库 Hystrix 处理扇出中的
线程管理和容错问题
。
Groovy 是 后端导向的语言,主要服务于 JVM 平台的后端逻辑、自动化脚本和构建工具链
然而,这种 BFF 模式存在局限性:
- 需要维护大量的脚本,每个终端一个脚本,维护复杂。
- 反应式编程本身学习曲线陡峭且复杂
- UI 开发者不喜欢用 Groovy/Java/RxJava
第三阶段:GraphQL Federation + Java Version 21
- 采用DGS微服务:基于 GraphQL 的 SprintBoot 服务
- API 网关处理GraphQL
GraphQL Federation 取代了 BFF,因为客户端可以通过 GraphQL 精确选择所需的字段,解决了 REST API 过度或不足获取数据的问题
,使得一个 API 可以服务于不同的 UI。这意味着 UI 工程师不再需要进行服务器端开发来获取自己所需要的数据。
Java 版本升级
从 Java 8 迁移到 Java 17 后,得益于G1垃圾收集器的改进,CPU 使用率降低约 20%,这是一个非常恐怖的优化,会省下非常多的云资源。这些jdk的升级其实很难做,需要兼顾每一个第三方包。是一项很繁琐的工作。
举个视频中的例子,网飞处理Spring Boot 3升级中的Jakarta EE 命名空间的变化,Spring Boot 3 是基于JDK 17,并且从javax
切换到了jakarta.ee
。对于应用程序而言,这只是简单的查找并替换。
但对于依赖 javax 并且已经编译成JAR包的老旧库来说,而这些老旧库已经找不到源代码了
但还在使用,运行时仍然会尝试寻找 javax.servlet.Filter。但此时,环境提供的却是 jakarta.servlet.Filter。由于包名和类名不匹配,即使功能相同,JVM也无法找到对应的类,从而导致运行时错误。
针对这种二进制不兼容性
的情况,Netflix利用 Gradle transforms 来解决。通过 Gradle插件工具,在依赖包下载时的 artifact resolution time 进行字节码重写
,相当于二进制的查找并替换,将所有javax 替换为 jakarta。
由于这些API在功能上没有变化,只是命名空间改变,所以这种重写是安全的。而这个工具已作为Netflix Nebula生态系统的一部分开源。
此外网飞目前正测试和推广 Java 21+,Java 21+ 的 ZGC 垃圾收集器(低暂停时间)和虚拟线程(Virtual Threads)特性都非常优秀。 虚拟线程结合结构化并发很有可能完全取代反应式编程(如 RxJava),简化并发代码的开发和调试。