|
|
1 `2 M6 x6 Y/ r/ w0 }在radosgw中,可以针对bucket设置其对象的生命周期,当对象存活时间超出这个时间后,rgw会对其进行删除回收,这里设置的粒度目前是在bucket,也就是不能针对object设置其失效时间5 ^5 [$ z* O+ M2 y; F7 ]( f D* G& p
bucket的lc机制弊端是很明显的,在lc打开的情况下,进行lc的做法是遍历整个bucket的对象,找出超过存活时间的对象进行回收,这就意味着,如果单个bucket对象数量十分庞大,这个遍历就会极其消耗性能0 b. a: b- G1 v5 f e
开始
- p' B- h4 E- `* r! p首先,与lc相关的参数有! w" V, f, k9 {1 G! m
"rgw_enable_lc_threads": "true",$ Z% P- U& @ s- B7 r% e
"rgw_lc_debug_interval": "-1",
' p6 q4 z* }# V! w# p "rgw_lc_lock_max_time": "60",
3 V+ B: }! L. ~" w "rgw_lc_max_objs": "32",
% t, D4 Y3 ~% g/ l% Q1 n5 r "rgw_lc_max_rules": "1000"
! y4 n0 S( P, l0 G$ M "rgw_lifecycle_work_time": "00:00-06:00"
0 L- W( H0 b! K) [5 M c默认情况下,bucket的属性标识中对象是永不过期的,对于某个bucket,手动设置其过期时间的接口,使用boto3的做法是% `# ^% N/ C- ^& y& w. H
#pip install boto3 -i https://pypi.tuna.tsinghua.edu.cn/simple boto
3 t+ P0 A/ @9 ?) }2 efrom boto3.session import Session
4 A. c" R% S$ wimport boto3
! m2 i( s0 H Uimport uuid
1 p( |, v! g' eaccess_key = 'xxx'
' o1 |5 E7 b6 M1 k b0 i. Y1 lsecret_key = 'xxxxx'
' F. g7 }& l% ]- `5 W3 {host = 'http://ip:7480'
" V- g& f8 A$ R$ P5 @session = Session(access_key, secret_key)
. [( ~9 C1 ~% M b* N( c! tbuckets = ['bucket1']
% n6 n& R% M+ y# K2 f% N1 ds3_client = session.client('s3', endpoint_url=host)5 V) J9 W! j$ p5 X4 ^% d, I
for x in buckets:+ O" u3 v3 U/ H2 U2 ^, \( X/ Y' }
s3_client.put_bucket_lifecycle(9 D/ `4 a4 R. x) O
Bucket=x,
& b3 I6 w3 P0 T, B0 q# { LifecycleConfiguration={
% C1 I+ S3 F0 K+ A9 g 'Rules': [! Z" H- F9 J4 f8 o; e5 q
{2 {: C/ R3 D: V
'Expiration':
; Z) ?& k* `4 x3 l0 e {
7 T4 E3 [, K0 ] K6 y" r- r 'Days': 12 q1 b3 H3 p3 C9 ?) P
},
: r* d6 s& q, U' g. h0 X 'ID': str(uuid.uuid1()),
2 c; |: F7 s5 `0 i; @/ o5 G 'Status': 'Enabled',
& b7 l0 w3 e+ S" |5 ^ 'Prefix': ''
) g. s6 i; g/ w7 W' q }
, G; X% s0 _* P ],
# R3 j0 I Y$ j5 w2 j, ` })
! n- l% s3 o$ ?) r: { print s3_client.get_bucket_lifecycle(Bucket=x)
" m1 s6 F6 x/ _) N2 z* y设置完成后,可以通过命令radosgw-admin lc list
. O# I- |8 S5 q9 E7 \查看到当前lc的队列% ^- V# j, W$ U% k( E1 o
[store@server227 build]$ ./bin/radosgw-admin -k keyring -c ./ceph.conf lc list
; z0 Z) i7 N9 }2 d7 H[
8 [1 k. P5 _/ D( S" y {
: e2 T; D$ H; e. s: _2 U" ]2 \* J3 K "bucket": ":cpp-bucket1:eace8f62-b901-47ed-b18f-fe2441f33830.4155.1",1 W/ A7 u; X5 T; d
"status": "COMPLETE"
3 Z" W9 j4 k1 V' k( B }5 d, O# c! f/ \& p% r0 k
]1 m: O E. x9 E# J1 H! X% ?3 d
代码跟读
) I. ^3 ]6 `+ @, F. glc处理流程的入口是rgw/rgw_lc.cc
3 a5 ]8 Z# g# S. Z1 O( b4 \的*RGWLC::LCWorker::entry
: Q% o5 v# U* |8 L函数# T5 B* P$ ~" E' c; w2 W3 y0 P
void *RGWLC::LCWorker::entry() { r6 A& @0 ]) @. M1 Z( w' E
do {4 e& _' g1 T% s+ `
utime_t start = ceph_clock_now();
+ a2 d. u! X2 v9 h @( k rgw_lc_debug_interval > 0时或者达到设定的回收时间则返回true6 Z- f; `/ j, k9 \1 g
if (should_work(start)) {9 u8 O |6 Z& |' ^. ^
ldout(cct, 2) << "life cycle: start" << dendl;
3 Z8 K* q9 m0 B6 V8 ]8 C int r = lc->process();
' Y* M2 Z( w2 c if (r < 0) {1 t& n6 p- q- E
ldout(cct, 0) << "ERROR: do life cycle process() returned error r=" << r << dendl;# t4 S! ]$ F! @9 V9 p* r9 M
}
/ i7 B. f, C c" W$ Y2 ~7 L ldout(cct, 2) << "life cycle: stop" << dendl;# v2 o8 E% n9 E" Q5 A0 a, U, y/ i$ O
}
. d0 z/ s6 ]% G& O" d if (lc->going_down())8 [) x. r# x; f( J+ c. V1 v
break;
* g6 @) y8 K- Q: W/ K utime_t end = ceph_clock_now();& b+ `3 E2 P0 | |9 d
int secs = schedule_next_start_time(start, end);
# N1 f9 A0 E5 ~8 H$ r( } utime_t next;
# Q* @% d4 Z5 v, N7 F, l next.set_from_double(end + secs);+ @# U. f+ C5 X. k7 c9 V
ldout(cct, 5) << "schedule life cycle next start time: " << rgw_to_asctime(next) << dendl;+ t5 w- {9 E. P0 ?3 `0 B) ]# g$ k' J$ O
lock.Lock();
, V I1 E6 Z9 \6 u" i; S" { cond.WaitInterval(lock, utime_t(secs, 0));
6 G! k1 ^/ z# x7 r' }$ K lock.Unlock();! I* ^- ]. T3 `* S/ ^
} while (!lc->going_down());& Y1 b. R: L& @9 ~
return NULL;9 o( p. t& C2 l& r1 \5 t& Q, h6 G6 p
}
0 B5 V1 V+ z6 h* {0 m! a; I如果使能了lc的功能,lc的线程会在进行一次lc作业后,等待一段时间,再唤醒起来进行下一次的作业,这里函数的第三行,使用should_work
! s# T- G7 O" c: M6 J( }函数判断本次的lc是否进行,默认rgw_lc_debug_interval% Y! N* H& r& |8 N4 v/ A; T
为-1,表示不进行lc的作业,此时会进一步判断是否在设定的回收时间内,如果不在,也不进行lc作业
! Z, i# b9 @0 p; _1 T, n if (cct->_conf->rgw_lc_debug_interval > 0) {' @' N) m6 L r: o7 k$ a
* We're debugging, so say we can run */
9 K( i9 d& N' L# {) Z9 T: F return true;
. a4 T7 G K$ P, t; T } else if ((bdt.tm_hour*60 + bdt.tm_min >= start_hour*60 + start_minute) &&
0 t: A; {, X$ {5 d, p (bdt.tm_hour*60 + bdt.tm_min <= end_hour*60 + end_minute)) {
. H" H/ x X, E* f# A return true;( T% d: U9 N4 ` i$ o
} else {+ K _' T' v& a* n8 ^( M
return false;
) H5 `) I0 ?* c w }
- Q+ T# r9 j l$ f& s* q8 |' T: M: Y跟着lc->process()
1 R: i/ h6 F# k; G5 ]看下: l' y+ Z R: H! A% o
int RGWLC::process()7 @5 F# B! L* F4 s, J' a5 ~
{; a- y( n8 D6 B4 u
int max_secs = cct->_conf->rgw_lc_lock_max_time;
7 G# F1 P" }+ C9 f6 t0 X" @ const int start = ceph::util::generate_random_number(0, max_objs - 1);# _" D7 Q4 l& }5 u. b% w) I
for (int i = 0; i < max_objs; i++) {2 X- k' R/ R2 S$ ]8 C
int index = (i + start) % max_objs;
* R) z- l( z. f int ret = process(index, max_secs);
8 O. f, X* w7 A7 g0 b if (ret < 0)$ u7 {/ Z, R2 c. C+ z
return ret;; U5 D8 X+ s9 M+ r
} K) S( t% r2 e! ^; @3 N- a
return 0;4 v. {0 c+ q% ?& ` z* z, p; R7 P
}: ?( @# r( `3 o8 I1 D& I; a. T+ s
函数比较简短,限制每次作业的时间为rgw_lc_lock_max_time
& z1 W) e; ?" u. O: s,默认是60s,结合实际测试看,这个时间并不准确,继续看" Q. `# v- q' V, o8 \, Y7 \8 u
int RGWLC::process(int index, int max_lock_secs)
" J" m; x0 u5 N' G{
/ l; R- f. U% {3 k8 X3 Z 根据max_lock_secs来判断一个执行周期的时间长度
! K. i: g% l, x/ o: y; H/ A rados::cls::lock::Lock l(lc_index_lock_name);0 y- e# I3 w4 H' V/ U* y, P" a
do {$ K7 g5 T2 [7 Z- R1 @
utime_t now = ceph_clock_now();5 L% G- G9 c4 J4 }9 ?7 R* S
......
) V% O- U" M- ]$ P 从这里开始,正式开始进行对象的lc操作,注意到这是在一个死循环里
+ ]! t. ?" j: C* W: i- e) k ret = bucket_lc_process(entry.first);
0 A1 X/ }' x3 U" J8 Z# C+ Y bucket_lc_post(index, max_lock_secs, entry, ret);
. V6 e! E4 ~, n* [$ E }while(1);
1 z; e- _* B+ l( {lock的对象是lc_process4 y _" C: v$ _- ]' e2 F3 c
,这个函数中会多次请求osd进行lc的前期操作,包括查询head、set一些配置等,最重要的是检查bucket是否配置了lc,开始lifecycle到调用bucket_lc_process( ^# i/ W, q) V) y4 ~$ t
前,花费时间为68ms
3 @! ]3 r6 y/ w9 @,接下来( Y* n8 {7 ?5 ~ r& E
int RGWLC::bucket_lc_process(string& shard_id)
$ R- k8 x7 u! z: L2 [{% I8 K$ P; S3 ?# k9 p
对bucket的对象进行回收的操作主要在这个函数中进行
: X2 Q' u) X5 T, S ....) D$ Z1 B' X f- O; @7 ^: {. j
设定运行周期
4 V, u# ^9 _$ G; ?. z& S. `+ ~: z l.set_duration(time);
/ _; p( g, i9 I+ i ....
/ w2 T3 m2 ^1 ~7 B2 { do {
: M% d' W. U. M% b objs.clear();# T- t3 Y) @$ i2 G+ k' }4 Y+ Y
list_op.params.marker = list_op.get_next_marker();
S9 J: a% a/ d6 C! W 从bucket中选择1000个对象,使用的是bucket list方法
5 h/ Q( N# c! r {- }0 E ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
) n# j( ~) l7 [ if (ret < 0) {
. Q$ q; u+ F7 [% } if (ret == (-ENOENT)). H2 S. o( v* v' e z* P) K
return 0;* f/ o ~. u( e! ~: e, Y+ }0 Y6 |) ^7 U
ldout(cct, 0) << "ERROR: store->list_objects():" <<dendl;
, r5 B( r% {# Y# u* w return ret;& {. ~5 I8 o" @7 [) M& {6 p+ I
}
5 U7 a L+ g8 m' U
( S9 Z9 {' u9 j8 t) e" _ bool is_expired;2 O! [8 }+ s0 q% H5 l0 c
遍历这1000个对象,检查它们是否达到了过期时间
# E% O6 k, o+ H; L; e( A- _ for (auto obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {0 D: ]4 p X- n+ M: b
rgw_obj_key key(obj_iter->key);7 N3 @2 d) d" Z) k" X: o
....
; Q/ v4 K0 ^1 t if (prefix_iter->second.expiration_date != boost::none) {
, e0 s/ s3 ?* O6 _3 {4 d1 }8 ^ we have checked it before
' e1 M( _' k( k) P3 N is_expired = true;
) e3 R2 T6 e/ v } else {
3 E4 m6 ~ g3 @& [: ` 检查对象是否达到过期时间
1 W$ ]7 e( Q; Q( j0 A* |! ] is_expired = obj_has_expired(obj_iter->meta.mtime, prefix_iter->second.expiration);
" W: P9 P7 T; O- {% ?5 S: ~( h }
& ^, d) M* N) T: O7 L- D if (is_expired) { x$ U9 K' l) }2 p0 _" [
int ret = store->get_obj_state(&rctx, bucket_info, obj, &state, false);
* B$ Z6 [ U% a8 c1 m1 ~" F if (ret < 0) {1 y8 c0 y9 S% _2 y+ g* o
return ret;
* ]5 G. Z& J' ? }
; X9 r* M2 u% o if (state->mtime != obj_iter->meta.mtime) {
; Y* K( T, N8 G2 Q4 \, b$ A% f% ^ Check mtime again to avoid delete a recently update object as much as possible# k, v8 r6 r, e4 V( j
ldout(cct, 20) << __func__ << "() skipping removal: state->mtime " << state->mtime << " obj->mtime " << obj_iter->meta.mtime << dendl;
: o; j! ?: h! ~1 o4 Q+ L continue;( p9 b& }) ^( U$ q6 E
}
' }8 d- A& E; q9 m: B ret = remove_expired_obj(bucket_info, obj_iter->key, obj_iter->meta.owner, obj_iter->meta.owner_display_name, true);
; z* @' O) T1 f; @" `+ \ if (ret < 0) {
+ F3 s8 D2 ?1 c# \( A ldout(cct, 0) << "ERROR: remove_expired_obj " << dendl; V0 H5 ?. }: b: J
} else {
- c$ W4 {# ]6 }" Q# ?# S ldout(cct, 2) << "DELETED:" << bucket_name << ":" << key << dendl;
) J a# t/ J( g8 O2 e9 r0 n* ? }
/ j/ K0 h0 i& ]) o0 E if (going_down())
% @) v- [, Q( l return 0; j$ f3 X6 F) y- r) i9 ]( M
}- z$ N( V" d% C( i2 q+ k4 D m1 f
}
# d% x4 A3 Y! g: K: k# b } while (is_truncated);
8 I# k% V; b1 {# \; `4 C}
/ Q0 P. d6 a: E3 i& t在这个函数中,首先设定运行时间,线程通过bucket list的方式拿到1000个obj,获取使用按顺序的方式,遍历这1000个对象,如果该对象超过了过期时间,则删掉,关于循环的停止条件,is_truncated: if number of objects in the bucket is bigger than max, then truncated
. {7 G3 ~ ]+ c: M+ j+ x L,即要求遍历完整个bucket2 Y& _6 ^& c. u- f! K
这部分耗时主要依赖于bucket内对象的数量,对象越多,则耗时肯定会越多- K7 W8 R& J$ s
最后,针对对象中分片情况,进行了处理,在RGWLC::bucket_lc_process8 I- K- K6 p! a( F' a7 k7 A+ w
的最后5 c! k5 U% ^' b$ ?/ h
ret = handle_multipart_expiration(&target, prefix_map);
9 X( Q4 o: n9 L8 ^3 T return ret;
# v* ?3 E+ w+ }1 q3 u1 T! d其中prefix_map
+ h* ?, j& K7 t$ p& w表明了对象分片的相关信息* r2 P3 i$ f: m& b
看下对于分片的处理逻辑
- i6 E% e/ I" L" [6 Yfor (auto prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {
- `) [( K2 B5 G8 [) i- G8 D V ......
* r, `4 \0 t- G6 y do {
! D. Q3 `! |+ _% ]/ P$ j objs.clear();
2 \& y K& q7 Q! v' Z4 j1 m( T6 `) E( J list_op.params.marker = list_op.get_next_marker();+ \1 o8 B0 I/ ]0 G6 O3 [, O4 Q
ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
) E: v- T* w( O z: P; u ......1 W& I% E! ^6 b
} while(is_truncated);9 w: b, r) ^' a5 }
}6 v+ i) T9 G7 o( ?. c- m
可以看到,与对象删除一样,分片删除也是一次性取出1000个分片对象进行删除, {& A1 s# W3 e6 V* W
逻辑总结# c* \9 z& Y- \) M; y8 K0 j7 z
首先,在默认情况下,lc线程启动后,根据rgw_lc_debug_interval' y3 U& O$ Y! U. q R
及设定的rgw_lifecycle_work_time5 A( @. B" g0 |8 f f0 C
来判断要不要做本次的process,默认rgw_lc_debug_interval
& I7 q" r6 g+ d* V* y: l# r& Q为-1,表示不做本次lc,另外,在rgw_lc_debug_interval <=0
# r- p, D: f& v) s W8 c时,这个时间间隔单位将放大为天,原注释说的是We're in debug mode; Treat each rgw_lc_debug_interval seconds as a day$ o4 h% b( p0 G L7 ~
在决定要做本次lc后,根据rgw_lc_lock_max_time8 U. p* C% A0 v* a5 X
来判断每次lc的周期时间长度,然后每次从rgw_lc_max_objs& j* l: O: Y, z* p7 d$ @7 W2 X! h
中选取一个进行运行,增大这个值不会加快回收速度
' w0 [9 G: D( g9 }2 t关于rgw_lc_max_objs
" V: h6 ~2 ~7 a' f& K,它表示的是lc的处理线程的最大数量,它并不是并发的线程,因而增大这个值并不能加快回收速度,它类似于一个分组,bucket被设置lc规则后将被分配到某个lc中,例如,这个值设置为3,它启动回收的时候会随机启动lc.0、lc.1、lc.2中的其中一个,如果某个lc中有bucket则进行回收,测试发现,多个bucket都设置过期的情况下,单个rgw同一时间仅运行一个lc线程,因而设置自动回收速度其实是很慢的
2 J; N* y1 M5 O% z4 p% n接下来,请求并设定一些基本的元数据信息,最重要的是判断bucket是否需要做lc,它检测bucket是否进行了lc的设置,如果设置才做,没有设置,默认是不做: X! S! `2 W" ^, n1 C
然后,从指定的bucket中每次取出最多1000个有序对象,遍历这些对象并检查他们是否到达过期时间,过期则删除,直到bucket中所有的对象都遍历完成.* O! w; N& o) k
针对分片做了特殊处理,删除掉第一个片后,其余分片有专门的逻辑进行进一步的删除( t g7 |6 C G( h0 V8 m
环境中起2个rgw进行测试,发现2个rgw可以同时进行不同bucket的回收,因而,要加快回收速度,只能通过增加rgw的数量实现,单个rgw明显只能串行单线程进行单bucket的回收,不过,因为回收bucket涉及到bucket list,过快的回收速度也可能引发性能问题,需要权衡. R2 m! q( z. {. r
|
|