ceph rgw:compression实现

背景

compression是基于Placement Targets的,是其中的一个配置项。

Placement Targets

我们知道,在默认情况下,bucket index、bi log是存储在<zone>.rgw.buckets.index pool中的,object data是存储在<zone>.rgw.buckets.data pool中的,这其实是由配置在zone上的default-placement决定的。

Placement Targets可以让radosgw同时拥有多组不同的数据pool。用户上传数据时,可以选择不同Placement Targets,将数据存储到不同的pool中。不仅如此,还可以在Placement Targets上配置压缩算法等属性。使使用该规则上传的数据被压缩存储。

与Placement Targets相关的配置项主要有以下几个:

$ ./bin/radosgw-admin zone get --rgw-zone=default
{
    ......
    
    "placement_pools": [
        {
            "key": "default-placement", // 名称
            "val": {
                "index_pool": "default.rgw.buckets.index",
                "data_pool": "default.rgw.buckets.data",
                "data_extra_pool": "default.rgw.buckets.non-ec",
                "index_type": 0,  
                "compression": "" //采用的数据压缩算法
            }
        }
    ],
    
    ......
}

$ ./bin/radosgw-admin zonegroup get --rgw-zonegroup=default
{
    ......
    
    "placement_targets": [
        {
            "name": "default-placement", //需要是zone中已存在的key,否则无效
            "tags": []   //该placement pools对应的tags,用于给用户授权
        }
    ],
    "default_placement": "default-placement",
    
    ......
}

zonegroup和user的配置项中都有一项default_pacement配置,如果user的配置为“”,那么采用zonegroup的配置,不为空则以user中的配置为准。

$ ./bin/radosgw-admin user info --uid=testid
{
    ......
    
    "default_placement": "",  //该用户操作时的默认placement pools
    "placement_tags": [],     //该用户有权使用的placement pools

    ......
}

使用时,我们需要先创建一组pool,然后在zone的配置中增加placement_pools配置,然后在zonegroup中用placement_targets项,为zone中的placement_pools指定tags。最后在用户的placement_tags中加入对应的tags为用户授权。

Compression

介绍

文档:http://docs.ceph.com/docs/luminous/radosgw/compression/

在前面的placement pools的配置中,有这样一项配置:
"compression": "" //采用的数据压缩算法,这就是我们的压缩功能的唯一配置点,对于每条placement_pools,我们可以为其配置数据压缩算法。

要点

  1. 配置压缩算法之后,用户上传的每个对象都会以该算法进行压缩,然后存入rados中,要注意的是,某rgw对象使用的压缩算法会一并保存在rados中。所以在用户更改压缩算法之前上传的对象,不会被新的压缩算法所影响,其仍然保持其原有的压缩方式,并会被正确解压。

  2. 当用户上传对象时使用了sse(服务端加密),该对象不会被compressed。GetObj代码逻辑似乎可以同时支持sse和compression,但PutObj的逻辑不支持。这里mark下,之后持续关注下。

  3. 目前支持4种压缩算法,分别是 snappy、 zlib、 zstd、 以及lz4;但lz4算法需要在编译前定义HAVE_LZ4宏才会被支持。ceph默认是不支持的。另外,compression还可以设置成random,这时会随机选择一个算法。根据注释描述,random是用于测试的,不建议使用。

  4. 当使用Multipart Upload时,每一个part都会以上传时的compression算法进行压缩。所以,如果在Multipart Upload期间,修改了compression算法,会导致多个parts之间压缩算法不一致,最终在用户发送完成Multipart请求时,会被检查出来,并报错返回。

  5. 当进行range读时,用户传入的偏移量是压缩前的偏移量,所以需要通过对象上传时在对象xattr存入的blocks列表做转换,blocks列表中存储了每个块的原始尺寸和偏移以及压缩后的尺寸和偏移。range读取时,用户传入对象的起始偏移ofs和结束偏移end,先根据blocks的old_ofs对ofs和end分别做一次二分查找,找到ofs所在的块和end所在的块,然后将范围内的数据块读取并解压后,再根据原始偏移取出需要的部分。

  6. 各压缩算法压缩比率、资源消耗

三种算法的压缩比例,计算方法:压缩后size/压缩前size

总结:三种算法对文本压缩效果显著,对视频和图片收效甚微。

算法 文本压缩比 图片压缩比 视频压缩比
zlib 0.0739 0.924 0.974
zstd 0.0733 0.931 0.974
snappy 0.151 0.925 0.988

在radosgw节点上使用s3cmd的上传延迟,5次平均

算法 单个40MB文本文件 单个78MB视频文件
zlib 2.0s 7.5s
zstd 3.0s 9.0s
snappy 2.0s 5.0s
不压缩 3.0s 5.0s

总结:压缩后数据体积更小可节省一定的网络传输时间,但会消耗额外的压缩时间。总上传延迟取决于两者的差值。

算法 单个40MB文本文件 单个78MB视频文件
zlib 2.0s 7.5s
zstd 3.0s 9.0s
snappy 2.0s 5.0s
不压缩 3.0s 5.0s

三种算法运行时的cpu占用

总结:压缩算法会占用大量cpu资源,内存变化不大。

算法 cpu峰值 内存占用
snappy 35% 变化不大
zlib 80% -
zstd 80% -
不压缩 20% -

使用

如果只是想在已有的数据pool上打开compression,只需要修改对应的zone的配置:

$ ./bin/radosgw-admin zone placement modify --rgw-zone=default --placement-id=default-placement --compression=zlib

但如果要对新创建的pool设置压缩选项,最好是创建新的placement pools配置项,如果选择替换原有placement pools配置中的pool,会使被替换的pool游离在rgw之外,无法通过rgw读取和写入它的数据。

假设现在用户想要创建创建一组额外的存储pool,并需要在pool上开启compression功能。相关的步骤如下:
--index-pool=<pool> placement target index pool
--data-pool=<pool> placement target data pool
--data-extra-pool=<pool> placement target data extra (non-ec) pool
--placement-index-type=<type>
placement target index type (normal, indexless, or #id)
--compression=<type> placement target compression type (plugin name or empty/none)

1.创建对应的pool

$ ceph osd pool create default.rgw.buckets-compression.index 8
$ ceph osd pool create default.rgw.buckets-compression.data 32
$ ceph osd pool create default.rgw.buckets-compression.non-ec 8

2.在zone的配置中加入新的placement pools

这里有两种配置的方式,一种是通过radosgw-admin zone placement add命令,配合相关的options进行配置。另一种方式是使用radosgw-admin zone get/set通过编辑json文件来设置,这里选择前者。

$ radosgw-admin zone placement add --rgw-zone=default --placement-id=compression --index-pool=default.rgw.buckets-compression.index --data-pool=default.rgw.buckets-compression.data --data-extra-pool=default.rgw.buckets-compression.non-ec --compression=zlib

结果如下:

{
    ......
    "placement_pools": [
        {
            "key": "compression",
            "val": {
                "index_pool": "default.rgw.buckets-compression.index",
                "data_pool": "default.rgw.buckets-compression.data",
                "data_extra_pool": "default.rgw.buckets-compression.non-ec",
                "index_type": 0,
                "compression": "zlib"
            }
        },
        {
            "key": "default-placement",
            "val": {
                "index_pool": "default.rgw.buckets.index",
                "data_pool": "default.rgw.buckets.data",
                "data_extra_pool": "default.rgw.buckets.non-ec",
                "index_type": 0,
                "compression": ""
            }
        }
    ],
    ......
}

3.在zonegroup的配置中设置tags

$ radosgw-admin zonegroup placement add --rgw-zonegroup=default --placement-id=compression --tags="compress"

结果如下:

[
    {
        "key": "compression",
        "val": {
            "name": "compression",
            "tags": [
                "compress"
            ]
        }
    },
    {
        "key": "default-placement",
        "val": {
            "name": "default-placement",
            "tags": []
        }
    }
]

4.通过配置placement tags为user授权

在授权之前,我们试试看能否在ssd-placement对应的pool中进行操作。

$ radosgw-admin metadata get user:testid > /tmp/json

$ cat /tmp/json | grep placement
        "default_placement": "default-placement",
        "placement_tags": [],

$ s3cmd mb --bucket-location=':default-placement' s3://buckettwo
Bucket 's3://buckettwo/' created

$ s3cmd mb --bucket-location=':compression' s3://bucketthree
ERROR: Access to bucket 'bucketthree' was denied
ERROR: S3 error: 403 (AccessDenied)

给testid授权并测试权限。成功。

$ cat /tmp/json | grep placement
        "default_placement": "default-placement",
        "placement_tags": ["compress"],

$ radosgw-admin metadata put user:testid < /tmp/json

$ s3cmd mb --bucket-location=':compression' s3://bucketthree
Bucket 's3://bucketthree/' created

测试上传数据是否被压缩

$ s3cmd mb s3://bucket2 --bucket-location=':compression'
$ dd if=/dev/sda1 of=/tmp/m bs=4096 count=10000
$ s3cmd put /tmp/m s3://bucket2

size_utilizedsize_kb_utilized为实际占用的磁盘空间,可以看出,压缩生效了。

$ ./bin/radosgw-admin bucket stats --bucket=bucket2
{
    "bucket": "bucket2",
    "zonegroup": "f14e1358-dda3-470e-b395-7a8bd5a9aeee",
    "placement_rule": "ssd-placement",
    ......
    "usage": {
        "rgw.main": {
            "size": 40960000,
            "size_actual": 40960000,
            "size_utilized": 258473,
            "size_kb": 40000,
            "size_kb_actual": 40000,
            "size_kb_utilized": 253,
            "num_objects": 1
        },
        "rgw.multimeta": {
            "size": 0,
            "size_actual": 0,
            "size_utilized": 0,
            "size_kb": 0,
            "size_kb_actual": 0,
            "size_kb_utilized": 0,
            "num_objects": 0
        }
    },
    ......
}

实现

存储位置

我们在zone上设置的placement targets(包含compression属性)被存储在.rgw.root pool中的对象中。

$ ./bin/rados -p .rgw.root ls
zone_info.c3682995-3a15-4161-b55a-a7f48ddbdbee
zonegroup_info.f14e1358-dda3-470e-b395-7a8bd5a9aeee
zone_names.default
zonegroups_names.default

作用机制

对象上传/读取

上传对象并压缩的相关代码在RGWPutObj::execute()函数中,要点代码如下:

   // filter用于对数据进行处理,比如加密和压缩
  RGWPutObjDataProcessor *filter = nullptr;
  ......
  // 根据zone配置选择object的压缩类型,可为none或具体的压缩插件名字
  // http://docs.ceph.com/docs/kraken/radosgw/compression/
  const auto& compression_type = store->get_zone_params().get_compression_type(
      s->bucket_info.placement_rule);
  CompressorRef plugin;
  boost::optional<RGWPutObj_Compress> compressor;
  ......
  // 需要加密时,filter用于加密数据
  if (encrypt != nullptr) {
    filter = encrypt.get();
  } else {
    //no encryption, we can try compression
    if (compression_type != "none") {
      // 不需要加密时,并且compression_type被设置了,filter被用于压缩数据
      plugin = get_compressor_plugin(s, compression_type);
      if (!plugin) {
        ldout(s->cct, 1) << "Cannot load plugin for compression type "
            << compression_type << dendl;
      } else {
        //  如果一切都没问题,构造compressor
        compressor.emplace(s->cct, plugin, filter);
        filter = &*compressor;
      }
    }
  }
  ......

  do{
    ......
    // 在put_data_and_throttle函数中,会调用filter->handle_data,对数据进行压缩或加密
    // 然后通过next调用`RGWPutObjProcessor_Atomic`的`handle_data`
    // 将处理后的数据切分成一个head和多个tail对象
    // handle_data最终调用`store->aio_put_obj_data`函数,将对象写入rados
    op_ret = put_data_and_throttle(filter, data, ofs, need_to_wait);
    ......
  } while (len > 0);
  ......
  //  如果进行了压缩,将压缩信息加入xattr
  if (compressor && compressor->is_compressed()) {
    bufferlist tmp;
    RGWCompressionInfo cs_info;
    cs_info.compression_type = plugin->get_type_name();
    cs_info.orig_size = s->obj_size;
    cs_info.blocks = move(compressor->get_compression_blocks());
    ::encode(cs_info, tmp);
    attrs[RGW_ATTR_COMPRESSION] = tmp;
    ldout(s->cct, 20) << "storing " << RGW_ATTR_COMPRESSION
        << " with type=" << cs_info.compression_type
        << ", orig_size=" << cs_info.orig_size
        << ", blocks=" << cs_info.blocks.size() << dendl;
  }
  ......
  // 完成之前未完成的head和tail的写入,为head设置xattr
  op_ret = processor->complete(s->obj_size, etag, &mtime, real_time(), attrs,
                               (delete_at ? *delete_at : real_time()), if_match, if_nomatch,
                               (user_data.empty() ? nullptr : &user_data));
  ......

上述代码中的compressor负责进行数据的压缩,在rgw_compression.h/cc中,是其压缩和解压的函数实现。在压缩函数中,每次传入存有部分对象数据的bufferlist,压缩之后的内容放入一个名为blocks的数组(并没有另外分配空间,压缩的数据会覆盖原有bufferlist的数据),其实这个blocks数组仅仅起到一个管理的作用,用于存储压缩数据的界限,用于之后的解压。之后可以通过compressor->get_compression_blocks()获得blocks的数据,会被编码进对象的xattr。

当使用Multipart Upload时,每一个part都会以上传时的compression算法进行压缩。所以,如果在Multipart Upload期间,修改了compression算法,会导致多个parts之间压缩算法不一致,最终在用户发送完成Multipart请求时,会被检查出来,并报错返回。相关代码在RGWCompleteMultipart::execute()函数中。

对象读取的流程基本和上传对应,需要注意的是,range读取操作。用户传入的偏移量是压缩前的偏移量,所以需要通过对象上传时在对象xattr存入的blocks列表做转换,blocks列表中存储了每个块的原始尺寸和偏移以及压缩后的尺寸和偏移。range读取时,用户传入对象的起始偏移ofs和结束偏移end,先根据blocks的old_ofs对ofs和end分别做一次二分查找,找到ofs所在的块和end所在的块,然后将范围内的数据块读取并解压后,再根据原始偏移取出需要的部分。

压缩/解压实现

至于具体的压缩算法则在ceph/src/compressor/中实现,目前支持4种压缩算法,分别是snappy, zlib, zstd ,还有lz4(可能不完善,需要在编译前定义HAVE_LZ4宏才会支持)。

下面的函数是用于根据compression配置获得compression algorithm的函数,我们看到lz4算法需要定义HAVE_LZ4宏才会被支持默认是不支持的。

boost::optional<Compressor::CompressionAlgorithm> Compressor::get_comp_alg_type(const std::string &s) {
  if (s == "snappy")
    return COMP_ALG_SNAPPY;
  if (s == "zlib")
    return COMP_ALG_ZLIB;
  if (s == "zstd")
    return COMP_ALG_ZSTD;
#ifdef HAVE_LZ4
  if (s == "lz4")
    return COMP_ALG_LZ4;
#endif
  if (s == "" || s == "none")
    return COMP_ALG_NONE;

  return boost::optional<CompressionAlgorithm>();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • multisite代码中大量使用了Boost的协程,在了解协程的使用方法后,整体代码结构还是比较清晰的。 协程实现...
    宋新颖阅读 5,223评论 0 9
  • 说明:多数据中心(multisite)功能oNest v6.1新增的功能,旨在实现异地双活,提供了备份容灾的能力。...
    ScottJing阅读 6,590评论 4 11
  • 在我搭建基于Spring Cloud的微服务体系应用的时候所需要或者是常用的属性配置文件,还有这些属性的用途,此配...
    StrongManAlone阅读 3,957评论 0 18
  • 看见这个想笑的 晚上妈妈给我打电话了,听得出来她好多了,我好开心啊 下午去厕所还把自己锁在里面了,让大家等了我好一会儿
    Lucky黑girl阅读 137评论 0 0