注:本文内容引用自张洋老师的知乎文章
https://zhuanlan.zhihu.com/p/26582003796,他
是一位存储研发专家。
问题背景
zStorage典型的部署拓扑结构如下图所示:采用3个存储节点,数据保存3副本,2个或更多计算节点。存储节点用于承载zStorage
分布式块存储系统
,通过FrontEnd(简称FE)向外提供NVMe-oF/
iSCSI
块存储协议。计算节点用于承载计算任务,例如Oracle、MogDB等数据库业务。计算节点通过NVMe-oF/iSCSI块存储协议映射zStorage卷,每个卷可以使用多路径,通过不同的FE发起IO操作。
在一切正常的情况下,IO通过路径1进行处理。一旦出现异常,例如路径1的网络故障,导致IO切换到路径2处理。这里有一个短暂的同步问题:
-
通过路径1发起一个写操作IO1,将某个位置X修改为:例如“111”。
-
路径1故障超时,通过路径2重发IO1,位置X成功修改为“111”。
-
继续通过路径2发起IO2,将位置X修改为“222”。
-
路径1上的IO1(已失效超时)重新将位置X修改为“111”。
以上四个步骤中,出现了失效的旧数据覆盖新数据的情况,导致数据丢失,这是一个严重的问题。该问题为本文将要讨论的主要问题:zStorage是如何处理多路径下可能出现的数据不一致问题?
FE 引入
分布式锁
FE通过MDS的
CAS语义
实现分布式锁逻辑,用以保证:
-
zStorage旧版本的锁语义:某个卷的IO只能在持有锁的FE上处理。在这种语义下,只要是同一个卷,无论是多少个计算节点下发IO,都统一在一个FE上处理。锁的粒度为:卷+FE。
-
zStorage新版本的锁语义:某个计算节点针对某个卷的IO只能在持有锁的FE上处理。在这种语义下,针对同一个卷,不同的计算节点的IO可以通过不同的FE处理。锁的粒度为:HOST+卷+FE。
对于本文要讨论的问题来说,两种语义是类似的,本文按照“zStorage旧版本的锁语义”继续讨论。
在zStorage系统中,monitor负责维护集群的状态,管理和维护节点、OSD、PG等视图信息。
当出现异常情况,需要切换路径时,假如从路径1切换到路径2。对于路径1的FE来说,有两种情况:
-
和monitor租约正常,通信正常,仅和HOST通信异常。这种情况下,该FE会收到路径切换通知,清理挂起的IO,保证全部发送出去,并释放锁。
-
和monitor租约异常,无法通信,或者该FE崩溃退出。这种情况下,由于租约超时,新路径的FE会自动抢占到锁。
以上两种情况,实际上还未解决前面提到的旧IO覆盖新IO的问题,只是将问题后推到了
ChunkServer
(简称CS)。后续还需要FE和CS配合,以识别出旧IO(即:失效的IO)。
ChunkServer 引入IO版本号
在FE和CS的通信协议中,增加IO版本号信息。在FE端,每当路径发生切换后,会增加IO版本号,以区分旧路径和新路径上的IO。
在CS端如何识别出失效的IO,以防止旧数据覆盖新数据呢?在CS内存中,维护了一个以“volume_id”为key,“io_version”为值的键值哈希表。每次IO都将IO版本号与哈希表中存储的值做对比: