Ceph 性能优化 之 带掉电保护的Rbd Cache
2016-04-20 19:35
856 查看
该文首次发表于’盛大游戏G云’微信公众号上,现贴到本人博客,方便大家交流学习
由于以内存作为缓存,缓存空间不能太大
还是因为以内存做缓存,所以存在掉电数据丢失的风险
为了克服原生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流经的所有组件,还请读者注意。
rbd cache size : librbd能使用的最大缓存大小
rbd cache max dirty : 缓存中允许的脏数据最大值,用来控制回写大小,不能超过rbd cache size
rbd cache target dirty :开始执行回写的脏数据阀值,不能超过rbd cache max dirty
rbd cache max dirty max :缓存中脏数据的最大缓存时间,用来避免因脏数据未达到回写要求而长时间存在
在i/o处理过程中,根据
i/o数据缓存后librbd会立即尝试合并相邻的i/o请求,以提高数据写入性能。并基于配置参数通知
下图展示了一个虚拟磁盘Image、应用i/o映射、
上图中磁盘Image逻辑切割为多个
正如上一节中所述,librbd将所有的数据都缓存在内存中,如果宿主出现掉电故障,
设计该方案时,我们需要考虑的问题有:
如何最大化利用原有的代码框架?
如何设计缓存文件的格式?
有了上述的理论基础,本着最大化利用原有代码的原则,我们设计了如下的磁盘对象与内存对象映射关系:
定义
定义
再次,从上文的分析我们了解到一个
rbd ssd cache : 缓存开关,默认开启
rbd ssd chunk size : 预设的i/o数据块大小,最小值为16(2^16 = 64KB)librbd基于此值组织缓存文件,不能超过
rbd ssd cache size : librbd能使用的最大磁盘缓存
rbd ssd cache max dirty : 缓存中允许的脏数据最大值,用来控制回写大小,不能超过rbd cache size
rbd ssd cache target dirty : 开始执行回写的脏数据阀值,不能超过rbd cache max dirty
rbd ssd cache path : 缓存文件路径
回到上面的问题,引入
加上日志文件头部
缓存文件中的各部分都按512字节对齐。另外,为了加快查找速度,
librbd收到i/o请求后,根据
从内存中的
创建
从内存中的
上述4步概要的描述了写i/o在librbd内部的处理逻辑。读i/o也是相似的处理逻辑,不同的是,Ceph集群作为了librbd的数据写入方,写入缓存文件的数据来自Ceph集群。
假定我们有如下的缓存配置:
rbd ssd chunk size = 4M
rbd ssd cache size = 10G
rbd ssd cache max dirty = 8G
rbd ssd cache target dirty = 5G
那么基于上面的计算公式:
也就是最大能缓存2560个对象,实际上写入1280(
为解决上面的问题,我们引入了“存储应用感知”功能,原理就是:librbd周期性的统计应用i/o的平均大小,动态调整
测试命令:fio –filename=/vdisk/fio –direct=1 –ioengine=libaio –rw={randwrite/randread/read/write} -numjobs=2 –bs={4k/8k/4m} –size=5G –runtime=300 –iodepth=16 –group_reporting –name=test
缓存磁盘:SAS 10000 RPM, 缓存大小 10G
通过挂载Rbd块设备到KVM虚拟机上,并格式为xfs文件系统进行测试;其中的一组测试结果如下:
表格中
至此我们对于
带掉电保护的Rbd Cache方案
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
根据上述逻辑图,Rbd Cache是Ceph块存储客户端库librbd内实现的一个缓存层,主要提供读缓存和写合并功能,用来提高读写性能,默认情况下Rbd Cache处于开启状态。需要注意的是:Ceph既支持以内核模块方式动态地为Linux主机提供块设备,也支持以Qemu Block Driver的形式为VM提供虚拟块设备,本文描述的是第二种形式。下面我们来看看Rbd Cache的内部实现
上述逻辑图并未完整画出i/o流经的所有组件,还请读者注意。
Rbd Cache的实现机制
librbd模块内默认以4MB为单位对虚拟磁盘进行切分,每个4MB的chunk称为一个Object,以
ObjectExtent为单位进行数据缓存;应用i/o通常会有不同的大小,每个i/o请求的数据以
Object为单位缓存到一个或多个
ObjectExtent中。Rbd Cache常用配置参数如下:
rbd cache size : librbd能使用的最大缓存大小
rbd cache max dirty : 缓存中允许的脏数据最大值,用来控制回写大小,不能超过rbd cache size
rbd cache target dirty :开始执行回写的脏数据阀值,不能超过rbd cache max dirty
rbd cache max dirty max :缓存中脏数据的最大缓存时间,用来避免因脏数据未达到回写要求而长时间存在
在i/o处理过程中,根据
ObjectExtent中的
<offset, len>创建数据缓存结构
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进行了改良。
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的大小。再继续前,让我们先了解下新引入的配置参数:
rbd ssd cache : 缓存开关,默认开启
rbd ssd chunk size : 预设的i/o数据块大小,最小值为16(2^16 = 64KB)librbd基于此值组织缓存文件,不能超过
Object大小
rbd ssd cache size : librbd能使用的最大磁盘缓存
rbd ssd cache max dirty : 缓存中允许的脏数据最大值,用来控制回写大小,不能超过rbd cache size
rbd ssd cache target dirty : 开始执行回写的脏数据阀值,不能超过rbd cache max dirty
rbd ssd cache path : 缓存文件路径
回到上面的问题,引入
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内部修改就是上面描述的那个样子(其实也不全是哦!亮点在下面),下面让我们一起来看看它是怎么工作的。
librbd收到i/o请求后,根据
<offset, len>将文件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大小的吗。细心的读者,一定注意到了这种假设很可能带来缓存空间的巨大浪费,而且也影响性能,让我们一起来看看到底是怎么回事吧。
假定我们有如下的缓存配置:
rbd ssd chunk size = 4M
rbd ssd cache size = 10G
rbd ssd cache max dirty = 8G
rbd ssd cache target dirty = 5G
那么基于上面的计算公式:
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,在业务不繁忙的时候, 重置缓存。
性能对比
说了这么多,来看看实际的测试效果吧,测试参数如下:测试命令:fio –filename=/vdisk/fio –direct=1 –ioengine=libaio –rw={randwrite/randread/read/write} -numjobs=2 –bs={4k/8k/4m} –size=5G –runtime=300 –iodepth=16 –group_reporting –name=test
缓存磁盘:SAS 10000 RPM, 缓存大小 10G
通过挂载Rbd块设备到KVM虚拟机上,并格式为xfs文件系统进行测试;其中的一组测试结果如下:
表格中
SAS Cache表示用SAS作缓存的情况;
Mem Cache表示原生
Rbd Cache;表格的前半部分,显示的是用单台VM测试情况下的性能对比;后半部分,显示的是用20台VM压测情况下的性能对比;结果表明,性能提升效果还是挺明显的。如果用SSD作为缓存磁盘,相信性能提升会更明显。
至此我们对于
Rbd Cache的改进介绍就结束了,如有不正确的地方欢迎指正,有更好的想法也欢迎留言。
相关文章推荐
- 深入理解PHP7内核之FAST_ZPP
- Flex 性能优化常用手法总结
- oracle 性能优化建议小结
- Lua性能优化技巧(一):前言
- Lua性能优化技巧(五):削减、重用和回收
- Lua性能优化技巧(三):关于表
- Lua性能优化技巧(四):关于字符串
- SQL Server 2016 查询存储性能优化小结
- MySQL性能优化 出题业务SQL优化
- PowerShell脚本性能优化技巧总结
- SQL SERVER性能优化综述(很好的总结,不要错过哦)第1/3页
- MySQL Index Condition Pushdown(ICP)性能优化方法实例
- Ajax无刷新分页的性能优化方法
- dedecms负载性能优化实例,三招让你的dedecms快10倍以上第1/2页
- 如何减少浏览器的reflow和repaint
- javascript日期处理函数,性能优化批处理
- Listview加载的性能优化是如何实现的
- JavaScript 性能优化小结
- JQuery教学之性能优化
- jQuery 性能优化手册 推荐