暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

分布式文件系统CURVE

潍鲸 2020-07-23
1259


CURVE是什么


CURVE是网易自主设计研发的高性能、高可用、高可靠分布式存储系统,具有非常良好的扩展性。基于该存储底座可以打造适用于不同应用场景的存储系统,如块存储、对象存储、云原生数据库等。CURVE 的设计开发始终围绕三个理念:一是顺应当前存储硬件设施发展趋势,做到软硬件结合打造顶级的存储产品;二是秉持 “Simple Can be harder than complex”,了解问题本质情况下选择最简单的方案解决问题;三是拥抱开源,在充分调研的前提下使用优秀的开源项目组件,避免造轮子。
当前我们基于CURVE已经实现了高性能块存储系统,支持快照克隆和恢复 ,支持QEMU虚拟机和物理机NBD设备两种挂载方式, 在网易内部作为高性能云盘使用。


CURVE架构


基本架构


要深入了解 CURVE 首先要了解 CURVE 整体架构。CURVE集群主要包括三个核心组件:MDS、Chunkserver、Client。



MDS

MDS是中心节点。它有两方面职责:一是存储管理元数据信息,包括系统的拓扑信息、文件系统的Namespace ( 树形目录结构,文件和目录,目录元信息等 ) 、Copyset ( Raft 复制组) 位置信息。二是感知集群状态并进行合理调度,包括感知Chunkserver上下线、收集Chunkserver负载信息、集群负载均衡与故障修复。MDS通过Etcd进行选主实现高可用,Leader-MDS 和 Follower-MDS并不进行数据同步,Leader-MDS挂掉之后,Follower-MDS从Etcd加载数据后再启动服务。


Chunkserver

Chunkserver是数据节点,负责数据存储。数据存储的最小单元是 chunk,支持覆盖写,管理数据存储的基本单位是Copyset。Chunkserver使用 Raft 协议做复制,保持数据的一致性和容灾。副本以 Copyset 为单位进行管理,不同节点上的多个 Copyset 构成一个 Raft Group,互为副本。数据在多个 Chunkserver 之间的负载均衡由 MDS 调度,是以 CopySet 为单位进行调度。


Client

Client是客户端,向应用提供类Posix文件系统接口,与MDS交互实现对元数据的增删改查,与Chunkserver交互实现对数据的增删改查,对io进行切分,对IOPS和带宽进行指定的QoS控制。Client还支持热升级,可以在用户无感知的情况下进行底层版本变更。


快照克隆


CURVE块存储系统中快照克隆子系统是独立于CURVE核心服务的,快照克隆操作由单独的SnapShotCloneServer进行处理,用户创建的快照会上传到S3集群以节约存储空间,并且支持增量和全量两种快照方式。




核心特性


高性能


高性能是 CURVE 的一大特点,也是我们创建CURVE项目的初衷。RPC 层面 CURVE 采用了高性能和低延迟并且已开源的 brpc;在一致性层面 选择了基于 quorum 机制并且开源的 braft,从协议层面来说 quorum 机制在延迟方面天生优于多副本强一致的方式。实现上CURVE 对 braft 快照的实现进行了优化,在状态机的实现上采用 chunkfilepool 的方式 ( 初始化集群的时候格式化出指定比例的空间用作 chunk ) 使得底层的写入放大为 0;此外CURVE还在chunk上进行更细力度的地址空间hash以达到读写分离、减小 IO碰撞等的效果,从而进一步提升IO性能。


高可用


高可用是CURVE的另一大特点。MDS、ChunkServer 以及 SnapShotCloneServer都支持多实例部署,部分实例异常不影响整个集群的可用性。

  • MDS

    MDS是无状态的,推荐至少部署两个实例。通过Etcd进行选主。多个MDS实例通过Etcd进行选主,当单个实例失效时,可以秒级切换到另外一个实例。失效实例上正在处理的请求,Client和SnapShotCloneServer都会对其进行重试,以达到不影响集群可用性的效果。

  • SnapShotCloneServer

    SnapShotCloneServer与MDS类似, 也是通过Etcd进行选主,不同的是,它通过负载均衡对外提供服务。失效期间的请求失败重试都是幂等的,不影响任务的正确性以及集群的可用性。

  • ChunkServer

    ChunkServer是一个集群,通过Raft协议保持数据一致性,并通过MDS做负载均衡。单个节点失效时,会影响到这个节点上存储的所有Copyset。对于Copyset上的Leader节点,会中断服务,等待重新选举;对于Copyset上的follower节点,服务不会受影响。当某个Chunkserver节点失效且在一段时间内无法恢复,MDS会将其上的数据迁移到其他节点上。


NEBD介绍


Curve Client作为Curve集群的入口,是以链接库的形式提供给Qemu/Curve-NBD等上层使用,所以在Curve Client需要升级的时候,需要对Qemu/Curve-NBD进行重启。
为了减少升级对上层业务的影响,对上层应用与Curve Client的耦合关系进行拆分,在两者之间加入了热升级模块:NEBD。

NEBD架构



上图是当前系统的部署架构。

  • NEBD Client:即part1,对应上层业务,包括Qemu/Curve-NBD;通过Unix Domain Socket的方式连接到指定的NEBD Server。

  • NEBD Server:即part2,负责接收part1的请求,并调用Curve Client进行相应处理;part2可以接收多个不同NEBD Client的请求。


从图中可以看到,part1取代了Curve Client直接与上层业务对接。这种情况下,part1的升级也会影响上层业务,所以在设计中,尽量减少了part1的处理逻辑,只进行请求转发,以及有限的重试逻辑。


升级part2/Curve Client时,需要执行以下步骤:

  1. 安装新版part2/Curve Client

  2. 停止当前part2进程

  3. 重新启动part2进程

在实践中,我们使用daemon监控part2进程,如果进程不存在,则会主动拉起。

在测试及生产环境中,从停止part2进程,到新版part2启动,耗时在1~5s之间。


NEBD模块架构图



part1


  • libnebd:提供给上层业务的API接口,包括open/close、读写等接口。

  • File Client:主要是对libnebd接口的具体实现,向part2发送用户请求。

  • MetaCache Manager: 主要记录当前已经open文件的信息。

  • Heartbeat Client:定期向part2发送心跳,心跳中携带已经open文件的信息。


part2


  • File Service:接受并处理part1发送过来的文件请求。

  • Heartbeat Service:接受并处理part1发送过来的心跳请求。

  • File Manager:负责part2上open文件的管理。

  • IO Executor:请求具体的执行层,调用curve-client的接口将请求交给后端存储集群处理。

  • Metafile Manager:负责元数据文件的管理,将元数据持久化到文件,或从文件中读取持久化信息。


关键点


part1重试逻辑


前面说到,part1只进行了有限的重试逻辑,具体体现在两方面:

  • RPC请求不设置超时时间

  • 只针对RPC请求本身的错误进行重试,RPC Response中返回的错误码直接返回给上层


以Write请求为例,其他请求也类似:

将上层Write请求通过RPC转发给part2,并不设置超时时间,进入等待


  • 如果RPC返回成功,则根据RPC Response成功与否,向上层返回。

  • 如果连接断开或无法连接,则等待一段时间后重试




心跳管理


为了避免上层业务退出时没有close文件,part2会定期检查文件的心跳状态(part1会通过心跳,定期上报以open文件的信息),如果上次心跳时间已经超时,则会主动close文件。
心跳超时close文件与上层业务主动close文件的区别在于,前者不会把文件信息从metafile中删除。
但是当上层业务出现假死,后续又恢复的情况下,也有可能导致心跳超时,造成文件被close。
所以,part2在收到part1请求时,会先检测metafile是否有当前文件记录,如果存在,且处于closed状态,则会先主动open对应的文件,然后执行后续请求。

MDS介绍


MDS是中心节点,负责元数据管理、集群状态收集与调度。MDS包含以下几个部分:

  • Topoloy: 管理集群的 topo 元数据信息。

  • Nameserver: 管理文件的元数据信息。

  • Copyset: 副本放置策略。

  • Heartbeat: 心跳模块。跟chunkserver进行交互,收集chunkserver上的负载信息,copyset信息等。

  • Schedule: 调度模块。用于自动容错和负载均衡。


Topoloy

topology用于管理和组织机器,利用底层机器的放置、网络的规划以面向业务提供如下功能和非功能需求。

  1. 故障域的隔离:比如副本的方式分布在不同机器,不同机架,或是不同的交换机下面。

  2. 隔离和共享:不同用户的数据可以实现固定物理资源的隔离和共享。

curve整体的拓扑结构如下图:




chunkserver:用于抽象描述物理服务器上的一块物理磁盘(SSD),chunkserver以一块磁盘作为最小的服务单元。

server: 用于抽象描述一台物理服务器,chunkserver必须归属于server。

zone: 故障隔离的基本单元,一般来说属于不同zone的机器至少是部署在不同的机架,再要求严格一点的话,属于不同zone的机器可以部署在不同机架组下面(一个机架组共享一组堆叠 leaf switch),一个server必须归属于一个zone。

pool: 用于实现对机器资源进行物理隔离,pool中server之间的交互仅限于pool之内的server。运维上,可以在上架一批新的机器的时候,规划一个全新的pool,以pool为单元进行物理资源的扩容(pool内扩容也可以支持,但是不建议pool内扩容,因为会影响每个chunkserver上的copyset的数量)。

借鉴ceph的设计,curve在如上物理pool之上又引入逻辑pool的概念,以实现统一存储系统的需求,即在单个存储系统中多副本PageFile支持块设备、三副本AppendFile(待开发)支持在线对象存储、AppendECFile(待开发)支持近线对象存储可以共存。



如上所示LogicalPool与pool为多对一的关系,一个物理pool可以存放各种类型的file。当然由于curve支持多个pool,可以选择一个logicalPool独享一个pool。

通过结合curve的用户系统,LogicalPool可以通过配置限定特定user使用的方式,实现多个租户数据物理隔离(待开发)。

logicalPool:用于在逻辑层面建立不同特性的pool,比如如上AppendECFile pool、AppendEC pool 、PageFile pool;实现user级别的数据隔离和共享。


NameServer


NameServer管理namespace元数据信息,包括(更具体的信息可以查看curve/proto/nameserver2.proto):

FileInfo: 文件的信息。

PageFileSegment: segment是给文件分配空间的最小单位 。

PageFileChunkInfo: chunk是数据分片的最小单元。

segment 和 chunk的关系如下图:



namespace信息较为直观,即文件的目录层次关系:



如上所示。在KV中,Key是ParentID + "/"+ BaseName,Value是自身的文件ID;这种方式可以很好地平衡几个需求:

  1. 文件列目录:列出目录下的所有文件和目录

  2. 文件查找:查找一个具体的文件

  3. 目录重命名:对一个目录/文件进行重命名

当前元数据信息编码之后存储在 etcd 中。


CopySet


Curve系统中数据分片的最小单位称之为Chunk,默认的Chunk大小是16MB。在大规模的存储容量下,会产生大量的Chunk,如此众多的Chunk,会对元数据的存储、管理产生一定压力。因此引入CopySet的概念。CopySet可以理解为一组复制组,这组复制组的成员关系完全一样。CopySet的概念在文献「Copysets: Reducing the Frequency of Data Loss in Cloud Storage」提出,本意是为了提高分布式存储系统中的数据持久性,降低数据丢失的概率。

在 Curve 系统引入 CopySet 有几个目的:

  1. 减少元数据量:如果为每个Chunk去保存复制组成员关系,需要至少 ChunkID+3×NodeID=20 个byte,在1PB的逻辑数据量,4MB的Chunk的场景下,需要5GB的数据量;而如果在Chunk到复制组之间引入一个CopySet,每个Chunk可以用ChunkID+CopySetID=12个byte,数据量减少到3GB

  2. 减少复制组数量:如果一个数据节点存在 256K个复制组,复制组的内存资源占用将会非常恐怖;复制组之间的通信将会非常复杂,例如复制组内Primary给Secondary定期发送心跳进行探活,在256K个复制组的情况下,心跳的流量将会非常大;而引入CopySet的概念之后,可以以CopySet的粒度进行探活、配置变更,降低开销

  3. 提高数据可靠性:在数据复制组过度打散的情况下,在发生多个节点同时故障的情况下,数据的可靠性会受到影响

ChunkServer,Copyset和Chunk三者之间的关系如下图:




Heartbeat


心跳用于中心节点和数据节点的数据交互,详细功能如下:

1. 通过chunkserver的定期心跳,检测chunkserver的在线状态(online,offline)

2. 记录chunkserver定期上报的状态信息(磁盘容量,磁盘负载,copyset负载等),以提供运维工具查看上述状态信息。

3. 通过上述信息的定期更新,作为schedule 模块进行均衡及配置变更的依据

4. 通过chunkserver定期上报copyset的copyset的epoch, 检测chunkserver的copyset与mds差异,同步两者的copyset信息

5. 支持配置变更功能,在心跳回复报文中下发mds发起的配置变更命令,并在后续心跳中获取配置变更进度。

心跳模块的结构如下:


MDS端


mds 端的心跳主要由三个部分组成:

TopoUpdater: 根据 chunkserver 上报的 copyset 信息更新拓扑中的信息。

ConfGenerator: 将当前上报的 copyset 信息提交给调度模块,获取该 copyset 上可能需要执行的任务。

HealthyChecker: 检查集群中的 chunkserver 在当前时间点距离上一次心跳的时间,根据这个时间差更新chunkserver状态。


hunkserver端


chunkserver 端的心跳由两个部分组成:

ChunkServerInfo/CopySetInfo: 获取当前 chunkserver 上的 copyset 信息上报给 MDS。

Order ConfigChange: 将 MDS 下发的任务提交给对应的 copyset 复制组。


chedule


系统调度是为了实现系统的自动容错和负载均衡,这两个功能是分布式存储系统的核心问题,也是 curve 是否能上生产环境的决定因素之一。自动容错保证常见异常(如坏盘、机器宕机)导致的数据丢失不依赖人工处理,可以自动修复。负载均衡和资源均衡保证集群中的磁盘、cpu、内存等资源的利用率最大化。

调度模块的结构如下:



Coordinator: 调度模块的对外接口。心跳会将chunkserver上报上来的copyset信息提交给Coordinator,内部根据该信息判断当前copyset是否有配置变更任务执行,如果有任务则下发。
任务计算: 任务计算模块包含了多个定时任务 和 触发任务。定时任务 中,CopySetScheduler 是copyset均衡调度器,根据集群中copyset的分布情况生成copyset迁移任务;LeaderScheduler 是leader均衡调度器,根据集群中leader的分布情况生成leader变更任务;ReplicaScheduler 是副本数量调度器,根据当前copyset的副本数生成副本增删任务;RecoverScheduler 是恢复调度器,根据当前copyset副本的存活状态生成迁移任务。触发任务 中,RapidLeaderScheduler 是快速leader均衡器,由外部触发,一次生成多个leader变更任务,使得集群的leader尽快大达到均衡状态。TopoAdapter 用于获取Topology中调度需要使用的数据。Common Strategy 中是通用的副本添加和移除策略。
任务管理: 任务管理模块用于管理计算模块产生的任务。operatorController 是任务集合,用于存放和获取任务;operatorStateUpdate 根据上报的copyset信息更新状态;Metric用于统计不同任务个数。

ChunkServer介绍


Curve存储秉承设计为支持各种存储场景的统一的分布式存储系统的目标,以高性能块存储为第一优先级,并逐步支持在线/近线对象存储,文件存储,大数据等各类场景。Curve ChunkServer承担了Curve存储的I/O路径的服务者角色,需要为各类存储存储场景提供优化的服务能力,极力提供更高的I/O性能,存储效率,可用性和可靠性。


Curve ChunkServer遵照类GFS的分布式存储架构设计,是数据节点,对外提供数据I/O(读,写,快照等)和节点管理功能(获取Chunkserver的状态,统计信息,修改各模块的运行时参数和状态等)的对外接口,底层基于ext4存储引擎,并进行抽象封装。ChunkServer跟Client和MetaServer交互,高效地执行Client I/O,响应MetaServer的空间分配和调度,数据迁移和恢复,I/O服务质量,状态查询和用户控制等各类请求,并维护数据的可靠性和副本一致性。


作为支持统一存储场景特别是高性能分布式块存储产品I/O路径的服务提供者,ChunkServer需要在性能,可用性,可靠性,易维护性等多方面表现优秀,包括:

  • 高并发 - 支持Client的高IOPS请求

  • 低延时 - Client I/O平均延时低,小于1ms

  • 高容错 - 在MDS的调度下,执行数据迁移和恢复, 并在此过程中保障I/O性能。容忍存储盘故障,保证集群数据存储整体的可靠性。

  • 快照 - 支持Chunk级别的快照功能

  • 热升级 - 可以针对集群进行在线升级,升级过程中ChunkServer集群可以继续不间断提供I/O服务


Chunk Server系统设计


总体架构


设计理念


ChunkServer中用到了跟设计紧密相关的重要性理念,包括如下:

管理域 - ChunkServer的管理域为存储盘,每个数据服务器上有多块盘,每块盘对应一个ChunkServer实例,每个ChunkServer实例在OS上体现为一个用户态服务进程。不同的ChunkServer之间的隔离达到进程级别,单个ChunkServer的意外故障不会影响整台机器,单个存储盘不稳定或软件缺陷造成的性能问题也不会扩散到其他存储盘。

复制组(Copyset) - I/O数据块以复制组为处理单元进行副本复制和一致性处理,为了平衡故障恢复时间,负载均衡和故障隔离性,每块盘平均分布若干个复制组。每个复制组有一个主节点(Leader)和多个从节点(Follower)。Client端具有数据块对应的复制组信息,数据写发送到主节点,由主节点发送到从节点,收到从节点的响应后,主节点向Client返回操作结果。

异步I/O - ChunkServer从收到I/O请求开始到返回操作结果,路径上有网络数据收发,副本复制,事务写I/O等若干个相对耗时的处理逻辑。全同步的方式将造成这几个耗时的处理逻辑的串行化,通过异步化,增加整个链路的I/O并发性,可以对ChunkServer的处理能力大为提高。

线程池 - 由于每台数据服务器上存在大量的复制组数目和众多异步化I/O逻辑,I/O路径上需要大量的线程来进行并发处理,通过线程池来对这些线程进行生命周期管理,可以将具体逻辑从线程管理的负担中解放出来,同时复用线程生命周期,提供对服务器cpu架构更友好的亲和性,从而提高性能。

副本复制 - 多数副本同步复制,少数副本异步复制,避免少数慢盘影响性能


软件架构


Chunk Server概要结构如下所示,通过网络消息收发层统一接收来自Client, MetaServer及其他ChunkServer的消息,不同类型的消息包括I/O请求,控制请求,以及事件通知,网络消息收发层通过RPC将不同的消息发送到服务层不同的子服务进行路由,子服务与Chunk Server中各个子系统相关联,包括I/O处理,快照,恢复,数据校验,配置,监控统计以及Chunk Server控制器等多个子系统,每个子服务包含一个接口集合,通过调用各个子系统中的不同功能提供服务。不同的子系统之间也可以通过对方的服务接口或内部接口进行相互协作。



各个功能子系统的功能和基本职责见下节功能子系统。


功能子系统


  • RPC Service Layer

    提供chunkserver对外的rpc服务处理逻辑,包含的rpc服务包括

    • ChunkService

      处理chunkserver I/O 相关服务,是chunkserver的核心服务,包含读chunk、写chunk(创建chunk在写时创建)、删除chunk、读chunk快照、删除chunk快照、获取chunk信息、创建克隆chunk等等I/O相关功能。

    • CliService

      提供RAFT配置变更相关的rpc服务,包含AddPeer,RemovePeer,ChangePeer、GetLeader、ChangeLeader、ResetPeer等配置变更相关的服务。CliService底层通过调用Braft的配置变更相关接口完成配置变更。

    • CopySetService

      调用CopySetNodeManager创建CopysetNode,即创建Raft节点。在chunkserver集群初始化时,通过MetaServer调用该接口在各ChunkServer上创建Raft节点,并启动Raft节点的相关服务。

    • RaftService

      Braft的内置的RaftService,在chunkserver初始化时需要启动Raft对外的相关rpc服务。

  • Internal Service Layer

    • CopysetNodeManager

      负责管理CopysetNode即RaftNode的创建和删除管理。

    • HeartBeat

      心跳模块主要完成chunkserver对MetaServer定期上报心跳,使MetaServer感知chunkserver的存活性,感知chunkserver故障情况。同时心跳报文还负责上报chunkserver的各种状态、磁盘使用量等数据。

    • CloneManager

      CloneManager主要负责克隆相关的功能,内部是一个线程池,主要负责异步完成克隆chunk的数据补全。关于克隆相关的内容将会在快照克隆相关介绍文档中详细介绍。(附链接)

    • CopysetNode

      CopysetNode封装了Raft状态机,是chunkserver的核心结构。

    • ChunkOpRequest

      ChunkOpRequest模块封装了对chunkservie到达的I/O请求的实际处理过程。

    • ChunkServerMetric

      ChunkServerMetric是对chunkserver所有Metric的封装,负责采集chunkserver内部的各种metric,结合外部Prometheus和Graphna,方便的展示chunkserver内部的各项数据指标,方便观测和诊断chunkserver的各种问题。

  • ConcurrentApplyModule

    并发控制层,负责对chunkserver的IO请求进行并发控制,对上层的读写请求安装chunk粒度进行Hash,使得不同chunk的请求可以并发执行。

  • DataStore

    DataStore是对chunk落盘逻辑的封装。包含chunkfile的创建、删除,以及实际对chunk的读写,chunk基本cow的快照,克隆chunk的管理等等。

  • LocalFileSystermAdaptor

    LocalFileSystermAdaptor是对底层文件系统的一层抽象,目前适配封装了ext4文件系统的接口。 之所以要做这层抽象,目的是隔离了底层文件系统的实际读写请求,如果将来curve要适配裸盘或者采用其他文件系统,可以在这层进行适配。


关键模块


chunkserver初始化


chunkserver注册

Chunkserver需要通过注册来加入一个Curve集群,并得到MDS分配的Chunkserver ID作为集群内的唯一合法标识,并需要在后续的通讯中提供此ID和token作为身份标记和合法性认证信息。

Chunkserver注册使用首次注册的模式。在首次启动时向MDS发送包含Chunkserver基本信息的注册消息,使MDS新增一条Chunkserver信息记录和调度对象,MDS向Chunkserver返回的消息中包含分配给此Chunkserver的ID作为此Chunkserver在集群中的唯一身份标志。后续Chunkserver启动或重启时,不再向MDS进行注册。

Chunkserver在启动时,流程如下:

  • 检测本地Chunkserver对应存储盘上的Chunkserver ID, 如果存在,说明注册过,直接跳过后续步骤;

  • 构造ChunkServerRegistRequest消息,使用RegistChunkServer接口发送给MDS;

  • 如果响应超时,或表示MDS暂时不可用,返回到步骤2进行重试;如果statusCode表示无效注册等含义,chunkserver退出

  • 将chunkserverID和token持久化到盘上,注册完毕


chunksever注册信息的持久化


chunkserver注册信息持久化的格式根据不同的存储引擎而不同,对于目前Ext4存储引擎, Chunkserver持久化信息保存在数据主目录下的chunkserver.dat文件中。 存储时,计算Chunkserver ID,token和数据版本号相关的checksum,构成ChunkserverPersistentData,以json的数据格式,写入到chunkserver.dat文件中。 读取时,获取的ChunkserverPersistentData数据首先校验校验码,防止读取到损坏的数据,并确认版本号是支持的版本。


Chunkserver Copyset重建


Chunkserver在重启时,需要重建在重启前已经分配过的Copyset,以响应来自Client/MDS端针对这些Copyset的访问。

由于Copyset被创建时和初始化时,会在数据主目录下创建copyset子目录,因而留下持久化信息,如1.4节所示。所以可以根据copyset目录列表,得到已经分配的Copyset列表;由于braft本身持久化了raft configuration,所以如果使用相同的copyset数据目录,braft可以自动从持久化信息中恢复出raft configuration。因此Copyset的持久化数据,可以全部从Chunkserver的本地存储盘获取。

Chunkserver启动时,通过扫描数据主目录下的"LogicPoolId/CopysetId"子目录列表,以获得Chunkserver已经创建的的Copyset列表,根据子目录名字的格式,可以解析出来poolId, 和copysetId,然后使用空的raft configuration可以重建Copyset实例,Copyset重建后,由于使用相同的raft各类数据目录,braft可以从snapshot meta中自动恢复出raft configuration出来,加载snapshot和log, 完成初始化。


HeartBeat


MDS需要实时的信息来确认Chunkserver的在线状态,并得到Chunkserver和Copyset的状态和统计数据,并根据信息下发相应的命令。 Chunkserver以心跳的方式来完成上述功能,通过周期性的心跳报文,更新Chunkserver和Copyset的信息,反馈上一个心跳周期的命令执行结果,并接受,解析和执行新的心跳响应报文。

Chunkserver通过心跳消息定期向MDS更新Chunkserver信息,Copyset状态信息,磁盘状态信息,配置变更命令执行状态等信息,MDS收到请求后,根据调度逻辑将新的copyset配置变更命令添加到消息响应中。

心跳流程详细流程图:



CopysetNode


CopysetNode封装了RaftNode的状态机,是chunkserver的核心模块,其总体架构如下图所示:



  • Node Interface

整个curve-raft对用户暴露的接口,主要提供给用户Propose task给raft statemachine处理

  • raft层

主要是核心处理逻辑的模块

(1)NodeImpl主要:

  1. 接收来自外部的Request,然后通过 Raft statemachine RawNode的接口提交给状态机处理,而且其会拥有一个独立Propose execqueue负责驱动Raft statemachine运转,并输出中间结果Ready

  2. 而且是Node Interface的实际实现者,除此之外,还包含了处理Tick timeout和InstallSnapshot的逻辑。

(2)RaftNode

主要是处理Raft StateMachine的输出的中间结果Ready,包括异步处理Ready的exec queue。包含需要转发给其他Peers的Msg,需要落盘的Op log entries,以前需要Apply的committed entries

(3)FSMCaller

负责User StateMachine的具体执行逻辑,也就是State Machine Apply的逻辑,会包含相应的Apply bthread异步batch的处理需要Apply的请求。

  • 持久化&网络接口

(1)Snapshot Storage:负责snapshot meta和snapshot read/write接口

(2)LogManager:Op Log日志管理者,包含Op Log的读写接口,还有Log落盘的batch控制,Op Log缓存等。实际的Op Log最基本的读写接口还是依赖LogStorage的实现。

(3)Raft Meta Storage:raft meta(term,vote,也就是HardState)的持久化

(4)Transport:负责管理复制组Peers的连接,提供message send和peers链接管理的接口


ChunkFilePool


ChunkFilePool位于DataStore层,Chunkserver使用基于ext4实现的本地文件系统,由于写操作存在较大的IO放大,因此在创建chunk文件时会调用fallocate为文件预分配固定大小的空间,但是即便fallocate以后,在写文件未写过的块时仍需要更改元数据,存在一定的IO放大。 curve使用的一种优化方案是直接使用覆盖写过一遍的文件。由于chunkserver上的所有chunk文件和快照文件都是相同固定大小的文件,所以可以预先生成一批被覆盖写过的固定大小文件,创建chunk文件或快照文件时直接从预分配的文件池中获取进行重命名,删除chunk文件或快照文件时再将文件重命名放到预分配池中,这个预分配池就是chunkfile-pool。

chunkserver目录结构:

chunkserver/
copyset_id/
data/
log/
meta/
snapshot/
...


chunkfilepool/
chunkfile_xxx_tmp
chunkfile_xxx_tmp
...

  • 系统初始化阶段:分配一定数量的chunk文件,这个预分配数量是可配置的,当系统重启的时候需要遍历chunkfilepool 目录,将还未分配出去的tmp chunk信息收集在对应的vector池子中,供后续查询使用。

  • GetChunk接口从chunkpool池子中取出一个闲置的chunk,如果池子里没有闲置的文件了,那么同步创建文件,如果池子的水位到达低水位,那么发起一个异步任务进行异步文件分配。

  • 文件分配的操作基于的是文件系统的rename,rename操作可以保证原子性,因此保证了创建文件的原子性。

  • 回收chunk:当外部删除chunk的时候,需要回收chunk,并将chunk置位。回收chunk避免了重新分配。


curve 快照克隆介绍


快照介绍




快照是云盘数据在某个时刻完整的只读拷贝,是一种便捷高效的数据容灾手段,常用于数据备份、制作自定义镜像、应用容灾等。快照系统提供给用户快照功能接口,用户通过快照系统可以创建删除和取消快照,也可以从快照恢复数据或者创建镜像。
curve的快照独立于curve核心服务,支持多级快照,首次快照是全量转储,后面是增量快照,第一次快照时,会把快照数据全量转储到S3,之后的快照只需要转储在上一个版本快照基础上修改过的数据,节省空间。快照数据异步转储到S3对象存储服务上,快照服务通过etcd实现了选主高可用,服务重启可以自动继续未完成的快照。


curve快照架构




快照克隆系统


快照系统收到用户请求之后,通过调用curvefs的接口创建临时快照,再将临时快照转储到对象存储系统,同时将快照的管理数据持久化到数据库中。快照系统主要的工作可以分为两部分:

  1. 对外提供快照接口,供使用者调用来创建删除和查询快照信息。

  2. 负责快照数据的组织管理,调用curvefs和对象存储的接口控制快照数据在整个系统中的流动。


curvefs


curvefs提供创建、删除和读快照的RPC接口,供快照系统调用。快照创建流程中curvefs是快照数据的源,在curvefs上生成的临时快照通过快照系统转储到对象存储上;从快照恢复卷的流程,快照系统将对象存储中的数据写入curvefs。


对象存储


对象存储提供快照数据的存储能力,只提供对象文件的创建、删除和上传下载接口。供快照系统调用来存储从curvefs上读取的快照数据或者将快照数据从对象存储系统中下载下来写入curvefs中用来恢复卷文件。curve使用nos作为为对象存储。


快照流程


用户向快照克隆系统发起快照请求。请求在快照克隆系统在http service层生成具体的snap task,交给snapshot task manager调度处理。打快照的过程先在curvefs生成一个临时快照,然后把临时快照转储到对象存储系统中,最后删除curvefs的临时快照。

创建快照有以下几个步骤:

  1. 生成快照记录,并持久化到etcd。这一步需要进行几个判断:对于同一个卷,同时只能有一个快照正在打的快照,所以需要先判断卷是否有正在处理的快照请求;快照克隆系统理论上支持无限快照,在实际的实现过程中我们对快照克隆的深度增加一个限制,一个卷最多打多少层快照可以在快照的配置文件中进行修改,所以还需要判断下快照层数是否超过限制。如果判断通过,就可以从mds读取原卷的元数据信息,生成快照记录,持久化到etcd。这一步生成的快照状态为pending。

  2. 在curvefs创建临时快照,并返回快照的seqNum,更新快照记录的seqNum。

  3. 创建快照映射表(见1.4.1),保存快照映射表到对象存储S3。

  4. 从curvefs转储快照数据到对象存储S3。转储时,快照克隆系统先从curvefs读取快照数据,然后上传快照数据到对象存储S3。

  5. 删除curvefs的临时快照。

  6. 更新快照状态。这一步更新快照状态为done。


快照的数据组织


每个快照在快照克隆系统中有一条对应的记录,并持久化到etcd中,快照记录使用uuid作为快照的唯一标识。

在打快照的过程中,快照首先在curvefs临时生成,最终会转储到S3上。


快照数据在S3上的组织


快照的数据以chunk为单位转储到S3上,每个chunk在s3上对应着一个Object,存储数据的object称为data object。每个快照还有一个object记录着快照的元数据信息,称为meta object。meta object记录着快照的文件信息以及快照的chunk到object的映射表的映射关系。meta object包括2部分:

  1. 快照文件信息,包括原始数据卷名称,卷大小,chunk大小等

  2. 快照chunk映射表,记录着快照data object的列表

下图是快照数据的示意图


curve快照系统的接口


见快照克隆的接口文档。文档链接


克隆介绍


curve克隆模块介绍


第一节介绍了快照系统,用于对curvefs的文件创建快照,并将快照数据增量地转储到S3服务上;但是对于一个完整的快照系统来说,仅仅支持快照创建功能是不够的,快照的作用是文件恢复或者文件的克隆,这一节介绍一下curve的克隆(恢复也可以认为是某种程度上的克隆)。
curve的克隆按照数据来源分,可以分为从快照进行克隆、从镜像进行克隆。按照是否数据全部克隆出来之后才提供服务可以分为延迟克隆和非延迟克隆。


 curve克隆架构图



MDS:负责文件信息和文件状态的管理,对外提供管理操作文件或者查询文件的接口。

SnapshotCloneServer:负责快照和克隆任务信息的管理,处理快照逻辑和克隆逻辑。

S3对象存储:存放快照的数据。

ChunkServer:文件数据的实际存放位置,使用读时拷贝机制支持"Lazy Clone"功能;当Client像克隆卷发起读请求时,如果读区域还未写过,从镜像(保存在curvefs)或者快照(保存在s3服务)上拷贝数据,返回给Client并异步将数据写入到chunk文件中。



克隆流程


对于curvefs的克隆来说,实际上就是将数据从源位置(源位置指拷贝的对象所在的位置,如果是从快照克隆,源位置就是对象在S3上的位置;如果是从curvefs上的文件做的克隆,源位置就是对象在ChunkServer上的位置)拷贝到目标位置(目标位置是克隆生成的chunk所在的位置)。在实际的使用场景中,很多情况下并不需要同步等待所有数据都拷贝过去,而是在实际用到数据的时候再进行拷贝。因此在克隆的时候可以设置一个lazy的标记,表示是否使用lazy方式进行克隆,如果lazy为true则表示用到时再拷贝;如果lazy为false,则需要等待所有数据拷贝到ChunkServer。由于只有在读取数据时会依赖源位置的数据,所以采用读时拷贝的策略。


创建克隆卷


  1. 用户指定要克隆的文件或快照,向SnapshotCloneServer发送创建克隆卷的请求;

  2. SnapshotCloneServer收到请求后,如果克隆对象是快照,在快照克隆系统的本地保存的快照信息中(目前持久化到etcd)中获取快照信息;如果克隆对象是文件的话,就向MDS查询文件信息。

  3. SnapshotCloneServer向MDS发送CreateCloneFile请求,对于克隆创建的新文件的初始版本应设为1,MDS收到请求后会创建新的文件,并将文件的状态设置为Cloning(文件不同状态的含义及作用会在后面章节阐述)。需要注意的事,一开始创建的文件会被放到一个临时目录下,例如用户请求克隆的文件名为“des”,那么此步骤会以"/clone/des"的名称去创建文件。

  4. 文件创建成功后,SnapshotCloneServer需要查询各个chunk的位置信息,如果克隆对象为文件,只需要获取文件名称;如果克隆对象为快照,则先获取S3上的metaobject进行解析来获取Chunk的信息;然后先通过MDS为每个Segment上的Chunk分配copyset,再调用ChunkServer的CreateCloneChunk接口为获取到的每个Chunk创建新的Chunk文件,新建的Chunk文件的版本为1,Chunk中会记录源位置信息,如果克隆对象为文件,可以以/filename/offset@cs作为location,filename表示数据源的文件名,@cs表示文件数据在chunkserver上;如果克隆对象为快照,以url@s3作为location,url表示源数据在s3上的访问url,@s3表示源数据在s3上。

  5. 当所有的Chunk都在chunkserver创建并记录源端信息后,SnapshotCloneServer通过CompleteCloneMeta接口将文件的状态修改为CloneMetaInstalled,到这一步为止,lazy的方式和非lazy的方式的流程都是一样的,两者的差别在下面的步骤中体现。

  6. 如果用户指定以lazy的方式进行克隆,SnapshotCloneServer首先会将前面创建的“/clone/des”重命名为“des”,这样一来,用户就可以访问到新建的文件并进行挂载读写。lazy克隆不会继续后面的数据拷贝,除非显式调用快照克隆系统的flatten接口,然后SnapshotCloneServer会继续循环调用RecoverChunk异步地触发ChunkServer上Chunk数据的拷贝,当所有的Chunk都拷贝成功以后,再调用CompleteCloneFile将文件的状态改为Cloned,调用时指定的文件名为“des”,因为在前面已经将文件重命名了。

  7. 而如果用户没有指定以lazy的方式进行克隆,意味着需要将所有的数据同步下来后才能对外提供服务,SnapshotCloneServer首先循环调用RecoverChunk触发ChunkServer上Chunk数据的拷贝,当所有Chunk数据拷贝下来后,调用CompleteCloneFile将文件的状态改为Cloned,此时调用时指定的文件名为“/clone/des”;然后再将“/clone/des”重命名为“des”供用户使用。


恢复文件


恢复的操作本质也是利用了克隆的过程,简单描述来说就是新建一个临时文件,将新建的文件当做克隆来处理,克隆成功后,删除原文件,然后将新文件重命名为原文件名,该文件的属性和原文件是一样的(文件名、文件大小、文件版本等),在恢复过程中相对于克隆过程主要有以下几个不同点:

  1. 恢复前会获取原文件的信息,然后用原文件的文件名、版本号和文件大小作为参数来创建新的文件,新建的文件同克隆一样放在/clone目录下;

  2. 调用CreateCloneChunk时指定的版本号为Chunk在快照中的实际版本号;

  3. 恢复成功后,在将新文件重命名为原文件名时会覆盖原文件。


写克隆卷


克隆卷的写入过程同写普通卷一样,区别在于克隆卷的chunk会记录bitmap,数据写入时,会将对应位置的bit置位1;bitmap的作用在于读克隆卷时区分哪些区域已经被写过,哪些区域未被写过需要从克隆源读取。


读克隆卷


1.客户端发起读请求;

2.chunkserver端接收到读请求后,会判断所写的chunk上记录的表示源对象位置的flag,然后会再判断读取的区域的在bitmap中对应的bit是否为1,如果不是1会触发拷贝;

3.chunkserver通过记录的位置从源对象上拷贝数据,拷贝以slice(默认1MB,可配置)为单位;

4.拷贝成功后,返回用户读取数据。


文件状态


上面过程中我们提到了克隆文件拥有多种状态,这些不同状态代表了什么含义,其作用又是什么?



  • Cloning

    克隆文件的初始状态,此时表示SnapshotCloneServer正在创建Chunk,并将Chunk的数据源位置信息装载到各个Chunk上。在这一过程中,文件不可用,用户在界面无法看到文件,只能看到任务信息。

    此状态下,对于克隆来说会在/clone目录下存在一个文件;对于恢复来说/clone目录存在一份文件同时原文件也存在。

  • CloneMetaInstalled

    如果文件处于这一状态,说明Chunk的源位置信息都已装载成功,如果用户指定了lazy的方式,此状态下的文件就可以对外提供挂载使用了;此状态过程中,SnapshotCloneServer会去触发各个Chunk从数据源处拷贝数据,此时文件还不允许打快照。

    此状态下,如果是lazy的方式,文件会被移到实际目录中,如果是恢复会先删除原文件;如果是非lazy的方式,文件所处位置同Cloning状态。

  • Cloned

    此状态下说明所有Chunk的数据已经全部完成了拷贝,此时的文件可提供所有的功能服务。

    此状态下文件会被移到实际目录中,如果是恢复会先删除原文件。


curve快照系统的接口


见快照克隆的接口文档。文档链接

文章转载自潍鲸,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论