Ceph動態更新參數機制淺析

說明:本篇中所有代碼分析,都是基於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'

第二種還有兩個比較好用的地方:

  1. 單條命令可以改變所有的實例的某個參數值:

    ceph tell <mon/osd/mds>.* injectargs '--<參數名> <參數值>'

     

    設置所有OSD的heartbeat超時時間:

    ceph tell osd.* injectargs '--osd_heartbeat_grace 60'

  2. 單條命令可以改變多個參數:

    ceph tell <mon/osd/mds>.1 injectargs '--<參數名> <參數值> --<參數名> <參數值>'

     

    設置OSD 1的heartbeat超時時間,及發起heartbeat的時間間隔

    ceph tell osd.1 injectargs '--osd_heartbeat_grace 60 --osd_heartbeat_interval 10'

  3. 當然,上面兩個可以結合使用:

    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(const std::string& s, std::ostream *oss)

{

    ...

    // 解析參數

    ret = parse_injectargs(nargs, oss);

     

    ...

    // 生效更新的參數

    _apply_changes(oss);

}

 

int md_config_t::parse_injectargs(std::vector<const char*>& args, std::ostream *oss)

{

  ...

  for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) {

    // 逐個解析參數,並更新參數。

    int r = parse_option(args, i, oss);                                        

    if (r < 0)                                                                 

      ret = r;                                                                 

  }   

  ...                                                                        

}

 

int md_config_t::parse_option(std::vector<const char*>& args,

                   std::vector<const char*>::iterator& i,

                   ostream *oss)

{

  ...

  // 檢查更新的參數中,是否有子系統(subsystem)的log級別參數,也就是以debug_開頭的日誌級別參數。

  // 但是下面的這種遍歷,似乎很低效啊,如果根本就沒有debug_類參數,還是會檢查一遍。

  // 再看了一下L版的代碼,已經修改了,在parse_option中沒有這種遍歷式檢查subsystem,而是直接在md_config_t類的構造函數中把所有參數vector抓換爲map,這樣在運行時,能更高效的找到某個參數。

  // subsystems?

  for (o = 0; o < subsys.get_num(); o++) {

    std::string as_option("--");

    as_option += "debug_";

    as_option += subsys.get_name(o);

    if (ceph_argparse_witharg(args, i, &val,

                  as_option.c_str(), (char*)NULL)) {

      int log, gather;

      int r = sscanf(val.c_str(), "%d/%d", &log, &gather);

      ...

        // ceph的日誌級別參數調整,直接就通過下面的兩個函數調整了,和其他參數的處理是有區別的。

        subsys.set_log_level(o, log);

        subsys.set_gather_level(o, gather);

      ...

    }

  }

 

  // 對於非log level參數,區分了Bool型和其他類型,然後調用的set_val_impl進行設置

  for (o = 0; o < NUM_CONFIG_OPTIONS; ++o) {

    ostringstream err;

    const config_option *opt = config_optionsp + o;

    std::string as_option("--");

    as_option += opt->name;

    if (opt->type == OPT_BOOL) {

        if (ceph_argparse_binary_flag(args, i, &res, oss, as_option.c_str(),

             (char*)NULL)) {

            if (res == 0)               

                set_val_impl("false", opt);

           else if (res == 1)          

                set_val_impl("true", opt);

           else                        

                ret = res; 

         ...

    }

    else if (ceph_argparse_witharg(args, i, &val, err,

                   as_option.c_str(), (char*)NULL)) {      

        int res = set_val_impl(val.c_str(), opt);

    }

    ...

}

 

 

int md_config_t::set_val_impl(const char *val, const config_option *opt)

{

 assert(lock.is_locked());  

  // 先設置參數值,具體看下面的函數原型

  int ret = set_val_raw(val, opt);

  if (ret)                       

    return ret;                  

  // 參數通過上面的調用實現了,但是參數並不一定生效,對於大部分參數,已經在進程啓動的時候加載了,所以現在更重要的是將這些參數生效。

  // changed是一個set類型變量,主要記錄的是從上一次apply_changes後改變的參數。

  // 在injectargs函數中,parse_injectargs解析之後,調用了_apply_changes函數,函數的參數就是changed變量:

 changed.insert(opt->name);     

  return 0;                      

}

 

// 設置參數值,根據參數類型來設置,做一些強制類型轉換。

int md_config_t::set_val_raw(const char *val, const config_option *opt)

{

  switch (opt->type) {

    case OPT_INT: {

      *(int*)opt->conf_ptr(this) = f;

    }

    case OPT_STR:

      *(std::string*)opt->conf_ptr(this) = val ? val : "";

    ...

  }

}

上面代碼完成了參數的更新,主要有兩點:

  1. debug_類的參數(日誌級別參數)單獨走的一套,和其他參數不是同一種處理。
  2. 其他參數,先更新內存中的參數值;然後更重要的是:需要使其生效,即能夠讓系統真實的使用這些參數了。

 

後續的apply_changes是如何實現的?我們先暫放一下,先來看看daemon方式修改參數,因爲這兩種最終都會通過_apply_changes來使參數生效。

daemon方式的實現

我們繼續看看daemon...config set方式的實現:

ceph.in中有關daemon的代碼:

def main():

    ...

    // 如果命令中有'daemon'字符串,先嚐試看有沒有socket path,如果沒有就從配置文件中去找具體的instance的admin_socket參數值,也就是socket文件路徑

    sockpath = None                       

    if parsed_args.admin_socket:          

        sockpath = parsed_args.admin_socket

    elif len(childargs) > 0 and childargs[0] in ["daemon", "daemonperf"]:

       ...

            if childargs[1].find('/') >= 0:          

                sockpath = childargs[1]              

            else:                                    

                # try resolve daemon name            

                try:                                 

                    sockpath = ceph_conf(parsed_args, 'admin_socket',

                                         childargs[1])

                ...            

            # for both:           

            childargs = childargs[2:]

    ...

    if sockpath and daemon_perf:

         ...

    elif sockpath:

        try:

            // 嘗試連接admin_socket,併發送命令

            print(admin_socket(sockpath, childargs, format))

    ...

}

 

def admin_socket(asok_path, cmd, format=''):

    def do_sockio(path, cmd_bytes):

        """ helper: do all the actual low-level stream I/O """

        // 創建socket,並連接,然後發送相關命令

        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

        sock.connect(path)

        try:

            sock.sendall(cmd_bytes + '\0')

            len_str = sock.recv(4)

            if len(len_str) < 4:

                raise RuntimeError("no data returned from admin socket")

            l, = struct.unpack(">I", len_str)

            sock_ret = ''

 

            got = 0

            while got < l:

                bit = sock.recv(l - got)

                sock_ret += bit

                got += len(bit)

 

        except Exception as sock_e:

            raise RuntimeError('exception: ' + str(sock_e))

        return sock_ret

    ...

    try:

        ret = do_sockio(asok_path, json.dumps(valid_dict))

    except Exception as e:

        raise RuntimeError('exception: ' + str(e))

 

    return ret

Admin socket是什麼呢?其實就是UNIX Domain Socket,是在socket架構上發展出的一種IPC機制,雖然網絡socket也可用於同一臺主機的進程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用於IPC更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因爲,IPC機制本質上是可靠的通訊,而網絡協議是爲不可靠的通訊設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,類似於TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。

 UNIX Domain Socket與網絡socket編程最明顯的不同在於地址格式不同,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用創建,如果調用bind()時該文件已存在,則bind()錯誤返回。

從上面的代碼可以看到,我們現在是一個client,嘗試進行socket連接、通信,那server端在哪裏?

每個OSD實例在啓動的時候,都有一個CephContext類變量cct變量,這個變量中會new AdminSocket對象,在ceph_osd.cc的main函數中會有cct的初始化操作:common_init_finish(g_ceph_context)。

我們先來看看common_init_finish函數:

common/common_init.cc:

void common_init_finish(CephContext *cct)

{

  cct->init_crypto();

 

  // 啓動一些線程,包括log線程、admin socket的初始化

  int flags = cct->get_init_flags();

  if (!(flags & CINIT_FLAG_NO_DAEMON_ACTIONS))

    cct->start_service_thread();

   

  // 修改admin socket path的權限

  if ((flags & CINIT_FLAG_DEFER_DROP_PRIVILEGES) &&

      (cct->get_set_uid() || cct->get_set_gid())) {

    cct->get_admin_socket()->chown(cct->get_set_uid(), cct->get_set_gid());

  }

}

 

common/ceph_context.cc:

void CephContext::start_service_thread()

{

  ...

  if (_conf->admin_socket.length())

    _admin_socket->init(_conf->admin_socket);

}

 

common/admin_socket.cc:

bool AdminSocket::init(const std::string &path)

{

  ...

  // 綁定socket文件,並開始監聽sock fd

  err = bind_and_listen(path, &sock_fd);

  ...

}

可以看到:UNIX Domain Socket server已經準備就緒,開始監聽網絡。

在CephContext類構造函數中,創建了一個AdminSocket類變量_admin_socket,註冊了一些_admin_socket可以處理的command:

CephContext::CephContext(uint32_t module_type_, int init_flags_)

{

  ...

  _admin_socket = new AdminSocket(this);

  _admin_hook = new CephContextHook(this);

  _admin_socket->register_command("config show", "config show", _admin_hook, "dump current config settings");

  _admin_socket->register_command("config set", "config set name=var,type=CephString name=val,type=CephString,n=N",  _admin_hook, "config set <field> <val> [<val> ...]: set a config variable");

  _admin_socket->register_command("config get", "config get name=var,type=CephString", _admin_hook, "config get <field>: get the config value");

  ...

}

從上面的代碼可以看到,配置參數相關的這幾個都已經註冊在admin_socket中,而且這些命令相關的hook是CephContextHook類。

同時,在OSD啓動時,admin_socket初始化成功後,調用了final_init,也註冊了一些admin_socket可以識別的command。

我們再看看OSD的inal_init函數:

void OSD::final_init()                         

{                              

  int r;

  // 這個admin_socket變量是從CephContext中獲取的,並沒new對象。這樣CephContext和OSD中的admin_socket就是同一個,

  AdminSocket *admin_socket = cct->get_admin_socket();

  asok_hook = new OSDSocketHook(this);

  r = admin_socket->register_command("status", "status", asok_hook,

                     "high-level status of OSD");

  assert(r == 0);

  r = admin_socket->register_command("flush_journal", "flush_journal",

                                     asok_hook,

                                     "flush the journal to permanent store");

  ...

}

在這裏繼續註冊了一些可以通過admin socket來執行的命令,這裏的hook是OSDSocketHook類。

AdminSocket類繼承的Thread類,在線程入口函數entry中,會通過poll方式等待event,然後有connection的時候,會進行do_accept,然後進行正常的網絡stream讀寫。

當client通過admin socket向server端發送了命令後,admin socket server會接收消息,在do_accept函數中,會判斷這個command是否註冊,如果註冊了,調用相應的hook->call處理,最後將結果回覆給client。

在CephContext類中,hook是CephContextHook類變量:

common/ceph_context.cc:

class CephContextHook : public AdminSocketHook {

  CephContext *m_cct;

 

public:

  explicit CephContextHook(CephContext *cct) : m_cct(cct) {}

 

  bool call(std::string command, cmdmap_t& cmdmap, std::string format,

        bufferlist& out) {

    m_cct->do_command(command, cmdmap, format, &out);

    return true;

  }

}

在OSD類中,hook是一個OSDSocketHook類變量:

osd/OSD.cc:

class OSDSocketHook : public AdminSocketHook {

  OSD *osd;

public:

  explicit OSDSocketHook(OSD *o) : osd(o) {}

  bool call(std::string command, cmdmap_t& cmdmap, std::string format,

        bufferlist& out) {

    stringstream ss;

    bool r = osd->asok_command(command, cmdmap, format, ss);

    out.append(ss);

    return r;

  }

}

在這裏,我們先只看config set相關的處理,在CephContext的do_command函數中:

common/ceph_context.cc:

void CephContext::do_command(std::string command, cmdmap_t& cmdmap,

            std::string format, bufferlist *out)

{

  ...

    else if (command == "config set") {

      std::string var;  

      std::vector<std::string> val;

 

      if (!(cmd_getval(this, cmdmap, "var", var)) ||

          !(cmd_getval(this, cmdmap, "val", val))) {

        f->dump_string("error", "syntax error: 'config set <var> <value>'");

      } else {

        // val may be multiple words

        string valstr = str_join(val, " ");

        int r = _conf->set_val(var.c_str(), valstr.c_str());

        if (r < 0) {

          f->dump_stream("error") << "error setting '" << var << "' to '" << valstr << "': " << cpp_strerror(r);

        } else {

          ostringstream ss;

          // 調用apply_changes, 生效參數

          _conf->apply_changes(&ss);

          f->dump_string("success", ss.str());

        }

      }

    }

  ...

}

 

common/config.cc:

int md_config_t::set_val(const char *key, const char *val, bool meta, bool safe)

{

  ...

  // 下面的代碼結構是不是很熟悉,對,和前面的tell方式中的parse_option很像,先設置了debug級別,然後再設置其他的參數。

  if (strncmp(k.c_str(), "debug_", 6) == 0) {

    ...

    subsys.set_log_level(o, log);

    subsys.set_gather_level(o, gather);

    ...

  }

  ...

 

  for (int i = 0; i < NUM_CONFIG_OPTIONS; ++i) {

    ...

    return set_val_impl(v.c_str(), opt);

    ...

  }

  ...

}

可以看到,和tell方式一樣,也是先改變參數值,然後再調用_apply_changes來生效之。

參數如何應用到運行中的系統

現在,我們來看看_apply_changes的實現:

void md_config_t::_apply_changes(std::ostream *oss)

{

  // 從變量命名來看,是reverse obs_map_t,也就是obs_map_t結構體的反轉結構,obs_map_t是這樣的結構:typedef std::multimap <std::string, md_config_obs_t*> obs_map_t;

  // rev_obs_map_t除了反轉外,map value type成了set。

  // obs_map_t是個multimap,存的是配置參數和config observer的映射關係。

  // 現在的rev_obs_map_t是config observer和配置參數的映射關係,可以直接通過observer來查看都有哪些參數。

 typedef std::map < md_config_obs_t*, std::set <std::string> > rev_obs_map_t;

 

  expand_all_meta();

 

  rev_obs_map_t robs;

  std::set <std::string> empty_set;

  char buf[128];

  char *bufptr = (char*)buf;

  // 這個循環就是要完成obs_map_t結構的反轉,以及參數option組成的string set。

  for (changed_set_t::const_iterator c = changed.begin();

       c != changed.end(); ++c) {

    const std::string &key(*c);

    // multimap::equal_range是從map中查找某個key,返回第一個匹配的key對應的pair,和最後一個匹配key的下一個pair

    // 所以,這裏就找到了某個參數的所有observers

    pair < obs_map_t::iterator, obs_map_t::iterator >

      range(observers.equal_range(key));

    if ((oss) &&

    (!_get_val(key.c_str(), &bufptr, sizeof(buf))) &&

    !_internal_field(key)) {

      (*oss) << key << " = '" << buf << "' ";

      // 如果observers這個map中沒有這個key,range的first和second都是指向同一個pair,或者都是NULL。

      // 所以,這裏就說明:沒有任何observer關注這個參數。

      // 這裏的unchangeable是不是在設置參數的經常看到,其實就標明沒有observer,也就更不能談生效了。但是我們在檢查參數的時候,確實看到了參數變了,就是因爲之前看到的先設置了參數。

      // 在L版的代碼中,這個"unchangeable"輸出已經換成了"not observed, change may require restart",也更清楚的說明了你修改的參數沒人關注,沒生效。

      if (range.first == range.second) {

        (*oss) << "(unchangeable) ";

      }

    }

    // 生成rev_obs_map_t類型的robs

    for (obs_map_t::iterator r = range.first; r != range.second; ++r) {

      rev_obs_map_t::value_type robs_val(r->second, empty_set);

      pair < rev_obs_map_t::iterator, bool > robs_ret(robs.insert(robs_val));

      std::set <std::string> &keys(robs_ret.first->second);

      keys.insert(key);

    }

  }

 

  // 遍歷所有的observers,然後依次調用它的handle_conf_change,來應用參數。

  // Make any pending observer callbacks

  for (rev_obs_map_t::const_iterator r = robs.begin(); r != robs.end(); ++r) {

    md_config_obs_t *obs = r->first;

    obs->handle_conf_change(this, r->second);

  }

 

  changed.clear();

}

上面代碼中註釋挺詳細的了,我們在這裏說一下observers是什麼?就是具體的MON、OSD、MDS實例,這裏用的是觀察者模式,觀察者關注自己關心的參數,這些參數一旦有更新,觀察者會通過handle_conf_change使其生效。

那麼,這些observer是什麼時候開始watch的?我們這裏只說OSD,在OSD::pre_init中,調用了cct->_conf->add_observer(this); pre_init也是在OSD啓動時調用的。

// 具體的MON、OSD、MDS實例在啓動時,調用本函數,將自己將入到config的observers中

void md_config_t::add_observer(md_config_obs_t* observer_)

{

  Mutex::Locker l(lock);

  // 每個observer都watch哪些config option呢?就是通過自己的get_tracked_conf_keys來定義的

  const char **keys = observer_->get_tracked_conf_keys();

  for (const char ** k = keys; *k; ++k) {

    obs_map_t::value_type val(*k, observer_);

    observers.insert(val);

  }

}

我們先看看OSD都關注了哪些config options:(其實很少的,除了一些log的,就10幾個)

const char** OSD::get_tracked_conf_keys() const

{

  static const char* KEYS[] = {  

    "osd_max_backfills",         

    "osd_min_recovery_priority"

    "osd_op_complaint_time",     

    "osd_op_log_threshold",      

    "osd_op_history_size",       

    "osd_op_history_duration",   

    "osd_enable_op_tracker",     

    "osd_map_cache_size",        

    "osd_map_max_advance",       

    "osd_pg_epoch_persisted_max_stale",

    "osd_disk_thread_ioprio_class"

    "osd_disk_thread_ioprio_priority",

    // clog & admin clog         

    "clog_to_monitors",          

    "clog_to_syslog",            

    "clog_to_syslog_facility",   

    "clog_to_syslog_level",      

    "osd_objectstore_fuse",      

    "clog_to_graylog",           

    "clog_to_graylog_host",      

    "clog_to_graylog_port",      

    "host",                      

    "fsid",                      

    "osd_client_message_size_cap",

    "osd_client_message_cap",    

    NULL                         

  };                             

  return KEYS;                   

}

看了上面的參數列表,是不是很驚訝,就這些?難道我經常設置的參數,貌似生效了(參數值確實改變了),其實根本沒有真正的生效。

我們再來看OSD的handle_conf_changes:

void OSD::handle_conf_change(const struct md_config_t *conf,      

                 const std::set <std::string> &changed)           

{

  if (changed.count("osd_max_backfills")) {

    service.local_reserver.set_max(cct->_conf->osd_max_backfills);

    service.remote_reserver.set_max(cct->_conf->osd_max_backfills);

  }

  ...

}

這個函數就會根據不同的參數,調用具體的函數,將新的參數值應用到運行的系統中。

 

到這裏,我們也基本搞清了動態更新參數的實現過程,當然,還有很多細節並沒有研究。我們還是先搞清整個流程、原理,然後遇到問題,再深入細節分析。

總結

  1. 通過daemon admin socket修改參數走的是UNIX Domain Socket模式。所以,每次只能設置一個實例的參數;並且,只能在本地設置本地實例的參數。
  2. 通過tell修改參數走的是正常的命令發送流程。可以通過'*'的方式設置所有的實例的參數。
  3. 非log級別debug_類參數的動態更新,都經歷了兩步:設置進程內存中的參數值;調用相應的observer生效之。
  4. log級別的debug_類參數,是直接設置參數並生效的(當然,依賴於ceph中log的實現)。
  5. Ceph使用過程中,很多動態更新的參數,其實並沒有真正的生效,修改參數時,返回的結果中有"unchangeable",或“not observed, change may require restart”這些字串的都沒生效。   ----------------------------------------------------更正:感謝各位指正,“參數設置返回‘unchangeable’的,都沒生效”確實有點問題,只能說可能沒生效。如果在MON、OSD、MDS運行中使用某個參數時,直接取的cct->_conf->{參數名},這種着實生效了;如果是在進程啓動時,就已經加載的,運行時直接使用,並沒有實時取_conf中參數的,是不生效的。所以,具體的參數還要具體分析,尤其是在調優或debug問題的時候,一定到代碼裏看看參數的使用,如果並沒真正生效,可能會誤以爲該參數沒有調優效果。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章