MD 集群

集群 MD 是用于集群的共享设备 RAID,它支持两个级别:raid1 和 raid10(有限支持)。

1. 磁盘格式

每个集群节点都使用单独的写入意图位图。位图记录了可能在该节点上启动但尚未完成的所有写入。磁盘布局如下

0                    4k                     8k                    12k
-------------------------------------------------------------------
| idle                | md super            | bm super [0] + bits |
| bm bits[0, contd]   | bm super[1] + bits  | bm bits[1, contd]   |
| bm super[2] + bits  | bm bits [2, contd]  | bm super[3] + bits  |
| bm bits [3, contd]  |                     |                     |

在“正常”运行期间,我们假设文件系统确保一次只有一个节点写入任何给定块,因此写入请求将

  • 设置相应的位(如果尚未设置)

  • 将写入提交到所有镜像

  • 安排在超时后清除该位。

读取只是正常处理。文件系统有责任确保一个节点不会从另一个节点(或同一节点)正在写入的位置读取。

2. 用于管理的 DLM 锁

有三组锁用于管理设备

2.1 位图锁资源 (bm_lockres)

bm_lockres 保护各个节点位图。它们的名称形式为 bitmap000(节点 1)、bitmap001(节点 2)等等。当一个节点加入集群时,它会以 PW 模式获取锁,并且在节点作为集群一部分的生命周期内保持这种状态。锁资源编号基于 DLM 子系统返回的槽位号。由于 DLM 从 1 开始计数节点,而位图槽位从 0 开始,因此从 DLM 槽位号减 1 即可得到位图槽位号。

特定节点的位图锁的 LVB 记录了正在被该节点重新同步的扇区范围。没有其他节点可以写入这些扇区。这在有新节点加入集群时使用。

2.2 消息传递锁

每个节点在启动或结束重新同步以及更新元数据超级块时,都必须与其他节点通信。此通信通过三个锁:“token”、“message”和“ack”以及“message”锁之一的锁值块 (LVB) 进行管理。

2.3 新设备管理

单个锁:“no-new-dev”用于协调新设备的添加——这必须在整个阵列中同步。通常,所有节点都持有对此设备的并发读取锁。

3. 通信

消息可以广播到所有节点,并且发送者会等待所有其他节点确认消息后再继续。一次只能处理一条消息。

3.1 消息类型

有六种传递的消息类型

3.1.1 METADATA_UPDATED

通知其他节点元数据已更新,并且该节点必须重新读取 md 超级块。这是同步执行的。它主要用于指示设备故障。

3.1.2 RESYNCING

通知其他节点重新同步已启动或结束,以便每个节点可以暂停或恢复该区域。每个 RESYNCING 消息都会标识发送节点即将重新同步的设备范围。这会覆盖该节点之前的任何通知:每个节点一次只能重新同步一个范围。

3.1.3 NEWDISK

通知其他节点正在向阵列中添加设备。消息包含该设备的标识符。请参阅下文了解更多详细信息。

3.1.4 REMOVE

正在从阵列中删除故障或备用设备。该设备的时隙号包含在消息中。

3.1.5 RE_ADD

正在重新激活发生故障的设备——假设已确定它再次正常工作。

3.1.6 BITMAP_NEEDS_SYNC

如果一个节点在本地停止但位图不干净,则会通知另一个节点接管重新同步的所有权。

3.2 通信机制

DLM LVB 用于在集群节点内进行通信。为此目的使用了三种资源

3.2.1 token

保护整个通信系统的资源。拥有 token 资源的节点被允许通信。

3.2.2 message

携带要通信数据的锁资源。

3.2.3 ack

获取该资源意味着集群中的所有节点都已确认消息。该资源的 BAST 用于通知接收节点有节点想要通信。

该算法如下

  1. 接收状态 - 所有节点在 “ack” 上都有并发读取器锁

    sender                         receiver                 receiver
    "ack":CR                       "ack":CR                 "ack":CR
    
  2. 发送者获取 “token” 上的 EX,发送者获取 “message” 上的 EX

    sender                        receiver                 receiver
    "token":EX                    "ack":CR                 "ack":CR
    "message":EX
    "ack":CR
    

    发送者检查是否仍然需要发送消息。在等待 “token” 时收到的消息或其他事件可能已使此消息不适当或冗余。

  3. 发送者写入 LVB

    发送者将 “message” 从 EX 降级为 CW

    发送者尝试获取 “ack” 的 EX

     [ wait until all receivers have *processed* the "message" ]
    
                                      [ triggered by bast of "ack" ]
                                      receiver get CR on "message"
                                      receiver read LVB
                                      receiver processes the message
                                      [ wait finish ]
                                      receiver releases "ack"
                                      receiver tries to get PR on "message"
    
    sender                         receiver                  receiver
    "token":EX                     "message":CR              "message":CR
    "message":CW
    "ack":EX
    
  4. 由 “ack” 上的 EX 授权触发(指示所有接收者都已处理消息)

    发送者将 “ack” 从 EX 降级为 CR

    发送者释放 “message”

    发送者释放 “token”

                                receiver upconvert to PR on "message"
                                receiver get CR of "ack"
                                receiver release "message"
    
    sender                      receiver                   receiver
    "ack":CR                    "ack":CR                   "ack":CR
    

4. 处理故障

4.1 节点故障

当节点发生故障时,DLM 会使用槽位号通知集群。该节点会启动一个集群恢复线程。集群恢复线程

  • 获取故障节点的 bitmap<number> 锁

  • 打开位图

  • 读取故障节点的位图

  • 将设置的位图复制到本地节点

  • 清除故障节点的位图

  • 释放故障节点的 bitmap<number> 锁

  • 启动当前节点上位图的重新同步,在 recover_bitmaps 中调用 md_check_recovery,然后 md_check_recovery -> metadata_update_start/finish,它将通过 lock_comm 锁定通信。这意味着当一个节点正在重新同步时,它会阻止所有其他节点写入阵列的任何位置。

重新同步过程是常规的 md 重新同步。但是,在集群环境中,当执行重新同步时,它需要告诉其他节点哪些区域被暂停。在重新同步开始之前,节点会发送带有需要暂停区域的 (lo,hi) 范围的 RESYNCING。每个节点都维护一个 suspend_list,其中包含当前暂停的范围列表。在接收到 RESYNCING 时,节点会将该范围添加到 suspend_list。同样,当执行重新同步的节点完成时,它会向其他节点发送一个带有空范围的 RESYNCING,并且其他节点会从 suspend_list 中删除相应的条目。

可以使用辅助函数 ->area_resyncing() 来检查是否应暂停特定的 I/O 范围。

4.2 设备故障

设备故障会通过元数据更新例程进行处理和通信。当一个节点检测到设备故障时,它将不允许对该设备进行任何进一步的写入操作,直到所有其他节点都确认了该故障。

5. 添加新设备

要添加新设备,必须所有节点都“看到”要添加的新设备。为此,使用以下算法

  1. 节点 1 发出 mdadm --manage /dev/mdX --add /dev/sdYY,这将发出 ioctl(ADD_NEW_DISK,其中 disc.state 设置为 MD_DISK_CLUSTER_ADD)

  2. 节点 1 发送带有 uuid 和槽号的 NEWDISK 消息

  3. 其他节点使用 uuid 和槽号发出 kobject_uevent_env (步骤 4,5 可以是一个 udev 规则)

  4. 在用户空间中,节点搜索磁盘,可能使用 blkid -t SUB_UUID=""

  5. 其他节点根据是否找到磁盘发出以下任一指令:ioctl(ADD_NEW_DISK,其中 disc.state 设置为 MD_DISK_CANDIDATE,并且 disc.number 设置为槽号) ioctl(CLUSTERED_DISK_NACK)

  6. 如果找到设备,其他节点将释放“no-new-devs” (CR) 上的锁

  7. 节点 1 尝试对 “no-new-dev” 获取 EX 锁

  8. 如果节点 1 获得锁,它会在取消标记磁盘为 SpareLocal 后发送 METADATA_UPDATED

  9. 如果没有(获得 “no-new-dev” 锁),则操作失败并发送 METADATA_UPDATED。

  10. 其他节点通过以下 METADATA_UPDATED 获取有关是否添加了磁盘的信息。

6. 模块接口

md 核心可以向集群模块发出 17 个回调。理解这些回调可以很好地概述整个过程。

6.1 join(nodes) 和 leave()

当使用集群位图启动阵列时以及当阵列停止时,会调用这些函数。join() 确保集群可用并初始化各种资源。集群中只有前 “nodes” 个节点可以使用该阵列。

6.2 slot_number()

报告集群基础设施建议的槽号。范围从 0 到 nodes-1。

6.3 resync_info_update()

这会更新存储在位图锁中的重新同步范围。起始点会随着重新同步的进行而更新。终点始终是阵列的末尾。它发送 RESYNCING 消息。

6.4 resync_start(), resync_finish()

当重新同步/恢复/重塑开始或停止时,会调用这些函数。它们会更新位图锁中的重新同步范围,并发送 RESYNCING 消息。resync_start 报告整个阵列正在重新同步,resync_finish 报告没有任何部分正在重新同步。

resync_finish() 还会发送 BITMAP_NEEDS_SYNC 消息,允许其他节点接管。

6.5 metadata_update_start(), metadata_update_finish(), metadata_update_cancel()

metadata_update_start 用于获取对元数据的独占访问权。如果获得访问权后仍需要进行更改,则 metadata_update_finish() 将向所有其他节点发送 METADATA_UPDATE 消息,否则可以使用 metadata_update_cancel() 来释放锁。

6.6 area_resyncing()

这结合了两个功能要素。

首先,它会检查是否有任何节点当前正在给定扇区范围内重新同步任何内容。如果找到任何重新同步,则调用者将避免在该范围内写入或读取平衡。

其次,在节点恢复期间,它会报告所有区域都正在为 READ 请求重新同步。这避免了集群文件系统和处理节点故障的集群 RAID 之间的竞争。

6.7 add_new_disk_start(), add_new_disk_finish(), new_disk_ack()

这些用于管理上述新磁盘协议。添加新设备时,会在将其绑定到阵列之前调用 add_new_disk_start(),如果成功,则在完全添加设备后调用 add_new_disk_finish()。

当添加设备以响应先前的请求时,或者当设备被声明为“不可用”时,会调用 new_disk_ack()。

6.8 remove_disk()

当从阵列中移除备用或故障设备时,会调用此函数。它会导致向其他节点发送 REMOVE 消息。

6.9 gather_bitmaps()

这会向所有其他节点发送 RE_ADD 消息,然后从所有位图中收集位图信息。然后使用此组合位图来恢复重新添加的设备。

6.10 lock_all_bitmaps() 和 unlock_all_bitmaps()

当将位图更改为无时会调用这些函数。如果节点计划清除集群 raid 的位图,它需要确保没有其他节点正在使用 raid,这可以通过锁定集群内的所有位图锁来实现,并且这些锁也会相应地解锁。

7. 不支持的功能

集群 MD 尚未支持某些功能。

  • 更改 array_sectors。