调度器:引入 HostState 级别锁¶
https://blueprints.launchpad.net/nova/+spec/host-state-level-locking
Nova FilterScheduler 实现虽然本质上是多线程的,但访问所有活动线程共享的共享内存 HostState 数据结构时,不使用任何锁。虽然这意味着调度器在负载下做出的大多数决策在内部并不一致,但这对于基本用例来说不一定是一个大问题,因为 Nova 确保即使由于竞争条件,也能通过重试机制维持设定的资源使用策略 [1]。然而,这可能会在一些更复杂的用例中导致问题。一些非详尽的例子包括:高资源利用率、高负载、特定类型的主机和资源(例如 Ironic 节点 [2] 和复杂的资源,例如 NUMA 拓扑或 PCI 设备)。
我们建议修改调度器代码,使用轻量级的事务方法来避免完全锁定,同时减轻一些竞争条件。
问题描述¶
我们的调度器服务本质上是多线程的,因为它当前运行一个使用 EventletExecutor 的 oslo-messaging RpcServer。 这意味着每个用于 select_destinations 的传入 RPC 消息都将在自己的绿色线程中分发。
收到消息后,每个绿色线程将从数据库读取所有 ComputeNode 状态,并可能 [3] 填充内部全局数据结构,该结构保存将用于过滤的主机状态。
进一步地,在选择主机后,每个线程将对选定的对象调用 HostState.consume_from_instance() 方法,该方法将“消耗”所调度实例的资源,从选定的 HostState 对象中。 这等效于 Claims 代码在请求到达 nova-compute 服务后所做的事情,除了不是更新 ComputeNode 表之外,而是更新调度器服务内存中的 HostState 对象。
然而,由于在过滤器函数运行并决定主机通过之后,直到选择单个主机状态之间没有线程互斥。 许多其他并发线程可能已经更新了相同的主机状态。 经典的竞争条件。 一旦考虑到这一点,一些明显的改进方向就出现了。
当我们调用 consume_from_instance() 时,我们基本上是在对主机状态进行资源声明,而该状态可能自过滤器函数决定通过主机以来已经发生了变化。 此时,我们拥有所有信息,可以提前知道声明是否会失败,并尝试选择不同的主机。 这大致等同于重试事务。
值得注意的是,即使我们发现主机似乎会失败,我们仍然可能希望选择它,因为我们即使在刷新周期内,在已选择的计算主机中注册了重试后,也不会丢弃 HostState 上消耗的资源,因此实际上可能是一个假阴性。
需要某种粒度足够细的锁定机制,既不会造成过多的不必要的开销,又能更一致地处理 HostState。
用例¶
没有针对此目的的特定用例。 这是一个内部重构,旨在提高调度器中的数据一致性,从而提高放置决策的整体有效性。
项目优先级¶
是 - 这项工作与调度器相关,是 Liberty 的优先主题之一。
提议的变更¶
首先,使用 Claim 逻辑代替(或在)HostState.consume_from_instance() 将非常有用,因为两者几乎完全重复。
接下来,此蓝图的范围将包括在访问和更新 HostState 字段周围添加同步原语。 一种轻量级的方法是在过滤器中不使用任何同步原语,因为对主机状态的访问是 a) 只读的 b) 通常是按资源进行的。 consume_from_instance 是我们希望确保访问同步的地方,因为一旦选择了主机,就需要消耗资源(请记住 - 许多并发线程可能正在尝试从同一个 HostState 消耗资源),如果任何“声明”失败,则不应消耗任何资源。 使用来自数据库读取后的新值更新主机状态也应同步。
难题的最后一部分是修改 FilterScheduler._schedule() 方法,以考虑到 consume_from_instance() 中的声明失败,并尝试通过过滤器的下一个主机,或者选择忽略内存中的失败并冒着来自计算主机的重试的风险。
值得注意的是,此提议仅关注修复单个 nova-scheduler 进程中线程之间的数据一致性。 运行多个 worker 仍然意味着它们之间的内部状态在从数据库更新之间将是不一致的。 修复此问题不在本提案的范围内。
备选方案¶
我们可以重新设计调度器,以便本规范中讨论的问题变得无关紧要。 此蓝图旨在改进调度器当前实现中的一些明显问题,而不会改变基本设计。
数据模型影响¶
无
REST API 影响¶
无
安全影响¶
无
通知影响¶
无
其他最终用户影响¶
无
性能影响¶
即使此更改后每次请求都会产生同步开销,这可能会降低基本工作负载的平均响应时间,但我完全预计这将在大量请求或整体云容量较低(或特定资源,例如 Ironic 主机)的情况下,大大提高性能,因为它将大大减少发出的重试次数。
其他部署者影响¶
部署者可能需要考虑一些配置选项。 默认值可能会选择以不改变先前行为的方式。
开发人员影响¶
开发人员需要了解调度器中现在存在锁定,并在更改代码时考虑到这一点,尤其是在添加其他资源的情况下。
实现¶
负责人¶
- 主要负责人
<ndipanov>
工作项¶
重构 Claim 类,使其不直接依赖于 resource_tracker,以便它们可以在调度器代码中使用,并可能移出 compute/ 子树
修改 HostState.consume_from_instance() 以使用 Claim 逻辑,并为此获取 HostState 实例范围内的锁。
修改 HostState.update_from_compute_node() 以获取 HostState 实例范围内的锁来更新主机状态。
修改 FilterSchedule._schedule() 方法,以期望 consume_from_instance() 中的声明事务失败并采取适当的操作。
依赖项¶
无
测试¶
正如通常与竞争问题一样,很难提出确定性的测试。 测试将仅限于单元测试,以确保调用了适当的同步原语。
文档影响¶
可能有一个额外的配置选项来启用 consume_from_instance() 的事务性质,并可能还有一个选项来告诉调度器即使本地声明失败也要继续尝试着陆实例。
参考资料¶
历史¶
Liberty 提供的可选部分,旨在每次更新规格说明时描述新的设计、API 或任何数据库模式更新。有助于读者了解随着时间推移发生的变化。
发布名称 |
描述 |
|---|---|
Liberty |
引入 |