说明:本篇中所有代码分析,都是基于Jewel 10.2.9版本。本篇都是个人理解,其中有些理解或者解释有不合理的,还请指正。
在Ceph的使用中,运行时调整参数值是个高频的操作,使用起来简单方便,最重要的是不用重启服务即可生效。
如何使用
Ceph动态调整参数有两种方式:
第一种:
1. ceph daemon <mon /osd/mds >.< id > config set <参数名> <参数值> 比如,设置OSD 1的heartbeat超时时间: ceph daemon osd.1 config set osd_heartbeat_grace 60 |
第二种:
2. ceph tell <mon /osd/mds >.< id > injectargs '--<参数名> <参数值>' 设置OSD 1的heartbeat超时时间: ceph tell osd.1 injectargs '--osd_heartbeat_grace 60' |
第二种还有两个比较好用的地方:
-
单条命令可以改变所有的实例的某个参数值:
ceph tell <mon /osd/mds >.* injectargs '--<参数名> <参数值>' 设置所有OSD的heartbeat超时时间: ceph tell osd.* injectargs '--osd_heartbeat_grace 60' |
-
单条命令可以改变多个参数:
ceph tell <mon /osd/mds >.1 injectargs '--<参数名> <参数值> --<参数名> <参数值>' 设置OSD 1的heartbeat超时时间,及发起heartbeat的时间间隔 ceph tell osd.1 injectargs '--osd_heartbeat_grace 60 --osd_heartbeat_interval 10' |
-
当然,上面两个可以结合使用:
ceph tell <mon /osd/mds >.* injectargs '--<参数名> <参数值> --<参数名> <参数值>' 设置所有OSD的heartbeat超时时间,及发起heartbeat的时间间隔 ceph tell osd.* injectargs '--osd_heartbeat_grace 60 --osd_heartbeat_interval 10' |
源码分析
那么Ceph内部是如何实现上面提到的动态更新呢?我们来深入代码中一探究竟。
tell方式的实现
我们先看tell...injectargs方式的实现:
ceph命令行的输入,源码入口都是ceph.in文件,是python文件,也就是/usr/bin/下的可执行文件ceph。
ceph.in中有关tell的代码:
def main(): ... // 如果命令行中有'injectargs'字串,就进行切分成两个:'injectargs'之前的部分是childargs,之后的部分是injectargs。 if 'injectargs' in childargs: position = childargs.index( 'injectargs' ) injectargs = childargs[position:] childargs = childargs[:position] if verbose: print( 'Separate childargs {0} from injectargs {1}' .format(childargs, injectargs), file=sys.stderr) else : injectargs = None ... if injectargs and '--' in injectargs injectargs. remove ( '--' ) ... // 如果childargs有'tell'命令,或者前面已经确定有injectargs,就将childargs重新赋值,也就是说childargs是真正要去发送给server端的命令。 // 在这里,和tell就没关系了,tell关键字并不会发到后端。 is_tell = False if len(childargs) and childargs[0] == 'tell' : childargs = childargs[2:] is_tell = True if is_tell: if injectargs: childargs = injectargs if not len(childargs): print( '"{0} tell" requires additional arguments.' .format(sys.argv[0]), 'Try "{0} tell <name> <command> [options...]" instead.' .format(sys.argv[0]), file=sys.stderr) return errno .EINVAL ... // 每个命令执行,都有目标端,如果命令行指明了目标server,就向具体的server发;如果是'*',会先把所有的instance id拿到,然后在后面的for循环中,逐个去发送命令。 if target[1] == '*' : if target[0] == 'osd' : targets = [(target[0], o) for o in osdids()] elif target[0] == 'mon' : targets = [(target[0], m) for m in monids()] ... for target in targets: ... |
从上面的代码,可知,tell命令,并没有走daemon的admin socket来进行通信,而是走正常的client->server的通信,而且,'*'也没有那么高效,就是拿到所有的id,然后逐个发送消息。
然后再经过librados、osdc等模块,将消息发送给具体的Monitor、OSD、MDS。
我们接下来直接看OSD端收到injectargs命令后,如何处理。
在OSD.cc中,do_command函数就是专门处理各种命令的,我们直接看injectargs分支:
void OSD::do_command(Connection *con, ceph_tid_t tid, vector<string>& cmd, bufferlist& data) {
... else if (prefix == "injectargs" ) {
vector<string> argsvec; cmd_getval(cct, cmdmap, "injected_args" , argsvec); if (argsvec.empty()) {
r = -EINVAL; ss << "ignoring empty injectargs" ; goto out; } string args = argsvec.front(); // 获取所有的要进行更新的参数 for (vector<string>::iterator a = ++argsvec.begin(); a != argsvec.end(); ++a) args += " " + *a; osd_lock.Unlock(); cct->_conf->injectargs(args, &ss); osd_lock.Lock(); } ... } |
拿到client想要更新的所有参数,然后调用了injectargs,每个OSD都有一个CephContext类变量cct,md_config_t类型的_conf变量也是CephContext类的成员。
int md_config_t::injectargs( co
|