bucket对象回收lifecycle机制

bucket对象回收lifecycle机制

2019-07-07

针对功能,对照代码,理解radosgw的lc机制

在radosgw中,可以针对bucket设置其对象的生命周期,当对象存活时间超出这个时间后,rgw会对其进行删除回收,这里设置的粒度目前是在bucket,也就是不能针对object设置其失效时间

bucket的lc机制弊端是很明显的,在lc打开的情况下,进行lc的做法是遍历整个bucket的对象,找出超过存活时间的对象进行回收,这就意味着,如果单个bucket对象数量十分庞大,这个遍历就会极其消耗性能

首先,与lc相关的参数有

"rgw_enable_lc_threads": "true",
"rgw_lc_debug_interval": "-1",
"rgw_lc_lock_max_time": "60",
"rgw_lc_max_objs": "32",
"rgw_lc_max_rules": "1000"
"rgw_lifecycle_work_time": "00:00-06:00"

默认情况下,bucket的属性标识中对象是永不过期的,对于某个bucket,手动设置其过期时间的接口,使用boto3的做法是

#pip install boto3 -i https://pypi.tuna.tsinghua.edu.cn/simple boto
from boto3.session import Session
import boto3
import uuid

access_key = 'xxx'
secret_key = 'xxxxx'
host = 'http://ip:7480'

session = Session(access_key, secret_key)
buckets = ['bucket1']
s3_client = session.client('s3', endpoint_url=host)
for x in buckets:
    s3_client.put_bucket_lifecycle(
           Bucket=x,
           LifecycleConfiguration={
               'Rules': [
                   {
                       'Expiration':
                           {
                               'Days': 1
                           },
                       'ID': str(uuid.uuid1()),
                       'Status': 'Enabled',
                       'Prefix': ''
                   }
               ],
           })
    print s3_client.get_bucket_lifecycle(Bucket=x)

设置完成后,可以通过命令radosgw-admin lc list查看到当前lc的队列

[store@server227 build]$ ./bin/radosgw-admin -k keyring -c ./ceph.conf  lc list
[
    {
        "bucket": ":cpp-bucket1:eace8f62-b901-47ed-b18f-fe2441f33830.4155.1",
        "status": "COMPLETE"
    }
]
代码跟读

lc处理流程的入口是rgw/rgw_lc.cc*RGWLC::LCWorker::entry函数

void *RGWLC::LCWorker::entry() {
  do {
    utime_t start = ceph_clock_now();
    //rgw_lc_debug_interval > 0时或者达到设定的回收时间则返回true
    if (should_work(start)) { 
      ldout(cct, 2) << "life cycle: start" << dendl;
      int r = lc->process();
      if (r < 0) {
        ldout(cct, 0) << "ERROR: do life cycle process() returned error r=" << r << dendl;
      }
      ldout(cct, 2) << "life cycle: stop" << dendl;
    }
    if (lc->going_down())
      break;

    utime_t end = ceph_clock_now();
    int secs = schedule_next_start_time(start, end);
    utime_t next;
    next.set_from_double(end + secs);

    ldout(cct, 5) << "schedule life cycle next start time: " << rgw_to_asctime(next) << dendl;

    lock.Lock();
    cond.WaitInterval(lock, utime_t(secs, 0));
    lock.Unlock();
  } while (!lc->going_down());

  return NULL;
}

如果使能了lc的功能,lc的线程会在进行一次lc作业后,等待一段时间,再唤醒起来进行下一次的作业,这里函数的第三行,使用should_work函数判断本次的lc是否进行,默认rgw_lc_debug_interval为-1,表示不进行lc的作业,此时会进一步判断是否在设定的回收时间内,如果不在,也不进行lc作业

if (cct->_conf->rgw_lc_debug_interval > 0) {
    /* We're debugging, so say we can run */
    return true;
} else if ((bdt.tm_hour*60 + bdt.tm_min >= start_hour*60 + start_minute) &&
           (bdt.tm_hour*60 + bdt.tm_min <= end_hour*60 + end_minute)) {
    return true;
} else {
    return false;
}

跟着lc->process()看下

int RGWLC::process()
{
  int max_secs = cct->_conf->rgw_lc_lock_max_time;

  const int start = ceph::util::generate_random_number(0, max_objs - 1);

  for (int i = 0; i < max_objs; i++) {
    int index = (i + start) % max_objs;
    int ret = process(index, max_secs);
    if (ret < 0)
      return ret;
  }

  return 0;
}

函数比较简短,限制每次作业的时间为rgw_lc_lock_max_time,默认是60s,结合实际测试看,这个时间并不准确,继续看

int RGWLC::process(int index, int max_lock_secs)
{
  //根据max_lock_secs来判断一个执行周期的时间长度
  rados::cls::lock::Lock l(lc_index_lock_name);
  do {
    utime_t now = ceph_clock_now();
    ......
    //从这里开始,正式开始进行对象的lc操作,注意到这是在一个死循环里
    ret = bucket_lc_process(entry.first);
    bucket_lc_post(index, max_lock_secs, entry, ret);
  }while(1);

lock的对象是lc_process,这个函数中会多次请求osd进行lc的前期操作,包括查询head、set一些配置等,最重要的是检查bucket是否配置了lc,开始lifecycle到调用bucket_lc_process前,花费时间为68ms,接下来

int RGWLC::bucket_lc_process(string& shard_id)
{
    //对bucket的对象进行回收的操作主要在这个函数中进行
    ....
    //设定运行周期
    l.set_duration(time);
    ....
    do {
        objs.clear();
        list_op.params.marker = list_op.get_next_marker();

        //从bucket中选择1000个对象,使用的是bucket list方法
        ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);

        if (ret < 0) {
          if (ret == (-ENOENT))
            return 0;
          ldout(cct, 0) << "ERROR: store->list_objects():" <<dendl;
          return ret;
        }
        
        bool is_expired;
        //遍历这1000个对象,检查它们是否达到了过期时间
        for (auto obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {
          rgw_obj_key key(obj_iter->key);
          ....
          if (prefix_iter->second.expiration_date != boost::none) {
            //we have checked it before
            is_expired = true;
          } else {
            //检查对象是否达到过期时间
            is_expired = obj_has_expired(obj_iter->meta.mtime, prefix_iter->second.expiration);
          }
          if (is_expired) {
            int ret = store->get_obj_state(&rctx, bucket_info, obj, &state, false);
            if (ret < 0) {
              return ret;
            }
            if (state->mtime != obj_iter->meta.mtime) {
              //Check mtime again to avoid delete a recently update object as much as possible
              ldout(cct, 20) << __func__ << "() skipping removal: state->mtime " << state->mtime << " obj->mtime " << obj_iter->meta.mtime << dendl;
              continue;
            }
            ret = remove_expired_obj(bucket_info, obj_iter->key, obj_iter->meta.owner, obj_iter->meta.owner_display_name, true);
            if (ret < 0) {
              ldout(cct, 0) << "ERROR: remove_expired_obj " << dendl;
            } else {
              ldout(cct, 2) << "DELETED:" << bucket_name << ":" << key << dendl;
            }

            if (going_down())
              return 0;
          }
        }
      } while (is_truncated);
}

在这个函数中,首先设定运行时间,线程通过bucket list的方式拿到1000个obj,获取使用按顺序的方式,遍历这1000个对象,如果该对象超过了过期时间,则删掉,关于循环的停止条件,is_truncated: if number of objects in the bucket is bigger than max, then truncated,即要求遍历完整个bucket

这部分耗时主要依赖于bucket内对象的数量,对象越多,则耗时肯定会越多

最后,针对对象中分片情况,进行了处理,在RGWLC::bucket_lc_process的最后

ret = handle_multipart_expiration(&target, prefix_map);

return ret;

其中prefix_map表明了对象分片的相关信息

看下对于分片的处理逻辑

for (auto prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
      ......
    do {
      objs.clear();
      list_op.params.marker = list_op.get_next_marker();
      ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
      ......
    } while(is_truncated);
}

可以看到,与对象删除一样,分片删除也是一次性取出1000个分片对象进行删除

逻辑总结

首先,在默认情况下,lc线程启动后,根据rgw_lc_debug_interval及设定的rgw_lifecycle_work_time来判断要不要做本次的process,默认rgw_lc_debug_interval为-1,表示不做本次lc,另外,在rgw_lc_debug_interval <=0时,这个时间间隔单位将放大为天,原注释说的是We're in debug mode; Treat each rgw_lc_debug_interval seconds as a day

在决定要做本次lc后,根据rgw_lc_lock_max_time来判断每次lc的周期时间长度,然后每次从rgw_lc_max_objs中选取一个进行运行,增大这个值不会加快回收速度

关于rgw_lc_max_objs,它表示的是lc的处理线程的最大数量,它并不是并发的线程,因而增大这个值并不能加快回收速度,它类似于一个分组,bucket被设置lc规则后将被分配到某个lc中,例如,这个值设置为3,它启动回收的时候会随机启动lc.0、lc.1、lc.2中的其中一个,测试发现,多个bucket都设置过期的情况下,单个rgw同一时间仅运行一个lc线程,因而设置自动回收速度其实是很慢的

接下来,请求并设定一些基本的元数据信息,最重要的是判断bucket是否需要做lc,它检测bucket是否进行了lc的设置,如果设置才做,没有设置,默认是不做

然后,从指定的bucket中每次取出最多1000个有序对象,遍历这些对象并检查他们是否到达过期时间,过期则删除,直到bucket中所有的对象都遍历完成.

针对分片做了特殊处理,删除掉第一个片后,其余分片有专门的逻辑进行进一步的删除

环境中起2个rgw进行测试,发现2个rgw可以同时进行不同bucket的回收,因而,要加快回收速度,只能通过增加rgw的数量实现,单个rgw明显只能串行单线程进行单bucket的回收,不过,因为回收bucket涉及到bucket list,过快的回收速度也可能引发性能问题,需要权衡

  • 本文作者:奋斗的松鼠 - tanweijie_2012@163.com
  • 本文链接: http://www.strugglesquirrel.com/2019/07/07/bucket对象回收lifecycle机制/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!
  • 特别说明:
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值