FileStore压缩存储(优化篇)

前言

前面已经分析过RBD在Ceph的文件分布,就是将一个完整的块设备,映射成大小相同的数据块,然后通过Crush算法进行Map,最后存储在文件中。FileStore承担了文件的存储工作,其实就是将文件安装PG进展组织,然后分目录存储。

原理和动机

文件从filename依次会进行以下三个步骤进行,最后落到磁盘,如下:

•filename -> object_id: filename通过hash函数直接转换成一个objectid

•object_id -> pg: object_id其实是一个整数,通过 object_id%pg)num, 对应到相应的pg, 这是一个简单暴力的映射,当pg数发生变化时,将会有大量的pg会重新映射,因此在运营过程中,尽量避免调整pg数。

•pg -> osd: pg到osd的映射,是ceph区别于其他分布式文件系统核心所在,在这个过程中,采用的是如雷贯耳的crush算法,crush算法是可以根据机架,机房等进行可以干预的伪一致性hash,而hash的依据是CrushMap。

通过上面三个步骤,真正的文件会落到{pool_id}.{pg_id_}_head的目录下,存储的是原始文件,并没有任何的修改。考虑到之前做个文件压缩方面的相关工作,如果文件先压缩后存储,是否会比裸存储会更快呢,从之前的实验表明,也有磁盘性能和CPU的性能差异巨大,如果压缩算法选择较好,压缩+存储的时间有可能会小于裸存储的时间。

压缩算法对比:

压缩算法这里不详细介绍,早期用的是LZO算法,当时觉得LZO算法是最快最好的压缩算法了,后面Google出了snappy算法,网上有对比,居然比LZO更快,而且在hadoop中广泛应用,所有本次采用了snappy算法。网上的性能对比如下:

:来自《HBase: The Definitive Guide》

•GZIP的压缩率最高,但是其实CPU密集型的,对CPU的消耗比其他算法要多,压缩和 解压速度也慢;

•LZO的压缩率居中,比GZIP要低一些,但是压缩和解压速度明显要比GZIP快很多,其 中解压速度快的更多;

•Zippy/Snappy的压缩率最低,而压缩和解压速度要稍微比LZO要快一些。

系统设计

在Ceph中,默认块大小是4M(可以修改), 每次从OSD中传输到FileStore中的数据块大小也为4M,那我们安装块大小的方式进行压缩存储,比较符合Ceph的默认行为,由于压缩以后,块大小并不统一,因此需要有个表来记录数据块的大小和偏移。那我们在文件的头部,设计了1KB大小的数据空间,来存储数据表。如下:

block_size: 文件的压缩前的真实大小

block_num: 文件压缩以后的块数

offset : 第x块的offset

block_len : 第x块的长度

为了支持超大文件和数据对齐,这里全部采用了64bit进行长度存储。那么如果头部设置了1KB,最多存储的块数是63块,按照4MB每块,最大文件为4MBx63=252MB,当然,头部大小是可以设置的。

数据结构设计

1、 宏定义:

1. #define HEADER_BLOCK_SIZE 1024 // 头部大小

2. #define HEADER_BOCK_NUM (HEADER_BLOCK_SIZE/16-1) //最大块数

2、 块描述:

1. struct BlockMeta {
2. uint64_t offset; // 偏移
3. uint64_t block_len; // 长度
4. }

3、 文件头描述:

1. class FileHeader {
2. public:
3. size_t block_size;
4. uint64_t block_num; // 为了对齐,采用64bit
5. BlockMeta block_list[HEADER_BOCK_NUM]; // 块描述列表
6. FDRef fd;
7.
8. FileHeader(FDRef fd);
9. uint64_t _get_size();
10. bool init();
11. bool save();
12. void add_block(uint64_toffset, uint64_tlen);
13. }

实现描述

实现主要涉及到FileStore.cc中的两个函数FileStore::_write和FileStore::read两个函数,主要修改如下:

FileStore::_write

1. if(cid==meta) // 判断是不是meta,不是meta才进行压缩
2. is_meta = true;
3.
4. r = lfn_open(cid, oid, true, &fd); // 打开文件
5. if (r < 0) {
6. ...
7. goto out;
8. }
9.
10. if(!is_meta){ // 打开文件头
11. fh = new FileHeader(fd);
12. fh->init();
13. offset = convert_offset(fh, offset, len); // 这边很关键,转换offset
14. }
15.
16. // seek
17. actual = ::lseek64(**fd, offset, SEEK_SET);
18. if (actual < 0) {
19. ...
20. }
21.
22. if(!is_meta){
23. // write
24. size_t len_bz;
25. string out_bz;
26. string tmp;
27. // 由于bufferlist和snappy的compress的类型有差异,需要先将buffer拷贝到string中
28. // 后续看是否能够优化
29. bl.copy(0, len, tmp);
30. // 采用snanny压缩
31. len_bz = snappy::Compress(tmp.c_str(), len, &out_bz);
32.
33. // 压缩后的数据写入文件
34. r = safe_pwrite(**fd, out_bz.c_str(), len_bz, offset);
35. if (r == 0){
36. // 更新文件头
37. r = len_bz;
38. fh->add_block(offset, len_bz);
39. fh->save();
40. }
41. }else{
42. // write
43. r = bl.write_fd(**fd);

44. if (r == 0)
45. r = bl.length();
46. }

FileStore::read

1. if(cid==meta) // 判断是不是meta,不是meta才进行压缩
2. is_meta = true;
3.
4. int r = lfn_open(cid, oid, false, &fd);
5. if (r < 0) {
6. ...
7. return r;
8. }
9.
10. if(!is_meta){
11. fh = new FileHeader(fd); // 获取文件头
12. fh->init();
13. if (len == 0) {
14. len = fh->get_block_size();
15. }
16. }else{
17. if (len == 0) {
18. ...
19. len = st.st_size;
20. }
21. }
22.
23. if(!is_meta){
24. // 由于文件启用了压缩,需要对offset和len进行转换
25. uint64_t offset_bz = convert_offset(fh, offset, len);
26. uint64_t len_bz = convert_len(fh, offset, len);
27. bufferptr bptr_bz(len_bz);
28. // 读取压缩后的文件到buffsetlist
29. got = safe_pread(**fd, bptr_bz.c_str(), len_bz, offset_bz);
30.
31. if (got < 0) {
32. ...
33. return got;
34. }
35.
36. // 解压buffer
37. string out_unbz;
38. size_t len_unbz;
39. snappy::GetUncompressedLength(bptr_bz.c_str(), len_bz, &len_unbz);
40. snappy::Uncompress(bptr_bz.c_str(), len_bz, &out_unbz);
41. // 这里也进行了一次拷贝,后面考虑优化一下
42. bufferptr bptr(out_unbz.c_str(), len_unbz);
43. bptr_bz.set_length(len_unbz); // properly size the buffer
44. bl.push_back(bptr); // put it in the target bufferlist
45. }else{
46.
47. bufferptr bptr(len); // prealloc space for entire read
48. got = safe_pread(**fd, bptr.c_str(), len, offset);
49. if (got < 0) {
50. ...
51. return got;

52. }
53. bptr.set_length(got); // properly size the buffer
54. bl.push_back(bptr); // put it in the target bufferlist
测试

测试方法:

采用rados bench进行测试,向test pool中写入数据,时间30s,对比各项指标。

测试机器:

本次测试在vmware虚拟机上进行,配置如下:

cpu: i7-4790 CPU @ 3.60GHz * 4

_内存: 8G

磁盘: 20G * 4 (4个虚拟磁盘)

Ceph OSD配置:

1. [root@centos7 os]# ceph osd tree
2. # id weight type name up/down reweight
3. -1 4 root default
4. -3 4 rack rack01
5. -2 4 host node01
6. 0 1 osd.0 up 1

7. 1 1 osd.1 up 1
8. 2 1 osd.2 up 1
9. 3 1 osd.3 up 1

测试过程:

a、在没有启用压缩的Ceph集群上测试结果如下:

1. [root@centos7 ~]# rados bench -p test 30 write --no-cleanup
2. Maintaining 16 concurrent writes of 4194304bytes for up to 30 seconds
or 0 objects
3. Object prefix: benchmark_data_centos7_4889
4. sec Cur ops started finished avg MB/s cur MB/s last lat avg
lat
5. 0 0 0 0 0 0 - 0
6. 1 16 26 10 39.9737 40 0.793068 0.492239
7. 2 16 39 23 45.9619 52 1.49245 0.981307
8. ...
9. 31 16 181 165 21.2693 0 - 2.52853
10. 32 16 182 166 20.7295 2 5.26863 2.54504
11. 33 16 182 166 20.1013 0 - 2.54504
12. 34 16 182 166 19.5105 0 - 2.54504
13. Total time run: 34.639023
14. Total writes made: 182
15. Write size: 4194304
16. Bandwidth (MB/sec): 21.017
17.
18. Stddev Bandwidth: 19.3491
19. Max bandwidth (MB/sec): 64
20. Min bandwidth (MB/sec): 0
21. Average Latency: 3.03429

22. Stddev Latency: 2.70649
23. Max latency: 10.2387
24. Min latency: 0.104269

注: 上面结果均测试了3次,取了一次中间结果。

测试结果:

:上面的优化结果是采用[压缩存储数值]/[原始存储数值], (+)表示提升,(-)表示下降。

结果分析:

从上面的结果来看,吞吐率提高了1倍多,延迟降低超过50%,无论是吞吐率的方差,还是延迟的方差,都有很明显的下降,说明采用了压缩以后,系统性能稳定了很多,尤其是延迟方差,降低了将近80%。当然,由于测试的数据压缩比率比较高,性能偏好,在实际应用的时候,会受数据可压缩率的影响。

总结

本文从可FileStore层面,对Ceph做了压缩存储的优化,从测试效果来看,还是非常理想的,几乎提升了1倍多的性能。但是在实际应用中会受数据可压缩性的影响,启用压缩存储,对一些日志行的数据存储,是非常理想的选择。此工作只是一个优化的开始,其实后续还有很多的优化空间,比如,对数据进行动态选择压缩,或者在压缩的路径上进行优化,可以避免每个osd都重复压缩。

原文发布于微信公众号 - TStack(gh_035269c8aa5f)

原文发表时间:2017-05-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏何俊林

使用TensorFlow进行训练识别视频图像中物体

本教程针对Windows10实现谷歌公布的TensorFlow Object Detection API视频物体识别系统,其他平台也可借鉴。

6162
来自专栏北京马哥教育

几种经典的网络服务器架构模型的分析与比较

前言 事件驱动为广大的程序员所熟悉,其最为人津津乐道的是在图形化界面编程中的应用;事实上,在网络编程中事件驱动也被广泛使用,并大规模部署在高连接数高吞吐量的服务...

2895
来自专栏编程

SAS-编程中的小技巧

今天分享的是SAS软件使用过程的中的几个小技巧,掌握了一些小技巧,编程的效率会提高的更快,还能减少敲代码出错率,好处很多,小编就不一一赘述了。 ...

2908
来自专栏Java后端技术栈

面试必备:什么是一致性Hash算法?

最近有小伙伴跑过来问什么是Hash一致性算法,说面试的时候被问到了,因为不了解,所以就没有回答上,问我有没有相应的学习资料推荐,当时上班,没时间回复,晚上回去了...

1721
来自专栏腾讯安全应急响应中心

短网址安全浅谈

何谓短网址(Short URL)?顾名思义,就是形式上比较短的网址,当前主要是借助短网址来替代原先冗长的网址,方便传输和分享。短网址服务也就是将长网址转换为短网...

4430
来自专栏用户2442861的专栏

Redis作者谈Redis应用场景

毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis...

2652
来自专栏zhisheng

机器常见需要关注的监控指标

做运维,不怕出问题,怕的是出了问题,抓不到现场,两眼摸黑。所以,依靠强大的监控系统,收集尽可能多的指标,意义重大。但哪些指标才是有意义的呢,本着从实践中来的思想...

2541
来自专栏机器之心

专栏 | 想免费用谷歌资源训练神经网络?Colab详细使用教程

62111
来自专栏码匠的流水账

聊聊partition的方式

一般来说,数据库的繁忙体现在:不同用户需要访问数据集中的不同部分,这种情况下,我们把数据的各个部分存放在不同的服务器/节点中,每个服务器/节点负责自身数据的读取...

1661
来自专栏Python自动化测试

CSV文件在网络爬虫中的应用

在上一个文章中详细的介绍了CSV文件内容的读取和写入,那么在本次文章中结合网络爬虫的技术,把数据获取到写入到CSV的文件中,其实利用爬虫的技术...

1954

扫码关注云+社区

领取腾讯云代金券