转自:https://www.ustack.com/blog/ceph-internal-scrub/
Ceph 的主要一大特点是强一致性,这里主要指端到端的一致性。众所周知,传统存储路径上从应用层到内核的文件系统、通用块层、SCSI层到最后的HBA和磁盘控制器,每层都有发生错误的可能性,因此传统的端到端解决方案会以数据块校验为主来解决(这方面的讨论可以参考SCSI中端到端校验能解决数据完整性问题吗)。而在 Ceph 方面,更是加入了 Ceph 自己的客户端和网络、存储逻辑、数据迁移,势必导致更高的错误概率。
来自How to Prevent Silent Data Corruption
因为 Ceph 作为一个应用层的路径,它利用了 POSIX 接口进行存储并支持 Parity Read/Write,这时候如果封装固定数据块并且加入校验数据会导致较严重的性能问题,因此 Ceph 在这方面只是引入 Scrub 机制(Read Verify)来保证数据的正确性。
简单来说,Ceph 的 OSD 会定时启动 Scrub 线程来扫描部分对象,通过与其他副本进行对比来发现是否一致,如果存在不一致的情况,Ceph 会抛出这个异常交给用户去解决。
Scrub 流程
/* * Chunky scrub scrubs objects one chunk at a time with writes blocked for that * chunk. * * The object store is partitioned into chunks which end on hash boundaries. For * each chunk, the following logic is performed: * * (1) Block writes on the chunk * (2) Request maps from replicas * (3) Wait for pushes to be applied (after recovery) * (4) Wait for writes to flush on the chunk * (5) Wait for maps from replicas * (6) Compare / repair all scrub maps * * This logic is encoded in the very linear state machine: * * +------------------+ * _________v__________ | * | | | * | INACTIVE | | * |____________________| | * | | * | +----------+ | * _________v___v______ | | * | | | | * | NEW_CHUNK | | | * |____________________| | | * | | | * _________v__________ | | * | | | | * | WAIT_PUSHES | | | * |____________________| | | * | | | * _________v__________ | | * | | | | * | WAIT_LAST_UPDATE | | | * |____________________| | | * | | | * _________v__________ | | * | | | | * | BUILD_MAP | | | * |____________________| | | * | | | * _________v__________ | | * | | | | * | WAIT_REPLICAS | | | * |____________________| | | * | | | * _________v__________ | | * | | | | * | COMPARE_MAPS | | | * |____________________| | | * | | | | * | +----------+ | * _________v__________ | * | | | * | FINISH | | * |____________________| | * | | * +------------------+ * * The primary determines the last update from the subset by walking the log. If * it sees a log entry pertaining to a file in the chunk, it tells the replicas * to wait until that update is applied before building a scrub map. Both the * primary and replicas will wait for any active pushes to be applied. * * In contrast to classic_scrub, chunky_scrub is entirely handled by scrub_wq. * * scrubber.state encodes the current state of the scrub (refer to state diagram * for details). */
Ceph 的 PG.cc 源文件中的 ASCII 流程描述已经非常形象了,这里只简述内容和补充部分信息。
- OSD 会以 PG 为粒度触发 Scrub 流程,触发的频率可以通过选项指定,而一个 PG 的 Scrub 启动都是由该 PG 的 Master 角色所在 OSD 启动
- 一个 PG 在普通的环境下会包含几千个到数十万个不等的对象,因为 Scrub 流程需要提取对象的校验信息然后跟其他副本的校验信息对比,这期间被校验对象的数据是不能被修改的。因此一个 PG 的 Scrub 流程每次会启动小部分的对象校验,Ceph 会以每个对象名的哈希值的部分作为提取因子,每次启动对象校验会找到符合本次哈希值的对象,然后进行比较。这也是 Ceph 称其为 Chunky Scrub 的原因。
- 在找到待校验对象集后,发起者需要发出请求来锁定其他副本的这部分对象集。因为每个对象的 master 和 replicate 节点在实际写入到底层存储引擎的时间会出现一定的差异。这时候,待校验对象集的发起者会附带一个版本发送给其他副本,直到这些副本节点与主节点同步到相同版本。
- 在确定待校验对象集在不同节点都处于相同版本后,发起者会要求所有节点都开始计算这个对象集的校验信息并反馈给发起者。
- 该校验信息包括每个对象的元信息如大小、扩展属性的所有键和历史版本信息等等,在 Ceph 中被称为 ScrubMap。
- 发起者会比较多个 ScrubMap并发现不一致的对象,不一致对象会被收集最后发送给 Monitor,最后用户可以通过 Monitor 了解 Scrub 的结果信息
用户在发现出现不一致的对象后,可以通过 “ceph pg repair [pg_id]” 的方式来启动修复进程,目前的修复仅仅会将主节点的对象全量复制到副本节点,因此目前要求用户手工确认主节点的对象是”正确副本”。另外,Ceph 允许 Deep Scrub 模式来全量比较对象信息来期望发现 Ceph 本身或者文件系统问题,这通常会带来较大的 IO 负担,因此在实际生产环境中很难达到预期效果。
Scrub 问题
正如流程所述,目前的 Scrub 有以下问题:
- 在发现不一致对象后,缺少策略来自动矫正错误,比如如果多数副本达成一致,那么少数副本对象会被同化
- Scrub 机制并不能及时解决存储系统端到端正确的问题,很有可能上层应用早已经读到错误数据
对于第一个问题,目前 Ceph 已经有 Blueprint 来加强 Scrub 的修复能力,用户启动 Repair 时会启动多数副本一致的策略来替代目前的主副本同步策略。
对于第二个问题,传统端到端解决方案会更多采用固定数据块附加校验数据的“端到端校验”方案,但是 Ceph 因为并不是存储设备空间实际的管理和分配者,它依赖于文件系统来实现存储空间的管理,如果采用对象校验的方式会严重损耗性能。因此在从文件系统到设备的校验需要依赖于文件系统,而 Ceph 包括客户端和服务器端的对象正确性校验只能更多的依赖于 Read Verify 机制,在涉及数据迁移时需要同步的比较不同副本对象的信息来保证正确性。目前的异步方式会允许期间发生错误数据返回的可能性。