Volume state enforcer¶
https://blueprints.launchpad.net/cinder/+spec/cinder-state-enforcer
Cinder 中并发资源访问是一个导致资源损坏的问题,当多个 Cinder 入口点(例如 API 和管理器)同时修改资源时,就会发生这种情况。在 Icehouse 中,为了在资源被多个函数同时处理时排队请求,在管理器函数周围增加并使用了一些锁(这可以阻止其中一个操作同时修改底层资源)。不幸的是,这更像是一种重锤方法,掩盖了问题的症状,并且在调试时很难看出锁后面排队了哪些请求(或者为什么会发生死锁,如果发生的话)。
为了缓解并希望解决这个问题,我们将尝试以不同的方式攻击这些问题,将一个允许的状态转换表集成到 create_volume 工作流中,并进行战略性状态转换,并在这些状态转换不允许时中止/报错。未来这将有助于为其他工作流创建一组具体且定义明确的状态和转换(并且在查看代码和调试期间,将清楚地了解哪些转换可以同时进行以及哪些转换正在积极发生)。
问题描述¶
问题的高级描述
并发资源修改,不好 (EOM)。
更详细的描述
为了防止同时修改资源,Cinder 中正在添加锁,例如在
create_volume, attach_volume,delete_volume, detach_volume...中,管理器会获取一个名为volume_id, f.__name__的外部锁。这有助于使管理器更安全地访问并发资源,但最初的目标是将其作为解决更广泛问题的临时解决方案。此机制的一个问题是它没有使用 DLM(分布式锁管理器),而是只使用本地文件系统锁。这意味着一个 cinder-api 服务可以在第二个修改正在进行时修改资源(或发起请求以执行此操作)。当只有一个管理器处于活动状态时,这会起作用(因为其中一个正在进行的请求会备份到外部锁后面)。这解决了单个主管理器正在运行时的问题;但这不是典型的部署模式,不应将其推荐为部署和运行 Cinder 的方式(它应该是水平可扩展的,以便可以有 X 个活动管理器,其中 X > 1)。我们需要另一种可以水平扩展但也能解决相同最终目标(同时禁止 X 个实体修改资源)的解决方案。
由于此问题的范围更大(它适用于作用于资源的所有/大多数操作),我们必须从某个地方开始,因此我们将从研究如何在 create_volume 工作流中实现这一点开始。这引发了一个更大的问题,即如何以逐步的方式进行此更改,因为其他操作仍然依赖于锁,并且混合状态转换和锁获取技术可能不会导致正确的解决方案。我们必须探索如何以逐步的方式进行此操作,同时又不使 Cinder 更加不稳定。
用例¶
提议的变更¶
不要在管理器进程中获取本地文件系统锁,而是将锁的概念重构为一组允许和不允许的状态转换(这在概念上与锁使用的内部机制类似)。
让我们举一个简化的例子,说明这如何工作
当请求创建卷时,会为该卷创建数据库记录,在该数据库记录中,有一个字段称为 status(实际上存在多个这些状态字段,将来可能应该删除这些字段),用于向用户报告创建卷请求的状态,因为它在 Cinder 的各个组件(API、调度器和管理器)中移动。
该状态本身具有预期的转换图,并且是确定 Cinder 卷创建请求经过的更大状态转换的起点(以及允许经过的状态)。本提案建议不要覆盖此 status 字段,而是将 Cinder 的数据存储层扩展为使用一个新的 resource_states 表。它可能由其他形式的表表示,具体取决于数据存储的位置(如果使用 zookeeper,它将表示为资源树),我们必须强制执行的唯一约束是,我们可以以单个原子操作原子地获取和更新资源的给定状态。
潜在的模式可能如下所示
Resource |
State |
Transitioned on |
|---|---|---|
7c92ee46-7a2e-4183-99c5-909f3d46a90e 7c92ee46-7a2e-4183-99c5-909f3d46a90e 7c92ee46-7a2e-4183-99c5-909f3d46a90e 7c92ee46-7a2e-4183-99c5-909f3d46a90e |
CREATING_DB SCHEDULING CREATING_VOL NULL/None |
2014-05-22T15 2014-05-23T15 2014-05-23T15 2014-05-24T15 |
然后将使用此表结构(使用 NULL 状态来确定请求何时已完成其允许的状态转换)来确定 API 级别(在接受请求之前)资源当前正在被使用,并且 API 服务器然后尝试启动到所需状态的转换(例如,DETACHING),并且如果此转换允许(通过查看最后已知状态),则它可能会成功或失败地执行此转换。如果成功,它将继续执行所需操作的其余工作流(后续转换将在工作流的其余部分进行,如果需要,最终转换将转换为 NULL/None,以表示操作已完成)。如果转换被禁止/失败,API 请求将被拒绝,并且不允许操作继续进行(将来,可以放宽此模型,以允许同时进行状态转换,对于某些操作来说这是有意义的)。
为了实现这一点,在 create_volume 操作中,使用了 taskflow,这有助于分解卷创建经过的工作流(它也使得在进程崩溃时可以从先前的状态恢复成为可能)。这种分解使状态转换应该发生的位置以及状态转换变得明显(或更明显)。建议的路径是在工作流中添加新的节点,这些节点将在粒度级别执行和验证这些状态转换,以便有用且有意义(转换表对于试图确定 Cinder 内部发生的事情的运算符和开发人员也很有用)。当这与 taskflow 关于其自身内部 states(通过 notifications)的通知相结合时,可以很容易地了解 Cinder 内部发生的事情,并为使用和操作 Cinder 的用户、开发人员和运算符提供宝贵的信息。
备选方案¶
避免上述 resource_states 表的一种可能性是使用 DLM 并使用与 Cinder 中文件锁类似的方法。它的使用方式与文件锁的使用方式类似,尽管在 RPC 边界处仍然需要状态转换验证。例如,当释放锁并发出异步 RPC 调用时,可能会出现其他异步 RPC 调用也同时处于活动状态的情况,并且在这些 RPC 调用的接收者接受和执行请求的 RPC 操作时,需要使用状态转换和锁系统。
另一种可能的解决方案是不使用异步 RPC 调用,而是使用同步 RPC 调用,并且发送者仅在收到接收者已开始处理(或接受请求)的确认后才释放其拥有的 DLM 锁。接收者将在接受请求期间获取锁,从而确保发送者和接收者之间正确地传递锁。这需要一个敏感且难以正确实现的锁传递代码路径和过程(需要对该路径进行大量测试以确保正确性)。
在我看来,这两种替代方法都过于脆弱,并且没有使状态转换过程和图对开发人员、运算符和用户可见。缺乏这些信息会阻碍 Cinder 的采用,并使从不可避免的故障和操作问题中恢复和理解变得更加困难。
这不解决什么¶
我也想说明一下此规范不包含的内容。
它不包含跨项目资源使用和与使用 Cinder 的项目进行状态转换相关的不一致性(例如,Nova 发起的卷分离不会在 Nova API 流中提前中止,而是在 Cinder 对该资源执行其他状态转换时中止)。
它也不阻止 Cinder 在 Nova(即 VM 正在使用卷)下删除卷。
这些是更大的跨项目一致性问题,需要在项目之间更高层次地解决。值得注意的是,一旦项目本身具有一致的状态和转换集,就更容易实现跨项目一致性(如果没有内部一致性,跨项目资源使用可能应该被阻止/避免)。
数据模型影响¶
请参阅上述建议的表。
跨项目影响¶
我们必须小心保留现有的 API,以便依赖 Cinder 当前可见状态的 Nova 继续工作。这意味着我们需要一个 Nova 兼容的公开映射;同时,我们拥有一个更详细和一致的内部映射。
REST API 影响¶
也许在未来。
安全影响¶
N/A
通知影响¶
目前没有,如果将来需要这样做,状态转换信息也可以发送到通知系统。
其他最终用户影响¶
最终用户现在应该期望在同一组资源上并发执行操作时收到更多错误(或稍后再试)响应。以前,其中一些操作可能成功也可能不成功。
性能影响¶
将在 sqlalchemy 中创建一个新表,并为新的模式创建一个新模型。此表将具有很高的读写流量(因为 Cinder 中发生的所有操作都会向其写入数据),因此建议将表类型更改为更友好的格式,该格式对该表的有限使用情况表现更好。由于此表相对简单,因此将来(在实现正确性后)也可以将此表切换到可以针对小型读/写进行优化的其他后端,而无需太多历史记录(历史记录不太有用,除了希望查询资源过去发生情况的运算符和开发人员)。
其他部署者影响¶
N/A
开发人员影响¶
开发人员可能会从这些信息中受益匪浅,因为这将有助于他们了解工作流经过的状态(在 Cinder 级别),并将此与 taskflow 发出的事件流相结合,可以创建大量有用的运行时信息,这些信息可以在运行 Cinder 或开发 Cinder 时使用(在状态转换定义明确且易于理解时,添加新的状态转换的位置变得更加明显)。
实现¶
负责人¶
主要负责人
Harlowja
其他贡献者
DuncanT
其他人?
你,阅读这篇文章的人?
工作项¶
确定状态图并讨论应该在 Cinder 内部使用哪些状态(关键必需状态)以及哪些状态更具信息性(DuncanT 显然已经进行了一些分析)。
创建数据库模式迁移/添加以用于所决定的新模式。
为新模式创建数据库模型(并确定并讨论如何完成原子状态更新)。
确定关键位置,这些位置将发生这些状态转换(在 taskflow 任务之前或之后),或在 taskflow 之外的层级。
添加新的测试,以触发这些新的状态转换和违规检查,确保发生的是期望发生的事情。
同时致力于在 taskflow 中创建一个模型,该模型可以帮助其他项目避免为他们自己类似的需求/用例重新创建上述代码的块。
疯狂测试。
进行负载测试/并发测试(使用 rally 或 tempest)以验证改进是否有所帮助,而没有损害 Cinder。
里程碑¶
J/3 到 K(这可能不是一个短期规范)。
依赖项¶
N/A
测试¶
由于此更改会影响 Cinder 在低级别上的运行方式,因此需要进行大量的测试来验证并发操作是否被禁止。目前,tempest 可能不是测试这些并发操作的最佳方式,因为据我所知,它不会并行运行(并且只有在受控的并行进程中运行时才能发现这些并发问题)。因此,需要确定如何测试这些并发问题(rally 是否是使用其并发场景来探测此功能是否有效的方式?)。
文档影响¶
可能需要新的文档来解释为什么以前允许并发发生的操作现在不允许并发发生,因为新的状态转换将对同时可以和不能发生的事情更加严格。
还将有可能开始创建类似于 taskflow states 的文档,这些文档显示 Cinder 内部正在做什么以及允许的状态转换(即 Cinder 参考操作状态)。
参考资料¶
峰会讨论/会议
https://etherpad.openstack.org/p/juno-cinder-state-and-workflow-management