|
|
7 @/ M- g0 U% g在radosgw中,可以针对bucket设置其对象的生命周期,当对象存活时间超出这个时间后,rgw会对其进行删除回收,这里设置的粒度目前是在bucket,也就是不能针对object设置其失效时间, S. I( N5 }" a. D$ [; L
bucket的lc机制弊端是很明显的,在lc打开的情况下,进行lc的做法是遍历整个bucket的对象,找出超过存活时间的对象进行回收,这就意味着,如果单个bucket对象数量十分庞大,这个遍历就会极其消耗性能
- n! o+ l( {8 b开始8 B/ d* [8 r' O0 x
首先,与lc相关的参数有
- w4 v6 J' S) Y$ X* h8 i "rgw_enable_lc_threads": "true",7 f) k" w+ [3 Q* a# h1 i% N* P2 F
"rgw_lc_debug_interval": "-1",6 k1 m7 e) {4 O8 G b) I/ [
"rgw_lc_lock_max_time": "60",* K" v5 m- L0 N1 B
"rgw_lc_max_objs": "32",
0 v5 V$ b3 L' p( S# X) a# @ "rgw_lc_max_rules": "1000"
' D, ~8 ^' w V$ n6 @6 a "rgw_lifecycle_work_time": "00:00-06:00"
$ ~7 m( L# D* Y; x) l: O默认情况下,bucket的属性标识中对象是永不过期的,对于某个bucket,手动设置其过期时间的接口,使用boto3的做法是
( Z9 }$ N2 J2 w#pip install boto3 -i https://pypi.tuna.tsinghua.edu.cn/simple boto
" r1 Q4 M6 E; l: L+ b( sfrom boto3.session import Session# | _$ I9 }. f/ f
import boto3
+ K& K7 y: r; ximport uuid' z. d4 O7 {& o; c1 P% e
access_key = 'xxx'% s O3 e/ Q* b5 E# p0 h# B
secret_key = 'xxxxx', R, P/ P( O7 X
host = 'http://ip:7480'
# k, [/ z! Z% \8 e) i. jsession = Session(access_key, secret_key)
. D6 O N; K( J3 Qbuckets = ['bucket1']9 a% v: q. Y. ^ }: @. [: g
s3_client = session.client('s3', endpoint_url=host)! Q4 j& R$ d/ m e- ~
for x in buckets:: Q' s- Q* v8 `; |1 @. c
s3_client.put_bucket_lifecycle(
; H& c, c* Z' U' U, B+ W* T. I Bucket=x,; N+ ^" F$ m5 H" L1 |: t% V8 q" e
LifecycleConfiguration={
$ {7 Y. C4 ?1 n7 c2 } 'Rules': [
# e; X' k4 O, o8 w: k# J6 b0 y6 t1 w {+ v$ m% b: h4 {
'Expiration':
( O# m( @9 ~5 B* r0 `6 N {
( X, [- h3 a' I/ } _" G 'Days': 1
1 G2 i( ~3 ~# ^7 R [ },
1 n" G9 r( j0 O( {/ d1 @ f 'ID': str(uuid.uuid1()),
; w* V& F& \9 h8 X8 Y 'Status': 'Enabled',; L, L1 `8 j0 B6 L& |* t+ a
'Prefix': ''
) s% Y4 k! E7 y. _, D$ ?5 w/ U }1 s! j$ R$ t2 e4 ~: a, A
],
* }1 R6 h X& T; l, Y2 Q# o })7 E% A4 h0 N) \
print s3_client.get_bucket_lifecycle(Bucket=x)- {1 h0 [6 Z$ [4 A
设置完成后,可以通过命令radosgw-admin lc list% y( _5 D) F# x5 w; b. _6 i
查看到当前lc的队列2 Y' J( o2 j& }8 y* H
[store@server227 build]$ ./bin/radosgw-admin -k keyring -c ./ceph.conf lc list
: h. F) @7 ^8 `[# [/ L7 ]/ W( y4 ^) ^6 Q% j4 }! X
{" n' _& @4 F6 i! A2 v- M
"bucket": ":cpp-bucket1:eace8f62-b901-47ed-b18f-fe2441f33830.4155.1",
9 ~/ t% t5 l5 o4 E: E$ W. j, d "status": "COMPLETE"
) o- o8 E I) S3 p! I. F- M }
/ r4 d1 ~! }# ~/ Y]- s% k1 P5 a) O5 j
代码跟读1 w5 S3 H' g* D+ ], w7 b# u6 U! C( ^% v
lc处理流程的入口是rgw/rgw_lc.cc# _' u V: p+ T `, [8 x5 f; g
的*RGWLC::LCWorker::entry7 ^4 P( o: _+ |! [3 ?( a
函数7 n6 M! Q# M' R X. t& H
void *RGWLC::LCWorker::entry() {
6 D& D9 L+ Z, q+ @ do {" [; S6 Z! D* G2 B4 p0 O1 U
utime_t start = ceph_clock_now();
5 C* P' U% @4 ~1 m. n rgw_lc_debug_interval > 0时或者达到设定的回收时间则返回true8 s3 N* p! ^ A5 S% U
if (should_work(start)) {+ e/ h9 ]1 A& ^, t. I, t
ldout(cct, 2) << "life cycle: start" << dendl;
. c ]1 K5 N" i$ S* y' a$ \8 N int r = lc->process();
6 ]+ X0 R$ |- ^8 y' F7 @" N if (r < 0) {
8 f- L9 Y( Q- u1 ?4 O( h ldout(cct, 0) << "ERROR: do life cycle process() returned error r=" << r << dendl;. X, M) s' X' D# L) d
}) [9 ^$ o& C% g( y8 A
ldout(cct, 2) << "life cycle: stop" << dendl;
2 N* ~) @6 P t, w; a0 h+ S }6 b4 e: G# s) ]6 ]
if (lc->going_down())
. I, X9 Z c. c break;% J) \5 H/ Q- D$ C `
utime_t end = ceph_clock_now();
3 c, h0 \$ V8 o9 e* f int secs = schedule_next_start_time(start, end);
! I( s& U6 j( C4 n. _0 v& y utime_t next;2 h" a: s. c+ g6 ]2 }
next.set_from_double(end + secs);) X5 }- L0 w6 ?9 A& l1 [
ldout(cct, 5) << "schedule life cycle next start time: " << rgw_to_asctime(next) << dendl;; w! D2 l3 }; E& r/ S$ J
lock.Lock();
4 Y2 k: w2 f9 X- m& b cond.WaitInterval(lock, utime_t(secs, 0));4 {' Q% j4 v" k# j0 h$ E% u m
lock.Unlock();2 A7 Z/ e! _! Q8 x3 C
} while (!lc->going_down());" O( ~' M ]8 C* `) L; Z* H, ?
return NULL;
6 c* M, ~6 E/ x7 u}! U# H+ Q+ X# R8 k/ z/ L3 ?
如果使能了lc的功能,lc的线程会在进行一次lc作业后,等待一段时间,再唤醒起来进行下一次的作业,这里函数的第三行,使用should_work
( |# x( T. v: B4 Z函数判断本次的lc是否进行,默认rgw_lc_debug_interval! R' g2 h; u: M! D$ x
为-1,表示不进行lc的作业,此时会进一步判断是否在设定的回收时间内,如果不在,也不进行lc作业8 p2 l0 s, C$ P
if (cct->_conf->rgw_lc_debug_interval > 0) {5 \/ ^7 R+ t" r* A8 Z8 C# {
* We're debugging, so say we can run */, p b) F8 C$ [, H( K1 a0 p+ v! c
return true;
) Y# O" H) l9 u& t, X. x! { } else if ((bdt.tm_hour*60 + bdt.tm_min >= start_hour*60 + start_minute) &&
4 G- Z$ n- @8 s2 s* E (bdt.tm_hour*60 + bdt.tm_min <= end_hour*60 + end_minute)) {9 v% D; p" U* u$ D/ T9 V
return true;
7 W+ t' ?' G% R* u* d/ b } else {
# N1 e- R! J8 O+ w return false;
( n ~, k! P6 t, b }
$ i; t H. `$ r+ r) Z4 K3 ^5 Z跟着lc->process()! h) c/ v/ T! ]" J, d+ Z0 G' j. q( m
看下
$ |. ^& k0 n( x/ d1 o: Mint RGWLC::process()
$ C0 n1 F( Q3 ?- a% R{
2 I' W: W) W. [ int max_secs = cct->_conf->rgw_lc_lock_max_time;2 k9 Y/ C" Q' g: m
const int start = ceph::util::generate_random_number(0, max_objs - 1);
4 U2 U3 |* Z$ @7 L for (int i = 0; i < max_objs; i++) {
/ E- j0 g2 z8 @' k7 k7 s# w int index = (i + start) % max_objs; ^, c! E5 }2 l8 z
int ret = process(index, max_secs);
# J2 ~, ?$ v' ] if (ret < 0)
5 D8 o2 Z. j) K/ o" n3 k return ret;: w* ]/ ]+ p% f& \7 b
}" ~, Y( ?) z* F2 t n, R: e5 w
return 0;/ I, m: w& r7 H! @3 l
}' A/ j+ v+ {' O
函数比较简短,限制每次作业的时间为rgw_lc_lock_max_time! G& q0 H" O8 t7 Z$ Q
,默认是60s,结合实际测试看,这个时间并不准确,继续看. b9 `( y1 Y2 l+ H- s
int RGWLC::process(int index, int max_lock_secs)
7 i& l# K( E( w1 l, Z{0 D: M/ r" I: r* u
根据max_lock_secs来判断一个执行周期的时间长度3 m$ H3 N3 `2 x: R
rados::cls::lock::Lock l(lc_index_lock_name);
6 b3 W' f; f; [5 N1 G do {% V2 I: ?( u0 z2 t, P2 }: V
utime_t now = ceph_clock_now();6 M* [2 \, K- j0 Y; i! C% i9 m
......4 N, [ B2 i2 Q& k
从这里开始,正式开始进行对象的lc操作,注意到这是在一个死循环里5 H" k K5 x) c: r
ret = bucket_lc_process(entry.first);
/ l' r {8 @7 m- }1 O bucket_lc_post(index, max_lock_secs, entry, ret);
0 @1 w6 S1 f" b }while(1);. ^7 z+ @0 m* s) H
lock的对象是lc_process3 m& z( e3 t$ L" J, `
,这个函数中会多次请求osd进行lc的前期操作,包括查询head、set一些配置等,最重要的是检查bucket是否配置了lc,开始lifecycle到调用bucket_lc_process
- Z% y7 o2 k6 O& Y3 `3 D前,花费时间为68ms
) B& M/ A0 ?, e S* D/ k" {,接下来, ?- j* k3 d; m3 c: O; G
int RGWLC::bucket_lc_process(string& shard_id)' u: Y5 c- M6 c, k; C* J! _
{8 L5 T5 u& I; j
对bucket的对象进行回收的操作主要在这个函数中进行" t: S! k! Z) j/ i: n
....0 Z0 }" G0 |2 d5 V F) a$ t
设定运行周期
; Q- U3 r. V, M l.set_duration(time);2 f: s; h4 Z. _ Q+ [
....0 A6 }& l% r# @
do {9 R8 i* |5 v: @, l: I
objs.clear();
1 {0 V+ j( ~1 Z4 Y list_op.params.marker = list_op.get_next_marker(); U2 B$ L6 A% ^9 _& h( K" { N
从bucket中选择1000个对象,使用的是bucket list方法
1 d$ C$ A# H9 X) b ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
( j$ r4 k$ J5 a! r- u7 w if (ret < 0) {
0 x# L5 M: l5 k5 b4 X6 Q3 { if (ret == (-ENOENT))% z1 t2 z5 [" l3 G/ p
return 0;; Q0 H h3 `" G7 G: B8 N/ o
ldout(cct, 0) << "ERROR: store->list_objects():" <<dendl;1 N% \8 M* L/ K
return ret;
9 S% |( R3 [) \+ @& m j; p! U }
- P& z9 R }0 n3 K! u2 B9 O1 j 5 _. @ ^$ L6 C3 U7 _$ |3 p' ~
bool is_expired;/ D: ^1 ^: `; ] [, p* Y0 u R- }
遍历这1000个对象,检查它们是否达到了过期时间
5 A8 u3 T2 l9 B4 C5 ^+ J for (auto obj_iter = objs.begin(); obj_iter != objs.end(); ++obj_iter) {; T/ P4 o. d( s
rgw_obj_key key(obj_iter->key);7 N5 g) X7 P y" {( f" p5 k$ V
....
; X* S( \+ S( L" }0 a$ X if (prefix_iter->second.expiration_date != boost::none) {
8 w0 ~- O# H: m+ f we have checked it before# e% x- b$ C0 J, d0 S8 {" ~
is_expired = true;; g. u% [$ Y% l/ k
} else {7 ^1 Z+ v$ L! d( o$ V
检查对象是否达到过期时间- ` G" g- y3 W% G1 I
is_expired = obj_has_expired(obj_iter->meta.mtime, prefix_iter->second.expiration);
4 a6 H0 a6 @( q3 o+ e }( ^+ `2 a" a A9 z9 }% p
if (is_expired) {4 L" A" M$ c" p5 g# q, I) X. o
int ret = store->get_obj_state(&rctx, bucket_info, obj, &state, false);6 C1 ^) a5 ^! f: z
if (ret < 0) {) I B( o2 G9 W- U
return ret;; p: z5 l; f' q& j
}1 Y8 U% R+ V' X+ V S- G
if (state->mtime != obj_iter->meta.mtime) {9 I3 U; S2 f: s0 ~/ ?0 }
Check mtime again to avoid delete a recently update object as much as possible1 n' M0 U: W# b0 T0 v. L) Z, W
ldout(cct, 20) << __func__ << "() skipping removal: state->mtime " << state->mtime << " obj->mtime " << obj_iter->meta.mtime << dendl;: K: ~1 G$ n% f% ~; Y
continue;
6 b& y2 O6 A! z( w% q }7 {- D# V) Z2 K3 t
ret = remove_expired_obj(bucket_info, obj_iter->key, obj_iter->meta.owner, obj_iter->meta.owner_display_name, true);7 Y) D6 b7 \6 ~5 h! v$ M
if (ret < 0) {
C& k+ t( I0 H$ d. b \8 m* l ldout(cct, 0) << "ERROR: remove_expired_obj " << dendl;
! n; S8 r* O1 ?0 ` } else {; A3 o& v. l1 l z$ F1 P- j, O
ldout(cct, 2) << "DELETED:" << bucket_name << ":" << key << dendl;$ y* f+ z/ Y8 ?2 G2 z
}% D5 y7 ^7 t, ~4 _, p
if (going_down())3 n' P0 u' t8 @5 S
return 0;' R6 G1 m/ \0 M- g$ f! \
}1 {+ n, t r* x" z7 I2 F# M
}0 X- }) @% [$ j5 a! t+ o* n* B! {
} while (is_truncated);: b' L5 ?) S' J H u5 Q, t
}
* I O$ P+ C: c. o2 o在这个函数中,首先设定运行时间,线程通过bucket list的方式拿到1000个obj,获取使用按顺序的方式,遍历这1000个对象,如果该对象超过了过期时间,则删掉,关于循环的停止条件,is_truncated: if number of objects in the bucket is bigger than max, then truncated
4 T% ~. u F$ W" u @,即要求遍历完整个bucket
6 H/ u7 h% b8 o' g: g% w p& G这部分耗时主要依赖于bucket内对象的数量,对象越多,则耗时肯定会越多) m( m$ v8 q- G/ X
最后,针对对象中分片情况,进行了处理,在RGWLC::bucket_lc_process
6 s6 S1 m* l ]3 ?% |2 _+ @4 v的最后
^: ~8 x7 p9 Y6 G7 ]- G ret = handle_multipart_expiration(&target, prefix_map);
0 P2 @" N' W6 c return ret;
' ^) N0 i) I% z其中prefix_map+ f2 p& k% T I' a7 B: m
表明了对象分片的相关信息
/ f# |/ V7 m0 @( {+ ^看下对于分片的处理逻辑' _! m- {* N- E; V$ @) w4 |
for (auto prefix_iter = prefix_map.begin(); prefix_iter != prefix_map.end(); ++prefix_iter) {, G J; z( _" }% _7 g5 u
......
- j: C. |" O( n: P do {+ K! I8 w/ l) C/ ^8 n C
objs.clear();
: z/ A5 q/ Z$ y! t4 W9 L list_op.params.marker = list_op.get_next_marker();
. q' ~/ q! m& ? ret = list_op.list_objects(1000, &objs, NULL, &is_truncated);
# _/ A5 K2 [9 G& J* I. F ......
$ c( I+ X0 W: c } while(is_truncated); z7 j6 x/ i$ n) }" }9 g. Y" j/ H
}8 w* T2 ] P) ]6 {
可以看到,与对象删除一样,分片删除也是一次性取出1000个分片对象进行删除
; Y5 K; x( Y- W2 l: ?逻辑总结3 c. w" g& K1 e6 l$ ?5 C& C
首先,在默认情况下,lc线程启动后,根据rgw_lc_debug_interval
/ b2 ~6 y) G; [! L" X5 p及设定的rgw_lifecycle_work_time
8 F9 k5 p) T+ F来判断要不要做本次的process,默认rgw_lc_debug_interval4 f* [! @! c% T' G2 S9 p6 b
为-1,表示不做本次lc,另外,在rgw_lc_debug_interval <=0- \! ^! X$ v0 U
时,这个时间间隔单位将放大为天,原注释说的是We're in debug mode; Treat each rgw_lc_debug_interval seconds as a day% n5 I4 s0 N5 {. t9 \8 [6 G
在决定要做本次lc后,根据rgw_lc_lock_max_time
5 V7 l3 Y! s1 a' Q W% B7 i来判断每次lc的周期时间长度,然后每次从rgw_lc_max_objs" B, Z; z# M; d
中选取一个进行运行,增大这个值不会加快回收速度3 H/ P# j, L. H+ m; y ]& c
关于rgw_lc_max_objs1 \0 `7 m) v! W" B. m8 ?
,它表示的是lc的处理线程的最大数量,它并不是并发的线程,因而增大这个值并不能加快回收速度,它类似于一个分组,bucket被设置lc规则后将被分配到某个lc中,例如,这个值设置为3,它启动回收的时候会随机启动lc.0、lc.1、lc.2中的其中一个,如果某个lc中有bucket则进行回收,测试发现,多个bucket都设置过期的情况下,单个rgw同一时间仅运行一个lc线程,因而设置自动回收速度其实是很慢的, z2 p5 r. N) E$ y( L9 V
接下来,请求并设定一些基本的元数据信息,最重要的是判断bucket是否需要做lc,它检测bucket是否进行了lc的设置,如果设置才做,没有设置,默认是不做
0 m$ L4 |7 Q6 a5 A# p- y% S6 y然后,从指定的bucket中每次取出最多1000个有序对象,遍历这些对象并检查他们是否到达过期时间,过期则删除,直到bucket中所有的对象都遍历完成.: M7 M8 O5 ^) Y+ x1 A; T# D# J
针对分片做了特殊处理,删除掉第一个片后,其余分片有专门的逻辑进行进一步的删除
* x& V: } f- j) q环境中起2个rgw进行测试,发现2个rgw可以同时进行不同bucket的回收,因而,要加快回收速度,只能通过增加rgw的数量实现,单个rgw明显只能串行单线程进行单bucket的回收,不过,因为回收bucket涉及到bucket list,过快的回收速度也可能引发性能问题,需要权衡) \1 Z; X8 M2 i6 a% V
|
|