温水青蛙 2019-05-24
Ceph可以提供文件、块和对象三种类型的存储形式,但最为主要的存储形式就是块存储。Ceph块存储可以直接与云计算平台进行对接,比如OpenStack等。另外,Ceph提供了访问块存储的API和内核模块。内核模块解决了通过裸机访问Ceph块存储的问题。本文主要介绍一下用户态块存储客户端的架构和基本的读写流程,后续再详细介绍内核模块客户端的实现。
图1 Ceph的块存储
在介绍客户端实现之前我们有必要介绍一下Ceph块存储实现的基本原理。我们知道块存储(磁盘)在用户端呈现的其实就是一个线性空间,如图2蓝色长条。Ceph的做法是将该空间切割为等长的段,并用对象存储表示,而对象名称就是磁盘的LBA。
图2 Ceph块存储原理
比如我们创建了一个1G的块存储,并采用Ceph默认的策略(每4MB用一个对象存储)。那么,如果在磁盘的开头有写数据(比如1KB),客户端就会创建一个4MB的对象,名称格式为image_hash_lba。如图3是一个块存储存储数据的对象的实例,其中红色方框中的内容正是数据的LBA。
图3 块存储对象的名称实例
总的来说,Ceph的块存储是转换为对象来存储的。
在后续分析块存储架构之前,我们先了解一下客户端的API及使用方法。下面给出一些代码片段,这里并非完整的代码,也没有异常处理,仅仅用于说明基本流程。
了解到上述流程后,我们知道与块存储相关的有2个类,分别是RBD和Image。其中RBD是块存储系统相关的类,用于实现块存储相关的管理操作,比如创建删除镜像、创建镜像组和镜像的复制配对等等操作。而Image类则用于具体镜像(磁盘)的操作,比如镜像数据的读写、镜像属性和快照的创建删除等等。
在介绍块存储架构之前,我们先看一下块存储的使用流程。从上面代码可以看到这里核心的有2点,一个是创建一个IO上下文,这个是实现对象级数据读写的,我们在librados中曾经介绍过;另外一个是通过rbd.open打开一个块存储设备,而这里面最为重要的是传入了IO上下文参数(IoCtx)。
如果我们再继续深入介绍之前,我们先看一下rbd.open函数,如下是该函数的源代码(省略了部分不重要的内容)。从代码中可以看出这里的核心是创建了一个ImageCtx对象,并且通过参数传出。而传出的参数就是我们后面需要使用的访问磁盘的对象。同时,这里还需要注意的是构建ImageCtx对象时讲IO上下文传入其中,因此后续的IO操作就可以通过该上下文接口实现。
对于Ceph块存储客户端可以简单的划分为3层,分别是API接口层、块存储逻辑层和lirados层。当然,实际情况比这个要复杂的多(例如前文librados就包含4层),我们这里为了简化描述,暂且认为只包含这3层。后续,我们将深入介绍没一层。
图2 块设备基本架构
结合上述3层架构,我们看一下每层涉及的类。在API层主要包括RBD和Image 2个类,关于这两个类的作用我们在前文已经做过介绍,这里不在赘述。
图3 块存储结构类图
在块存储逻辑层最主要的类是ImageCtx类,其中主要包含CephContext、ImageRquestWQ和ObjectDispatcher类。其中ImageCtx类为块设备(Image)上下文类,该类负责处理块设备相关的,核心是实现块设备线性空间到对象存储之间的转换。除此之外,还包括一些高级功能,包括磁盘镜像、快照和磁盘组等。
上述ImageRequestWQ类是一个IO相关的队列类,接口层的异步请求会缓存到该队列中,并通过起内部接口实现IO的发送。而ObjectDispatcher类则负责将转换后的请求(对象请求)发送到RADOS集群中。
前面了解了RBD的基本架构及涉及的主要类,下面我们以写数据为例介绍一下其主要的流程。写流程的发起是以Image类的write函数开始的。整个调用流程如图4所示。
图4 写数据基本流程
具体细节我们不做介绍,这里需要注意的一点是调用了一个名为file_to_extents的函数,该函数将块设备的IO请求转化为对对象的IO请求。在该函数中,会根据块层IO的偏移和镜像的属性将IO切割为适合进行对象操作的IO序列。并且进一步的交给下层进行具体的处理。
好了,今天先到这里,今天我们介绍了Ceph块存储客户端的基本架构和写数据的流程,并对涉及的类进行了简单的介绍。后续,我们将进一步的深入细节,介绍块存储关键流程的实现。