可靠配额执行

Launchpad: https://blueprints.launchpad.net/neutron/+spec/better-quotas

以可靠和高效的方式执行网络资源配额,而不改变配额 API 接口。

问题描述

配额执行是一项相对简单的任务。当收到创建一个或多个资源的请求时,会检查可用的资源配额,如果请求无法满足,则向用户返回错误。在 API 请求串行处理的假设下,这可以完美地工作。

不幸的是,当使用多个 REST API worker 时,这个假设不成立。即使只有一个 API worker,但多个 API 服务器在负载均衡器后运行,也同样适用。

在这些情况下,即使可用的配额不足以接受所有请求,两个或多个并发请求也可能通过配额执行。

例如,假设有 2 个 REST API worker,当前的配额使用量为 Nmax - 1,其中 Nmax 是资源配额。然后发送两个请求并分发给 worker。每个 worker 在另一个 worker 完成 API 请求处理之前执行配额检查。两个请求都被接受,导致总资源使用量为 Nmax + 1

因此,利用多个 worker 和批量创建,租户理论上可以创建多达 Nmax * Nworkers 个资源。虽然这种情况不太可能发生,但租户仍然有可能使用比云提供商分配给他们的资源更多的资源。这种状况对云提供商和用户来说都不理想,前者可能会陷入数据中心资源过度订阅的境地,而后者可能会被收取超过预期的费用。

对于 RPC API worker 来说,根本不执行配额。这意味着通过 RPC 创建的资源不执行配额,通常是“内部”端口。虽然对于 DHCP 端口等服务端口不执行配额可能是期望的行为,但这可能不是正确的行为。事实上,这些端口仍然会在 REST API 请求的配额执行中贡献到整体使用量中。

提议的变更

添加“资源预留”的概念,该概念已被其他项目(如 nova 和 cinder)采用。每当配额执行检查成功时,立即将请求的资源数量标记为“已预留”。

无论 API 操作成功与否,都应在操作完成后立即删除预留,理想情况下是在插件调用返回时。如果服务器崩溃,应提供过期时间,以确保过时的预留不会阻止服务器重新启动后的资源分配。

同一租户和同一资源可以同时存在多个预留。配额检查应将所有现有预留添加到当前使用量计数中。

即使有预留,仍然存在一种竞争条件,如果一个 worker 在其他 worker 指定其预留之前执行配额检查,则可能会发生。可以使用无锁算法来强制执行此操作,这些算法会在没有执行条件时重试操作。可以使用数据库完整性约束来实现此目的,而无需使用锁定查询或其他分布式同步结构。

可以通过引入一个仅用于跟踪进行中预留的表来实现这一点。

因此,资源预留过程如下

1) 获取预留设施:将一个 worker 正在尝试为某个租户预留某种资源的特定数量写入适当的数据库表,例如 reservation_locks。

—————————————————* | RESOURCE_TYPE | TENANT_ID | LOCKED_BY | EXPIRATION | PK: RESOURCE_TYPE, —————|———–|———–————* TENANT_ID | network | xxx | worker_0 | some time | —————————————————*

选择的主键可确保两个不同的 worker 无法同时尝试为同一资源预留资源。过期时间戳可确保在服务器故障的情况下忽略过时的锁。

2) 提交事务。如果另一个 worker 尝试为同一租户和资源类型进行另一个预留,这将触发完整性违规错误。

在这种情况下,将尝试步骤 1 几次。尝试之间将有一个指数退避时间。

  1. 执行预留

将预留记录在适当的表中

——————————————–—————-* | RESOURCE_TYPE | TENANT_ID | BOOKING_AMOUNT | EXPIRATION | —————|———–|—————-—————-* | network | xxx | 2 | at some point | ——————————————–—————-*

  1. 释放预留设施

  2. 提交事务

示例

worker 0 worker 1 ———- ———- acquire_resv_facility acquire_resv_facility COMMIT - SUCCESS COMMIT - FAIL (INTEGRITY VIOLATION) do_reservation RETRY release_resv_facility COMMIT - FAIL (INTEGRITY VIOLATION) COMMIT - SUCCESS RETRY - do_reservation - COMMIT - SUCCESS - release_resv_facility - COMMIT - SUCCESS

获取预留设施的表充当集中式锁,仅利用正确强制执行的键,即使在活动/活动集群(如 MySql Galera)中也是如此。但是,该算法应被视为非阻塞的,因为 worker 将始终主动重试执行预留;此外,退避机制应防止饥饿。

值得注意的是,建议的过期时间戳将防止过时的记录阻止获取预留设施或创建“幽灵”资源预留。过期超时时间可以配置,但考虑到当前的 neutron 实现仍然受到 eventlet 触发的数据库死锁的影响,默认至少两分钟是明智的,默认 MySql 设置会阻塞线程大约 50 秒。

数据模型影响

本文档提出了两个新表

表名:bookings 属性:resource_type string tenant_id string booking_amount integer expiration datetime

表名:booking_locks resource_type string tenant_id string locked_by string expiration datetime

REST API 影响

对 API 接口没有影响。

安全影响

拟议的更改不应打开任何可能导致 DoS、泄露租户信息或允许攻击者插入其他租户网络的漏洞。

实施此规范的所有数据库查询都将按租户限定范围,并构建为防止 SQL 注入。

通知影响

无。

其他最终用户影响

无。

性能影响

在配额执行时将执行一些额外的数据库操作。

此更改还可能序列化先前由不同的 worker 并行处理的一些操作。

虽然预计整体性能影响可以忽略不计,但代码审查时将对其进行评估很重要。

还有一个有趣的问题涉及资源使用情况。确实值得探讨每次计数是否优于在创建或删除资源时更新其使用量计数器。为此,应仔细比较 SELECT 查询的成本与在资源创建/删除上添加数据库钩子并执行相应的 UPDATE 查询的成本。性能的收益/损失取决于 GET 操作与 POST/DELETE 操作的相对频率。更新肯定更昂贵,但 SELECT 更频繁。但是,可以实现此规范,而无需更改资源使用量计算,然后将来再回过头来考虑它。

IPv6 影响

无。

其他部署者影响

由于服务端口不再计入资源使用量,部署者理论上可能会期望端口使用量增加。如果考虑到端口通常与实例一起使用,并且此规范不会改变实例的执行标准,则这种增加应该得到控制。

开发人员影响

此规范的实施将附带充分的开发人员文档。

社区影响

没有,我不认为...但你永远无法确定。

备选方案

一个值得考虑的替代方案是使用单个表来管理预留。虽然元组 (resource_type, tenant_id, resource_amount) 不能可靠地用作在 worker 之间序列化预留的主键,但可以将“locked_by”属性添加到此表,并且以下元组可以构成主键

(resource_type, tenant_id, locked_by)

在提交预留时,将删除 locked_by 值,这将允许其他 worker 进行预留。

然而,此解决方案似乎不会在性能和可扩展性方面带来好处,并且还会使代码更难阅读。

即使可以使用锁定查询(例如:SELECT .. FOR UPDATE)语句,该解决方案已被证明不理想,并且在采用活动/活动复制的情况下,对于某些数据库后端而言,也会彻底失败。

此外,可以沿着 Nova 提出的算法构建替代的非阻塞算法 [1]。该算法的优点是对于每个配额执行操作,平均执行 1 个数据库事务,而不是 2 个,因此效率更高。另一方面,发生冲突时重试操作的成本会略高。虽然后一种细节不是很重要,但由于没有资源使用量计数器,因此无法将此替代无锁算法应用于 Neutron。

另一种替代方案是单独的配额授予机构。这是一种可能性,‘Blazar’ [2] 资源预留项目倡导使用这种方法,但对于许多 OpenStack 部署而言,这可能过于复杂。此外,虽然理论上可以使用 Blazar 来实现此目的,但该项目实际上是为预留要在租户之间共享一段时间的资源而设计的。另一方面,‘Boson’ [3] 项目可能代表一个更可行的替代方案,配额管理和执行委托给第三方应用程序。虽然该项目非常有趣,但它尚未处于可以被 Neutron 采用的开发阶段。

最后,也可以通过在 API worker 之间引入分布式锁来解决此问题。可以使用 memcached 或 zookeeper 轻松实现这种分布式协调。但是,如果可以设计一种仅利用数据库完整性的无锁算法,则可能没有必要诉诸分布式协调。

实现

负责人

salv-orlando

工作项

  1. 初步的“刨花”工作 - 重构现有的配额模块

  2. 添加资源预留逻辑并在配额执行中使用它们

  3. 从配额执行中删除服务端口

依赖项

此更改的重大依赖项如下:1) 删除自研 WSGI 框架并随后切换到 pecan 2) 审查插件接口。

虽然上述工作项定义了执行配额执行的新钩子,但它们不会改变配额执行模块的逻辑,因此可以正交地实现它。

测试

Tempest 测试

不会更改 API 接口。

功能测试

将添加适当的功能测试来验证正确的配额执行。由于适当的验证需要触发竞争条件,因此可能需要进行某种故障注入。将单独评估其必要性和可行性。

API 测试

不需要进一步的 API 测试。

文档影响

用户文档

记录服务端口不再计入整体资源使用量。

开发人员文档

由于当前没有配额的开发人员文档,因此很容易:“为配额执行模块编写开发人员文档”

参考资料