该文首次发表于’盛大游戏G云’微信公众号上,现贴到本人博客,方便大家交流学习
Ceph是一款开源的统一存储,在单一的系统上提供块、对象及文件存储接口。近年随着公有云/私有云的快速普及,凭借其自身良好的稳定性、扩展性及与Openstack的深度整合,Ceph Rbd块存储被大量的使用,作为VM的数据存储。有Ceph Rbd部署实践经验的IT工程师们对Rbd Cache一定不会陌生,它是Ceph Rbd客户端缓存,开启后能显著提高快设备i/o性能,但是它存在两个问题:
为了克服原生Rbd Cache存在的上述不足,盛大游戏G云对Rbd Cache进行了改良,我们的方案是:用高速非易失存储介质(如:SSD、SAS)替换内存作为Rbd Cache,通过用空间换时间的方式,保证i/o性能并规避上述的缺陷。下文将对原生Rbd Cache及改良后的方案分别加以说明。
下文称未经修改的Ceph Rbd Cache为原生Rbd Cache,改良后的方案称为G云版Rbd Cache,引用的对象术语及代码片段来自Hammer Ceph-0.94.1
根据上述逻辑图,Rbd Cache是Ceph块存储客户端库librbd内实现的一个缓存层,主要提供读缓存和写合并功能,用来提高读写性能,默认情况下Rbd Cache处于开启状态。需要注意的是:Ceph既支持以内核模块方式动态地为Linux主机提供块设备,也支持以Qemu Block Driver的形式为VM提供虚拟块设备,本文描述的是第二种形式。下面我们来看看Rbd Cache的内部实现
上述逻辑图并未完整画出i/o流经的所有组件,还请读者注意。
librbd模块内默认以4MB为单位对虚拟磁盘进行切分,每个4MB的chunk称为一个Object
,以ObjectExtent
为单位进行数据缓存;应用i/o通常会有不同的大小,每个i/o请求的数据以Object
为单位缓存到一个或多个ObjectExtent
中。Rbd Cache常用配置参数如下:
在i/o处理过程中,根据ObjectExtent
中的
创建数据缓存结构BufferHead
或者合并/拆分原有BufferHead
得到一个满足当前i/o需求的BufferHead
并将i/o数据缓存到该结构中,BufferHead
添加到bh_lru_dirty
队列。
i/o数据缓存后librbd会立即尝试合并相邻的i/o请求,以提高数据写入性能。并基于配置参数通知flush_thread
线程向后端Ceph集群发送i/o数据,i/o请求完成BufferHead
从bh_lru_dirty
队列移除并添加到bh_lru_rest
队列。当缓存数据达到缓存大小限制后,bh_lru_rest
队列中的数据会被删除。
下图展示了一个虚拟磁盘Image、应用i/o映射、Object
及BufferHead
的关系:
上图中磁盘Image逻辑切割为多个Object
,每个Object
可能包含0到多个BufferHead
,每个BufferHead
包含一个应用数据片段,如果是脏数据会被加入到bh_lru_dirty
队列,如果数据已经下刷到Ceph集群就会加入到bh_lru_rest
队列;
正如上一节中所述,librbd将所有的数据都缓存在内存中,如果宿主出现掉电故障,bh_lru_dirty
队列中的脏数据将丢失,也就是用户数据丢失。DT时代,数据是企业的核心资产,关键业务数据的丢失会给企业带来不可估量的损坏。为解决该缺陷,盛大游戏G云对原生的Rbd Cache进行了改良。
我们的方案是:将Rbd Cache
移到高速非易失性存储介质(如:SSD
,SAS
),每个虚拟磁盘的Rbd Cache
是存储介质上的一个文件。多个虚拟磁盘的Rbd Cache
可以存储在同一个存储介质上。通过使用更大的磁盘缓存空间,空间换时间思想,实现i/o的高速读写及避免掉电引起的数据丢失。
设计该方案时,我们需要考虑的问题有:
由上文的分析我们知道,librbd内部与i/o紧密相关的结构主要有Object
、BufferHead
以及bufferlist
,Object
是虚拟磁盘Image基于固定大小逻辑切分的一个数据块,从0开始编号;BufferHead
可以理解为Object
内的一个连续数据分片,数据分片大小随机,BufferHead
之间的间隔表示空闲空间,由于应用i/o大小不固定,一个BufferHead
可能被拆分,相邻的BufferHead
也可能被合并;BufferHead
中包含的应用数据实际存储在bufferlist
标示的内存结构中。三个结构间的关系如下;
有了上述的理论基础,本着最大化利用原有代码的原则,我们设计了如下的磁盘对象与内存对象映射关系:
ObjectMeta
结构,持久存储Object
对象信息,并定义相关的转换接口BufferMeta
结构,持久存储BufferHead
对象信息,并定义相关的转换接口bufferlist
是承载用户数据的内存区,直接用缓存文件存储用户数据数据结构映射关系建立起来了,那该按怎样一种方式在缓存文件中组织各种对象,并能实现高效的对象查找、更新呢?由于上层应用i/o的不确定性,缓存层可能就会出现大量的随机操作,缓存层的查找算法效率的高低直接决定了缓存层的性能。从实现的复杂度和效率两个维度考虑,我们最终选择了用bitmap
来组织管理各种对象。
再次,从上文的分析我们了解到一个Object
对象包含的Bufferhead
对象个数是动态变动的,并且每个Bufferhead
包含的数据长度也各不一样;换句话说就是,缓存文件中每个ObjectMeta
对象包含的BufferMeta
对象个数不固定。然而采用bitmap
来组织对象,需要预先知道各类对象的个数,这样才能确定bitmap
的大小。为了解决这组矛盾,我们引入了一个参数rbd ssd chunk size
来表示上层应用i/o的大小。再继续前,让我们先了解下新引入的配置参数:
Object
大小回到上面的问题,引入rbd ssd chunk size
参数后,我们基于如下的公式来确定各对象的个数:
ObjectMeta
的个数 = rbd ssd cache size
/rbd ssd chunk size
BufferMeta
的个数 = ObjectMeta
的个数 * 4加上日志文件头部FileHead
,最终的缓存文件格式是这样的:
缓存文件中的各部分都按512字节对齐。另外,为了加快查找速度,FileHead
及所有的bitmap
会加载到内存,同时在原有的Object
及BufferHead
结构中增加了offset
字段,分别用来加快ObjectMeta
及BufferMeta
的查找更新,如下:
class Object:public LRUObject {
...
loff_t offset; //对应的ObjectMeta在缓存文件中的位置
...
}
class BufferHead : public LRUObject {
...
loff_t offset; //对应的BufferMeta在缓存文件中的位置
...
}
盛大游戏G云版的Rbd Cache
内部修改就是上面描述的那个样子(其实也不全是哦!亮点在下面),下面让我们一起来看看它是怎么工作的。
将文件i/o映射为块设备i/o,生成ObjectExtent
数组Obj Bitmap
, 为ObjectExtent
数组中指向的Object
获取一个存放位置,创建ObjectMeta
对象并赋值,然后存储到缓存文件上述指定的位置处BufferHead
或者从缓存中合并/拆分出满足要求的BufferHead
,接着创建BufferMeta
对象并用前述BufferHead
对其赋值,然后从内存中的Buffer Bitmap
获取一个空闲位置,将BufferMeta
存储到缓存文件中上述指定位置处Chunk Bitmap
获取一个空闲位置,将i/o数据存储到缓存文件上述指定位置处上述4步概要的描述了写i/o在librbd内部的处理逻辑。读i/o也是相似的处理逻辑,不同的是,Ceph集群作为了librbd的数据写入方,写入缓存文件的数据来自Ceph集群。
还记得上面我们是基于预设的rbd ssd chunk size
来确定缓存文件bitmap
大小的吗。细心的读者,一定注意到了这种假设很可能带来缓存空间的巨大浪费,而且也影响性能,让我们一起来看看到底是怎么回事吧。
假定我们有如下的缓存配置:
那么基于上面的计算公式:
ObjectMeta
个数 = 10G/4M = 2560也就是最大能缓存2560个对象,实际上写入1280(rbd ssd cache target dirty = 5G
)个对象后会写线程就会触发回写,写入2048个对象后清理线程就会触发缓存清理, 由于缓存文件的独占访问,写入性能会极大的下降。如果应用i/o的平均大小是我们设定的4M,那持续写入5G的数据才触发回写,这也是我们期望的;但如果应用i/o的平均大小是64KB呢,写入160M(2560*64KB)就触发了回写。一个是5G,一个是160M,空间浪费有多大不言而喻。用户看到的现象是,10G的缓存,怎么写了320M就满了,性能达不到要求。
为解决上面的问题,我们引入了“存储应用感知”功能,原理就是:librbd周期性的统计应用i/o的平均大小,动态调整rbd ssd chunk size
,在业务不繁忙的时候, 重置缓存。
说了这么多,来看看实际的测试效果吧,测试参数如下:
通过挂载Rbd块设备到KVM虚拟机上,并格式为xfs文件系统进行测试;其中的一组测试结果如下:
表格中SAS Cache
表示用SAS作缓存的情况;Mem Cache
表示原生Rbd Cache
;表格的前半部分,显示的是用单台VM测试情况下的性能对比;后半部分,显示的是用20台VM压测情况下的性能对比;结果表明,性能提升效果还是挺明显的。如果用SSD作为缓存磁盘,相信性能提升会更明显。
至此我们对于Rbd Cache
的改进介绍就结束了,如有不正确的地方欢迎指正,有更好的想法也欢迎留言。