Uber推NCU标准化计算单元:异构服务器集群CPU分配新方案
Uber 引入了标准化计算单元(Normalized Compute Units,NCU),以便在高度异构的服务器集群中更一致地分配 CPU。这种方法已应用于 Uber 的有状态编排平台 Odin,它会考虑处理器之间的性能差异,而不是将每个 CPU 核心都视为具有相同性能。 Odin 管理着约 500 万个 CPU 核
Uber 提出了一套叫标准化计算单元(Normalized Compute Units,NCU)的做法,目的是让高度异构的服务器集群在分配 CPU 时更一致。这套方法已经用在 Uber 的有状态编排平台 Odin 上。它不再把每个 CPU 核心都当成性能相同,而是把处理器之间的性能差距算进去。
Odin 管理的 CPU 核心约有 500 万个,分布在本地数据中心和公有云里,硬件涵盖多个世代的 x86 以及 ARM 处理器。Uber 发现,最快和最慢的 CPU 核心,单线程性能差距可能接近一倍。结果是,同一个工作负载跑在不同服务器上,需要的物理 CPU 核心数可能差出不少。
这就带来两种相互冲突的风险。按最慢硬件的性能来分配,较快服务器上的算力就白白浪费了;按平均使用量来分配,工作负载迁到较慢服务器时又可能资源不够。有状态工作负载通常没法简单地把流量挪到另一台机器,后一种情况就可能变成可靠性问题。
为此,Uber 把工作负载需要的计算性能,与提供这些性能的物理 CPU 核心数量拆开来处理。
具体做法是,Odin 给每种服务器类型配一个性能系数,较老一代服务器的基准值定为 100。新硬件的数值可能高得多,Uber 表示部分服务器的性能系数接近 190。这些系数最初在硬件准入测试时确定,之后再用生产工作负载的数据做调整。
系统拿这些系数把 CPU 使用量换算成标准化单位。这样一来,Odin 就能算出某个工作负载需要多少计算能力,再根据它所在的那台服务器,折算成不同数量的物理 CPU 核心。
所以,同一项工作负载在性能较快的硬件上拿到的核心可能少一些,在较慢的硬件上拿到的核心则更多。目标不是让核心数量一致,而是让实际算力更接近。
Uber 还发现,不同技术栈对应的性能系数也不一样。比如 Kafka 测试中,ARM、较老的本地硬件和较新的 x86 系统之间性能差距明显。因此该公司会用生产环境的测量数据来更新系数,而不是只看合成基准测试。
无状态服务遇到服务器之间的性能差异,一般靠负载均衡就能化解,流量可以导到负载较低或速度更快的机器上。
有状态系统就没这么灵活了。请求可能必须落到保存特定分片或数据的那台服务器,把请求转到别处解决不了底层资源的问题。
Uber 在文章中提到了部分工作负载,其中 Leader 消耗的 CPU 是 Follower 的十倍以上。Leader 一旦从高性能服务器迁到较慢的服务器,哪怕工作负载本身没变,CPU 使用率也可能大幅上升,进而造成资源压力。
Uber 已把该方案用在 Odin 自动扩缩容工作负载中的约 140 万个 CPU 核心上。以 Kafka 为例,不同服务器类型之间的 CPU 利用率起初相差约 8 个百分点,利用率最高的硬件比最低的高出约 50%。
引入性能感知的资源分配后,这一差距缩到约 3 个百分点,利用率最高的硬件只比最低的高约 10%。Uber 还把 Kafka 的 CPU 总分配量减少了约 8%,缩减大部分发生在性能更快的硬件上,这部分节省可能更有价值。
这套做法和 Kubernetes 用的 CPU 抽象不一样。Kubernetes 里 CPU 请求以核心或毫核表示,不管底层机器规格如何,它都有意把一个 CPU 单位当作绝对数量的 CPU 时间。这种抽象适合定义可移植的工作负载,但它本身没法表达不同代际处理器之间的相对性能。
Uber 的 NCU 模型解决的是另一个略有差别的问题:让资源本身带上性能感知。它关心的不只是往一台机器上放多少工作负载、给某项工作负载分多少 CPU,而是一次 CPU 分配到底代表多少计算能力。