背景
compression是基于Placement Targets的漫谷,是其中的一個(gè)配置項(xiàng)煮仇。
Placement Targets
我們知道岖食,在默認(rèn)情況下醋安,bucket index刁憋、bi log是存儲(chǔ)在<zone>.rgw.buckets.index
pool中的滥嘴,object data是存儲(chǔ)在<zone>.rgw.buckets.data
pool中的,這其實(shí)是由配置在zone上的default-placement決定的至耻。
Placement Targets可以讓radosgw同時(shí)擁有多組不同的數(shù)據(jù)pool若皱。用戶上傳數(shù)據(jù)時(shí),可以選擇不同Placement Targets尘颓,將數(shù)據(jù)存儲(chǔ)到不同的pool中走触。不僅如此,還可以在Placement Targets上配置壓縮算法等屬性疤苹。使使用該規(guī)則上傳的數(shù)據(jù)被壓縮存儲(chǔ)互广。
與Placement Targets相關(guān)的配置項(xiàng)主要有以下幾個(gè):
$ ./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": "" //采用的數(shù)據(jù)壓縮算法
}
}
],
......
}
$ ./bin/radosgw-admin zonegroup get --rgw-zonegroup=default
{
......
"placement_targets": [
{
"name": "default-placement", //需要是zone中已存在的key,否則無效
"tags": [] //該placement pools對(duì)應(yīng)的tags卧土,用于給用戶授權(quán)
}
],
"default_placement": "default-placement",
......
}
zonegroup和user的配置項(xiàng)中都有一項(xiàng)default_pacement
配置惫皱,如果user的配置為“”,那么采用zonegroup的配置尤莺,不為空則以u(píng)ser中的配置為準(zhǔn)旅敷。
$ ./bin/radosgw-admin user info --uid=testid
{
......
"default_placement": "", //該用戶操作時(shí)的默認(rèn)placement pools
"placement_tags": [], //該用戶有權(quán)使用的placement pools
......
}
使用時(shí),我們需要先創(chuàng)建一組pool颤霎,然后在zone的配置中增加placement_pools
配置媳谁,然后在zonegroup中用placement_targets
項(xiàng),為zone中的placement_pools
指定tags友酱。最后在用戶的placement_tags
中加入對(duì)應(yīng)的tags為用戶授權(quán)晴音。
Compression
介紹
文檔:http://docs.ceph.com/docs/luminous/radosgw/compression/
在前面的placement pools的配置中,有這樣一項(xiàng)配置:
"compression": "" //采用的數(shù)據(jù)壓縮算法
缔杉,這就是我們的壓縮功能的唯一配置點(diǎn)锤躁,對(duì)于每條placement_pools
,我們可以為其配置數(shù)據(jù)壓縮算法或详。
要點(diǎn)
配置壓縮算法之后进苍,用戶上傳的每個(gè)對(duì)象都會(huì)以該算法進(jìn)行壓縮,然后存入rados中鸭叙,要注意的是觉啊,某rgw對(duì)象使用的壓縮算法會(huì)一并保存在rados中。所以在用戶更改壓縮算法之前上傳的對(duì)象沈贝,不會(huì)被新的壓縮算法所影響杠人,其仍然保持其原有的壓縮方式,并會(huì)被正確解壓。
當(dāng)用戶上傳對(duì)象時(shí)使用了sse(服務(wù)端加密)嗡善,該對(duì)象不會(huì)被compressed辑莫。GetObj代碼邏輯似乎可以同時(shí)支持sse和compression,但PutObj的邏輯不支持罩引。這里mark下各吨,之后持續(xù)關(guān)注下。
目前支持4種壓縮算法袁铐,分別是 snappy揭蜒、 zlib、 zstd剔桨、 以及l(fā)z4屉更;但lz4算法需要在編譯前定義HAVE_LZ4宏才會(huì)被支持。ceph默認(rèn)是不支持的洒缀。另外瑰谜,compression還可以設(shè)置成random,這時(shí)會(huì)隨機(jī)選擇一個(gè)算法树绩。根據(jù)注釋描述萨脑,random是用于測(cè)試的,不建議使用饺饭。
當(dāng)使用Multipart Upload時(shí)渤早,每一個(gè)part都會(huì)以上傳時(shí)的compression算法進(jìn)行壓縮。所以砰奕,如果在Multipart Upload期間蛛芥,修改了compression算法提鸟,會(huì)導(dǎo)致多個(gè)parts之間壓縮算法不一致军援,最終在用戶發(fā)送完成Multipart請(qǐng)求時(shí),會(huì)被檢查出來称勋,并報(bào)錯(cuò)返回胸哥。
當(dāng)進(jìn)行range讀時(shí),用戶傳入的偏移量是壓縮前的偏移量赡鲜,所以需要通過對(duì)象上傳時(shí)在對(duì)象xattr存入的blocks列表做轉(zhuǎn)換空厌,blocks列表中存儲(chǔ)了每個(gè)塊的原始尺寸和偏移以及壓縮后的尺寸和偏移。range讀取時(shí)银酬,用戶傳入對(duì)象的起始偏移ofs和結(jié)束偏移end嘲更,先根據(jù)blocks的old_ofs對(duì)ofs和end分別做一次二分查找,找到ofs所在的塊和end所在的塊揩瞪,然后將范圍內(nèi)的數(shù)據(jù)塊讀取并解壓后赋朦,再根據(jù)原始偏移取出需要的部分。
各壓縮算法壓縮比率、資源消耗
三種算法的壓縮比例宠哄,計(jì)算方法:壓縮后size/壓縮前size
總結(jié):三種算法對(duì)文本壓縮效果顯著壹将,對(duì)視頻和圖片收效甚微。
算法 | 文本壓縮比 | 圖片壓縮比 | 視頻壓縮比 |
---|---|---|---|
zlib | 0.0739 | 0.924 | 0.974 |
zstd | 0.0733 | 0.931 | 0.974 |
snappy | 0.151 | 0.925 | 0.988 |
在radosgw節(jié)點(diǎn)上使用s3cmd的上傳延遲毛嫉,5次平均
算法 | 單個(gè)40MB文本文件 | 單個(gè)78MB視頻文件 |
---|---|---|
zlib | 2.0s | 7.5s |
zstd | 3.0s | 9.0s |
snappy | 2.0s | 5.0s |
不壓縮 | 3.0s | 5.0s |
總結(jié):壓縮后數(shù)據(jù)體積更小可節(jié)省一定的網(wǎng)絡(luò)傳輸時(shí)間诽俯,但會(huì)消耗額外的壓縮時(shí)間〕性粒總上傳延遲取決于兩者的差值暴区。
算法 | 單個(gè)40MB文本文件 | 單個(gè)78MB視頻文件 |
---|---|---|
zlib | 2.0s | 7.5s |
zstd | 3.0s | 9.0s |
snappy | 2.0s | 5.0s |
不壓縮 | 3.0s | 5.0s |
三種算法運(yùn)行時(shí)的cpu占用
總結(jié):壓縮算法會(huì)占用大量cpu資源,內(nèi)存變化不大密任。
算法 | cpu峰值 | 內(nèi)存占用 |
---|---|---|
snappy | 35% | 變化不大 |
zlib | 80% | - |
zstd | 80% | - |
不壓縮 | 20% | - |
使用
如果只是想在已有的數(shù)據(jù)pool上打開compression颜启,只需要修改對(duì)應(yīng)的zone的配置:
$ ./bin/radosgw-admin zone placement modify --rgw-zone=default --placement-id=default-placement --compression=zlib
但如果要對(duì)新創(chuàng)建的pool設(shè)置壓縮選項(xiàng),最好是創(chuàng)建新的placement pools配置項(xiàng)浪讳,如果選擇替換原有placement pools配置中的pool缰盏,會(huì)使被替換的pool游離在rgw之外,無法通過rgw讀取和寫入它的數(shù)據(jù)淹遵。
假設(shè)現(xiàn)在用戶想要?jiǎng)?chuàng)建創(chuàng)建一組額外的存儲(chǔ)pool口猜,并需要在pool上開啟compression功能。相關(guān)的步驟如下:
--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.創(chuàng)建對(duì)應(yīng)的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
命令济炎,配合相關(guān)的options進(jìn)行配置。另一種方式是使用radosgw-admin zone get/set
通過編輯json文件來設(shè)置辐真,這里選擇前者须尚。
$ 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
結(jié)果如下:
{
......
"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的配置中設(shè)置tags
$ radosgw-admin zonegroup placement add --rgw-zonegroup=default --placement-id=compression --tags="compress"
結(jié)果如下:
[
{
"key": "compression",
"val": {
"name": "compression",
"tags": [
"compress"
]
}
},
{
"key": "default-placement",
"val": {
"name": "default-placement",
"tags": []
}
}
]
4.通過配置placement tags為user授權(quán)
在授權(quán)之前,我們?cè)囋嚳茨芊裨趕sd-placement對(duì)應(yīng)的pool中進(jìn)行操作侍咱。
$ 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授權(quán)并測(cè)試權(quán)限耐床。成功。
$ 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
測(cè)試上傳數(shù)據(jù)是否被壓縮
$ 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_utilized
和size_kb_utilized
為實(shí)際占用的磁盤空間楔脯,可以看出撩轰,壓縮生效了。
$ ./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
}
},
......
}
實(shí)現(xiàn)
存儲(chǔ)位置
我們?cè)趜one上設(shè)置的placement targets(包含compression屬性)被存儲(chǔ)在.rgw.root
pool中的對(duì)象中昧廷。
$ ./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
作用機(jī)制
對(duì)象上傳/讀取
上傳對(duì)象并壓縮的相關(guān)代碼在RGWPutObj::execute()函數(shù)中堪嫂,要點(diǎn)代碼如下:
// filter用于對(duì)數(shù)據(jù)進(jìn)行處理,比如加密和壓縮
RGWPutObjDataProcessor *filter = nullptr;
......
// 根據(jù)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;
......
// 需要加密時(shí)皆串,filter用于加密數(shù)據(jù)
if (encrypt != nullptr) {
filter = encrypt.get();
} else {
//no encryption, we can try compression
if (compression_type != "none") {
// 不需要加密時(shí),并且compression_type被設(shè)置了眉枕,filter被用于壓縮數(shù)據(jù)
plugin = get_compressor_plugin(s, compression_type);
if (!plugin) {
ldout(s->cct, 1) << "Cannot load plugin for compression type "
<< compression_type << dendl;
} else {
// 如果一切都沒問題恶复,構(gòu)造compressor
compressor.emplace(s->cct, plugin, filter);
filter = &*compressor;
}
}
}
......
do{
......
// 在put_data_and_throttle函數(shù)中娇唯,會(huì)調(diào)用filter->handle_data,對(duì)數(shù)據(jù)進(jìn)行壓縮或加密
// 然后通過next調(diào)用`RGWPutObjProcessor_Atomic`的`handle_data`
// 將處理后的數(shù)據(jù)切分成一個(gè)head和多個(gè)tail對(duì)象
// handle_data最終調(diào)用`store->aio_put_obj_data`函數(shù)寂玲,將對(duì)象寫入rados
op_ret = put_data_and_throttle(filter, data, ofs, need_to_wait);
......
} while (len > 0);
......
// 如果進(jìn)行了壓縮塔插,將壓縮信息加入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設(shè)置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負(fù)責(zé)進(jìn)行數(shù)據(jù)的壓縮拓哟,在rgw_compression.h/cc
中想许,是其壓縮和解壓的函數(shù)實(shí)現(xiàn)。在壓縮函數(shù)中断序,每次傳入存有部分對(duì)象數(shù)據(jù)的bufferlist流纹,壓縮之后的內(nèi)容放入一個(gè)名為blocks的數(shù)組(并沒有另外分配空間,壓縮的數(shù)據(jù)會(huì)覆蓋原有bufferlist的數(shù)據(jù))违诗,其實(shí)這個(gè)blocks數(shù)組僅僅起到一個(gè)管理的作用漱凝,用于存儲(chǔ)壓縮數(shù)據(jù)的界限,用于之后的解壓诸迟。之后可以通過compressor->get_compression_blocks()
獲得blocks的數(shù)據(jù)茸炒,會(huì)被編碼進(jìn)對(duì)象的xattr。
當(dāng)使用Multipart Upload時(shí)阵苇,每一個(gè)part都會(huì)以上傳時(shí)的compression算法進(jìn)行壓縮壁公。所以,如果在Multipart Upload期間绅项,修改了compression算法紊册,會(huì)導(dǎo)致多個(gè)parts之間壓縮算法不一致,最終在用戶發(fā)送完成Multipart請(qǐng)求時(shí)快耿,會(huì)被檢查出來囊陡,并報(bào)錯(cuò)返回。相關(guān)代碼在RGWCompleteMultipart::execute()
函數(shù)中掀亥。
對(duì)象讀取的流程基本和上傳對(duì)應(yīng)撞反,需要注意的是,range讀取操作铺浇。用戶傳入的偏移量是壓縮前的偏移量痢畜,所以需要通過對(duì)象上傳時(shí)在對(duì)象xattr存入的blocks列表做轉(zhuǎn)換垛膝,blocks列表中存儲(chǔ)了每個(gè)塊的原始尺寸和偏移以及壓縮后的尺寸和偏移鳍侣。range讀取時(shí),用戶傳入對(duì)象的起始偏移ofs和結(jié)束偏移end吼拥,先根據(jù)blocks的old_ofs對(duì)ofs和end分別做一次二分查找倚聚,找到ofs所在的塊和end所在的塊,然后將范圍內(nèi)的數(shù)據(jù)塊讀取并解壓后凿可,再根據(jù)原始偏移取出需要的部分惑折。
壓縮/解壓實(shí)現(xiàn)
至于具體的壓縮算法則在ceph/src/compressor/中實(shí)現(xiàn)授账,目前支持4種壓縮算法,分別是snappy惨驶, zlib白热, zstd ,還有l(wèi)z4(可能不完善粗卜,需要在編譯前定義HAVE_LZ4宏才會(huì)支持)屋确。
下面的函數(shù)是用于根據(jù)compression配置獲得compression algorithm的函數(shù),我們看到lz4算法需要定義HAVE_LZ4宏才會(huì)被支持默認(rèn)是不支持的续扔。
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>();
}