Skip to content

Better CPU Binding Implementation in Process-based Runtimes #339

Closed
@jschwinger233

Description

@jschwinger233

现有的 cpu 配额和绑定的 runtime 实现是:

  1. cpu-bind=true: 使用 cpuset.cpus + cpu.cfs_quota_us. 比方说请求 cpu=1.2, bind={1,2}, 落实到 OS 层面就 cpuset.cpus=1,2, cpu.cfs_quota_us=120000;
  2. cpu-bind=false: 使用 cpu.cfs_quota_us. 比方请求 cpu=1.2, 落实到 OS 层面就是 cpu.cfs_quota_us=120000

这造成了几个问题:

  1. cpuset.cpus + cpu.cfs_quota_us 的方案对性能影响非常大. 我做的简单测试, 三进程用这种方案分别绑定 1.2, 1.3, 1.5 cpu, 理论上希望能跑出 4 个 100% 的 cpu, 然而结果是 4 个 cpu 利用率波动在 85%~97%, 而且 cpu throttle 非常频繁.
  2. cpu unbind 的进程没有限制 cpuset.cpus, 可能会影响绑核的进程. Kubernetes 的做法是做一个动态的 share cpu pool, 比如初始时是 0-7, 后来若有进程绑定了 cpu 2, 那么 share pool 变成 0-1,3-7, 并更新到已有的非绑定容器上.
  3. shopee SRE 的需求, 希望 redis 进程能被分配两个 cpu, 一个专属一个共享, 平时 redis 只用专属, bgsave 的时候才使用共享; 同一个 host 上的所有 redis 进程们可以共享同一个 cpu.

因此新的方案是:

  1. 照抄 Kubernetes 的 share pool 方案, 动态把已绑定的 cpu 从里面摘除.
  2. cpu-bind=false 时, cpuset.cpus 指定 share pool, 同时设置 cpu.cfs_quota_us; share pool 更新的时候需要对已有进程的 cpuset.cpus 更新一遍
  3. cpu-bind=true 时, cpuset.cpus 正常设置, 不再限制 cpu.cfs_quota_us, 而是根据碎片核的占比设置 cpu.shares

比如以下是连续请求:

  1. 请求 cpu=1, bind=false, 最终 cpuset.cpus=0-7, cpu.cfs_quota_us=100000
  2. 请求 cpu=1, bind=true, 最终 cpuset.cpus=0, cpu.shares=1024 (1024 是默认 share, 相当于没有修改); 之后请求1创建的进程的 cpuset.cpus 被调整为 1-7.
  3. 请求 cpu=1.2, bind=true, 最终 cpuset.cpus=1,3, cpu.shares=20; 之后请求 1 创建的进程 cpuset.cpus 再次被调整为 2,4-7.
  4. 请求 cpu=1.2, bind=false, 最终 cpuset.cpus=2,4-7, cpu.cfs_quota_us=120000

那么对于 Shopee SRE 的需求, 可以在 redis-zero 保证:

  1. 用户在 web 请求 cpu=1, bind=true
  2. redis-zero 发送给 eru-core 的请求里修改为 cpu=1.01, bind=true
  3. redis 容器被绑上两核, 一个完整核一个碎片核, 对应需求“一个专属核一个共享核”

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions