【ceph】cephfs caps简介
目录
CAPS基础
基本概念
caps是mds授予client对文件进行操作的许可证,当一个client想要对文件元数据进行变更时,比如读、写、修改权限等等操作,它都必须先获取到相应的caps才可以进行这些操作。
ceph对caps的划分粒度很细,且允许多个client在同一个inode上持有不同的caps。
CAPS种类
根据元数据内容的不同,ceph将caps也分为了多个类别,每种类别只负责作用于某些特定的元数据:
类别 | 功能 |
---|---|
PIN | mds是否将inode pin在cache中 |
AUTH | 鉴权相关的元数据,主要是owner、group、mode;但是如果是完整的鉴权是需要查看ACL的,acl信息保存在xattr中,这就需要XATTR相关的cap |
XATTR | xattr |
FILE | 最重要也是最复杂的一个,用于文件数据,以及和文件数据相关的ize、atime、ctime、mtime等 |
CAPS PERMISSION种类
#define CEPH_CAP_GSHARED 1 /* client can reads (s) */
#define CEPH_CAP_GEXCL 2 /* client can read and update (x) */
#define CEPH_CAP_GCACHE 4 /* (file) client can cache reads (c) */
#define CEPH_CAP_GRD 8 /* (file) client can read (r) */
#define CEPH_CAP_GWR 16 /* (file) client can write (w) */
#define CEPH_CAP_GBUFFER 32 /* (file) client can buffer writes (b) */
#define CEPH_CAP_GWREXTEND 64 /* (file) client can extend EOF (a) */
#define CEPH_CAP_GLAZYIO 128 /* (file) client can perform lazy io (l) */
CAPS COMBINATION
一个完整cap通过【类别+permission种类】组成,client可以同时申请多个类别的caps。但是并不是每种caps都可以使用每种permission,有些caps只能搭配部分permission。有关caps种类和permission的结合使用,有以下几个规则:
PIN
二值型,有pin就代表client知道这个inode存在,这样mds就一定会在其cache中保存这个inode
AUTH、LINK、XATTR
只能为shared或者exclusive
- shared:client可以将对应元数据保存在本地并缓存和使用
- exclusive:client不仅可以在本地缓存使用,还可以修改
下面是两个例子:
- [A]s:某client对inode 0x11有As的cap,此时收到一个查看0x11状态的系统调用,那么client不需要再向mds请求,直接通过查询自身缓存并进行处理和回复
- [A]x:某client对inode 0x11有Ax的cap,此时收到一个修改mode的系统调用,client可以直接在本地进行修改并回复,并且在之后才将修改变更通知mds
FILE
如前所述,file是最复杂的一种,下面是File cap的各个类别:
file cap种类 | client权限 |
---|---|
Fs | client可以将mtime和size在本地cache并读取使用 |
Fx | client可以将mtime和size在本地cache并进行修改和读取 |
Fr | client可以同步地从osd读取数据,但不能cache |
Fc | client可以将文件数据cache在本地内存,并直接从cache中读 |
Fw | client可以同步地写数据到osd中,但是不能buffer write |
Fb | client可以buffer write,先将写的数据维护在自己内存中,再统一flush到后端落盘 |
CAPS管理
LOCK
caps由mds进行管理,其将元数据划分为多个部分,每个部分都有专门的锁(SimpleLock、ScatterLock、FileLock)来保护,mds通过这些锁的状态来确定caps可以怎么样分配。
mds内部维护了每个锁的状态机,其内容非常复杂,也是mds保证caps分配准确性和数据一致性的关键。
CAPS如何变更
- mds可以针对每个client进行授予和移除caps,通常是由其他client的行为触发
- 例:比如client1已经拥有了inode 0x111的cache read的cap,此时client2要对这个文件进行写,那显然除了授予client2响应的写caps的同时,还要剥夺client1的cache read的cap
- 当client被移除caps时,其必须停止使用该cap,并给mds回应确认消息。mds需要等待收到client的确认消息后才会revoke。(如果client挂掉或者出于某种原因没有回复ack怎么办?)
- client停止使用并不简单,在不同场景下需要完全不同的处理:
- 例1:client被移除cache read cap,直接把该file的cache删掉,并变更状态就行了,这样下次的read请求过来时,还是到osd去读
- 例2:client被移除buffer write cap,已经缓存了大量的数据还没有flush,那就需要先flush到osd,再变更状态和确认,这可能就需要较长时间
下面来看看一个修改权限的例子实际感受下:
CAPS相关告警
下面是一些caps相关的主要告警信息,可参考对照排查问题:
告警信息 | 问题 |
---|---|
Client failing to respond to capability release | mds发出了revoke cap消息但是client没有回复 |
Client failing to cache pressure | mds发送消息请求client去除一些pinned inode以减少内存使用,但client没有drop的足够或者没有回复的足够快 |
总结
- mds需要记住所有client pin的inode,
- mds的cache需要比client的cache更多
- caps是由mds和client端共同协作维护的,所以client需要正常运行,否则可能会block其他client(也就是说前面提出的问题,会被block?)
CAPS代码相关
CAPS数据表示和规则
一个client可以拥有多种类型(A,L,X,F)的caps,每种类型的caps也有多种permission类型(s,x,c,r,w,b,a,l)。那么如何表示这么多类型的caps呢?
- ceph首先规定了每种类型cap的bit范围,保证不同类型的cap的bit范围没有重叠。
/* generic cap bits */
#define CEPH_CAP_GSHARED 1 /* client can reads(s) */
#define CEPH_CAP_GEXCL 2 /* client can read and update(x) */
#define CEPH_CAP_GCACHE 4 /* (file) client can cache reads(c) */
#define CEPH_CAP_GRD 8 /* (file) client can read(r) */
#define CEPH_CAP_GWR 16 /* (file) client can write(w) */
#define CEPH_CAP_GBUFFER 32 /* (file) client can buffer writes(b) */
#define CEPH_CAP_GWREXTEND 64 /* (file) client can extend EOF(a) */
#define CEPH_CAP_GLAZYIO 128 /* (file) client can perform lazy io(l) */
/* per-lock shift */
#define CEPH_CAP_SAUTH 2 // A
#define CEPH_CAP_SLINK 4 // L
#define CEPH_CAP_SXATTR 6 // X
#define CEPH_CAP_SFILE 8 // F
- 通过定义每种类型permission类型的bit位和每种cap类型的偏移量,通过移位将两者组合起来形成单一cap。
#define CEPH_CAP_AUTH_SHARED (CEPH_CAP_GSHARED << CEPH_CAP_SAUTH) // As
#define CEPH_CAP_AUTH_EXCL (CEPH_CAP_GEXCL << CEPH_CAP_SAUTH) // Ax
#define CEPH_CAP_LINK_SHARED (CEPH_CAP_GSHARED << CEPH_CAP_SLINK) // Ls
#define CEPH_CAP_LINK_EXCL (CEPH_CAP_GEXCL << CEPH_CAP_SLINK) // Lx
#define CEPH_CAP_XATTR_SHARED (CEPH_CAP_GSHARED << CEPH_CAP_SXATTR) // Xs
#define CEPH_CAP_XATTR_EXCL (CEPH_CAP_GEXCL << CEPH_CAP_SXATTR) // Xx
#define CEPH_CAP_FILE(x) (x << CEPH_CAP_SFILE)
#define CEPH_CAP_FILE_SHARED (CEPH_CAP_GSHARED << CEPH_CAP_SFILE) // Fs
#define CEPH_CAP_FILE_EXCL (CEPH_CAP_GEXCL << CEPH_CAP_SFILE) // Fx
#define CEPH_CAP_FILE_CACHE (CEPH_CAP_GCACHE << CEPH_CAP_SFILE) // Fc
#define CEPH_CAP_FILE_RD (CEPH_CAP_GRD << CEPH_CAP_SFILE) // Fr
#define CEPH_CAP_FILE_WR (CEPH_CAP_GWR << CEPH_CAP_SFILE) // Fw
#define CEPH_CAP_FILE_BUFFER (CEPH_CAP_GBUFFER << CEPH_CAP_SFILE) // Fb
#define CEPH_CAP_FILE_WREXTEND (CEPH_CAP_GWREXTEND << CEPH_CAP_SFILE) // Fa
#define CEPH_CAP_FILE_LAZYIO (CEPH_CAP_GLAZYIO << CEPH_CAP_SFILE) // Fl
- 再通过或运算符将不同的cap组合起来形成多个caps
更形象的用图形来表示:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| p | _ |As x |Ls x |Xs x |Fs x c r w b a l |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| PIN | AUTH | LINK | XATTR | FILE
0 2 4 6 8
这里还需要注意两点:
- pin cap只需要bit位0,所以bit位1是空闲出来无用的
- 除了file cap,其他类型的都不会占用2bit以上,所以把file cap放到了高位
FUSE WRITE实例
一个实例
下面以fuse client write为例,简要分析下fuse write时caps的代码逻辑:
注:只截取了和caps相关的部分代码
int64_t Client::_write(Fh *f, int64_t offset, uint64_t size, const char *buf,
const struct iovec *iov, int iovcnt)
{
want = CEPH_CAP_FILE_BUFFER;
// 需要拥有file write(CEPH_CAP_FILE_WR)和auth shared(CEPH_CAP_AUTH_SHARED) caps才(即FwAs)能够写,get_caps中如果检查没有caps则会
// 去向mds申请并等待返回
int r = get_caps(in, CEPH_CAP_FILE_WR|CEPH_CAP_AUTH_SHARED, want, &have, endoff);
if (r < 0)
return r;
/* clear the setuid/setgid bits, if any */
if (unlikely(in->mode & (S_ISUID|S_ISGID)) && size > 0) {
struct ceph_statx stx = { 0 };
// 增加该inode对该caps的引用计数并检查该caps是否正在使用中
put_cap_ref(in, CEPH_CAP_AUTH_SHARED);
r = __setattrx(in, &stx, CEPH_SETATTR_KILL_SGUID, f->actor_perms);
if (r < 0)
return r;
} else {
put_cap_ref(in, CEPH_CAP_AUTH_SHARED);
}
// 如果有buffer 或者lazy io cap则直接在objectcacher cache中写
if (cct->_conf->client_oc &&
(have & (CEPH_CAP_FILE_BUFFER | CEPH_CAP_FILE_LAZYIO))) {
// do buffered write
if (!in->oset.dirty_or_tx)
get_cap_ref(in, CEPH_CAP_FILE_CACHE | CEPH_CAP_FILE_BUFFER);
get_cap_ref(in, CEPH_CAP_FILE_BUFFER);
// async, caching, non-blocking.
// 缓存写的调用,异步、cache、非阻塞
r = objectcacher->file_write(&in->oset, &in->layout,
in->snaprealm->get_snap_context(),
offset, size, bl, ceph::real_clock::now(),
0);
put_cap_ref(in, CEPH_CAP_FILE_BUFFER);
if (r < 0)
goto done;
// flush cached write if O_SYNC is set on file fh
// O_DSYNC == O_SYNC on linux < 2.6.33
// O_SYNC = __O_SYNC | O_DSYNC on linux >= 2.6.33
if ((f->flags & O_SYNC) || (f->flags & O_DSYNC)) {
_flush_range(in, offset, size);
}
} else {// 如果没有buffer cap,则直接通过osd写
if (f->flags & O_DIRECT)
_flush_range(in, offset, size);
// simple, non-atomic sync write
C_SaferCond onfinish("Client::_write flock");
unsafe_sync_write++;
get_cap_ref(in, CEPH_CAP_FILE_BUFFER); // released by onsafe callback
// 同步写的调用
filer->write_trunc(in->ino, &in->layout, in->snaprealm->get_snap_context(),
offset, size, bl, ceph::real_clock::now(), 0,
in->truncate_size, in->truncate_seq,
&onfinish);
client_lock.Unlock();
// 写完之后通过条件变量在这里等待,当写完成之后被唤醒,执行一些清理工作并返回
onfinish.wait();
client_lock.Lock();
_sync_write_commit(in);
}
}
参考链接
[1] What are “caps”? (And Why Won’t my Client Drop Them?)
[2] cephfs capabilities
转自:cephfs caps简介_https://blog.csdn.net/jiang4357291/article/details/103738524
cephfs caps简介 - https://www.freesion.com/article/3041236135/