OSD作为Ceph的核心组件,承担着数据的存储,复制,恢复等功能。本文基于Luminous版本通浅析OSD的初始化流程,了解OSD的主要功能、主要数据结构和其他组件的配合情况等。OSD守护进程的启动流程在ceph_osd.cc中的主函数中,具体和OSD相关的初始化主要集中在下面几个函数:
后文对这三个函数的主要流程进行分析。
pre_init在OSD实例被创建后调用,主要的目的有两个:
test_mount_in_use检查OSD路径是否已经被使用。如果已经被使用,返回-EBUSY。md_config_t,并通过add_observer加入被观察的key,当这个key的value发生改变时,可以及时观测到。cct->_conf->add_observer(this);
在pre_init后进入init流程,init作为初始化的主要函数,涉及点比较多。主要从如下三个方面入手:
SafeTimer进行初始化,SafeTimer主要用于周期性执行tick线程;后面通过add_event_after启动tick线程。tick_timer.init();
tick_timer_without_osd_lock.init();
service.recovery_request_timer.init();
service.recovery_sleep_timer.init();
mount接口进行挂载。getloadavg获取负载信息,在scrub中需要检测是否负载超出限制。validate_hobject_key进行测试。OSD::read_superblock读取元数据,decode到osd对应的superblock成员。osd_compat进行一些特性方面的检查。snap mapper对象是否存在,不存在的话新建。snap mapper对象保存了对象和对象快照信息。**disk perf和net perf对象的存在,不存在的话新建。disk perf和net perf对象保存了磁盘和网络相关的信息。ClassHandler,用来管理动态链接库。get_map获取superblock记录的epoch对应的OSDMap。具体分析见后文【对OSDMap的处理】。OSD::check_osdmap_features检查获取到的OSDMap的特性。OSD::create_recoverystate_perf创建recovery的pref,然后加入perfcounters_collection中,用来追踪recovery阶段的性能。OSD::clear_temp_objects 遍历所有pg的object,清除OSD down之前的曾经的临时对象。sharded wq中初始化OSDMap的引用。sharded wq是线程池osd_op_tp对应的工作队列,内含多个shard对应一组线程负责一个pg。OSD::create_logger。client_messenger和cluster_messenger。前者负责集群外通信,后者负责集群内通信。任何组件想要通讯都需要继承Dispatcher,加入messenger中并复写相关函数。client_messenger->add_dispatcher_head(this);
cluster_messenger->add_dispatcher_head(this);
Dispatcher加入到心跳messenger中,这些messenger对应的群内外的前后心跳。hb_front_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
objecter加入到objecter_messenger。MonClient::init初始化monclient,任何和monitor的通讯需要monclient。mgrclient,并加入client_messenger中。mgrc.init();
client_messenger->add_dispatcher_head(&mgrc);
logclient,logclient和monitor交互,保证了节点间日志的一致性。monc->set_log_client(&log_client);
update_log_config();
OSDService,设置OSDMap和superblock等成员。在OSDservice的初始化过程中,初始化或开启了一些timer和finisher。service.init();
service.publish_map(osdmap);
service.publish_superblock(superblock);
service.max_oldest_map = superblock.oldest_map;
OSD::set_disk_tp_priority设置线程池优先级。peering_tp.start();
osd_op_tp.start();
remove_tp.start();
recovery_tp.start();
command_tp.start();
set_disk_tp_priority();
heartbeat_thread.create()开启心跳。MonClient::authenticate进行monclient的鉴权。crush可能需要更新。
OSD::update_crush_device_class更新设备类型,该功能在Luminous中引入,可以区分osd是hdd/ssd。读取osd挂载目录的crush_device_class来决定设备类型,没有读取到读默认值,调用mon命令进行应用。OSD::update_crush_location()更新crush。更新OSD的weight和location,调用mon命令来创建或者移动bucket。OSDService::final_init()开启objecter。OSD::consume_map()。距离分析可以参考另外一篇blog。MMonSubscribe类型的消息。monc->sub_want("osd_pg_creates", last_pg_create_epoch, 0);
monc->sub_want("mgrmap", 0, 0);
monc->renew_subs();
OSD::start_boot进入boot流程,关于OSD的boot和状态转化,可以参考另一篇blog。在初始化过程中,有两个地方进行了OSDMap的获取:
superblock记录的epoch对应的OSDMap。获取的调用链为:
OSD::get_map-->OSDService::get_map-->OSDService::try_get_map
OSDMapRef OSDService::try_get_map(epoch_t epoch)
{
Mutex::Locker l(map_cache_lock);
OSDMapRef retval = map_cache.lookup(epoch);
if (retval) {
dout(30) << "get_map " << epoch << " -cached" << dendl;
if (logger) {
logger->inc(l_osd_map_cache_hit);
}
return retval;
}
...
OSDMap *map = new OSDMap;
if (epoch > 0) {
dout(20) << "get_map " << epoch << " - loading and decoding " << map << dendl;
bufferlist bl;
if (!_get_map_bl(epoch, bl) || bl.length() == 0) {
derr << "failed to load OSD map for epoch " << epoch << ", got " << bl.length() << " bytes" << dendl;
delete map;
return OSDMapRef();
}
map->decode(bl);
} else {
dout(20) << "get_map " << epoch << " - return initial " << map << dendl;
}
// 加入map_cache缓存
return _add_map(map);
}
try_get_map中使用了map_cache_lock保护,该所用于保护从cache中获取map的一致性。map_cache中查找,如果未找到再通过OSDService::_get_map_bl将map从map_bl_cache(和前面的MapCache不同)中读取或从磁盘中读取并加入到缓存中。bool OSDService::_get_map_bl(epoch_t e, bufferlist& bl)
{
// * 先检查一下cache中有没有
bool found = map_bl_cache.lookup(e, &bl);
if (found) {
if (logger)
logger->inc(l_osd_map_bl_cache_hit);
return true;
}
if (logger)
logger->inc(l_osd_map_bl_cache_miss);
found = store->read(coll_t::meta(),
OSD::get_osdmap_pobject_name(e), 0, 0, bl,
CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) >= 0;
// * 加入map_cache_bl缓存
if (found) {
_add_map_bl(e, bl);
}
return found;
}
前文提到。调用OSD::load_pgs对OSD上已有的pg进行加载:
list_collections从硬盘中读取PG(current目录下),并遍历。**OSD::load_pgs**中有一个优化点,可以通过多线程来加速加载。for (vector<coll_t>::iterator it = ls.begin();
it != ls.end();
++it) {
spg_t pgid;
//对PGTemp和需要清理的pg进行清理
//recursive_remove_collection函数主要进行了一下几个删除步骤
// 1. 遍历PG对应的Objects,删除对应的Snap
// 2. 遍历PG对应的Objects,删除Object
// 3. 删除PG对应的coll_t
if (it->is_temp(&pgid) ||
(it->is_pg(&pgid) && PG::_has_removal_flag(store, pgid))) {
dout(10) << "load_pgs " << *it << " clearing temp" << dendl;
recursive_remove_collection(cct, store, pgid, *it);
continue;
}
...
// 获取OSD Down前最后的pg对应的OSDMap epoch
epoch_t map_epoch = 0;
// 从Omap对象中获取
int r = PG::peek_map_epoch(store, pgid, &map_epoch);
...
if (map_epoch > 0) {
OSDMapRef pgosdmap = service.try_get_map(map_epoch);
...
//如果获取到了PG对应的OSMap
pg = _open_lock_pg(pgosdmap, pgid);
} else {
//如果没有,就用之前获取的OSDMap
pg = _open_lock_pg(osdmap, pgid);
}
...
//读取pg状态和pg log
pg->read_state(store);
//pg不存在?判断依据是info的history中created_epoch为0
if (pg->dne()) {
// 删除pg相关操作
...
}
...
PG::RecoveryCtx rctx(0, 0, 0, 0, 0, 0);
// 进入Reset状态
pg->handle_loaded(&rctx);
}
Q:PG为什么会不存在?
A:可能是在加载的过程中防止PG被移除
_open_lock_pg,这一步获取了PG对象且对对象进行了加锁,下面来分析一下代码。PG *OSD::_open_lock_pg(
OSDMapRef createmap,
spg_t pgid, bool no_lockdep_check)
{
assert(osd_lock.is_locked());
//构造PG
PG* pg = _make_pg(createmap, pgid);
{
//读取PGMap的写锁,因为要修改PGMap
RWLock::WLocker l(pg_map_lock);
// PG上锁
pg->lock(no_lockdep_check);
pg_map[pgid] = pg;
// PG的引用计数+1
pg->get("PGMap"); // because it‘s in pg_map
// 维护pg_epochs和pg_epoch结构
service.pg_add_epoch(pg->info.pgid, createmap->get_epoch());
}
return pg;
}
Q:在OSD启动的过程中,已经通过superblock对应的epoch尝试获取了OSDMap,为什么还需要在加载OSD的PG时,获取PG对应的OSDMap?
Q:什么时候应该上PG锁?
A:这里需要拷贝复制,为了保证前后一致性,需要上锁
Q:pg的引用计数什么时候增加?
A:类似只能指针的原理,pg作为等号右边的值,给别的变量拷贝赋值了,引用计数+1
PG::read_state,主要功能是读取pg log和pg statevoid PG::read_state(ObjectStore *store, bufferlist &bl)
{
// 通过PG::read_info读取PG状态
// PG的元数据信息保存在一个object的omap中
// 具体分析过程
int r = read_info(store, pg_id, coll, bl, info, past_intervals,
info_struct_v);
assert(r >= 0);
...
ostringstream oss;
pg_log.read_log_and_missing(
store,
coll,
info_struct_v < 8 ? coll_t::meta() : coll,
ghobject_t(info_struct_v < 8 ? OSD::make_pg_log_oid(pg_id) : pgmeta_oid),
info,
force_rebuild_missing,
oss,
cct->_conf->osd_ignore_stale_divergent_priors,
cct->_conf->osd_debug_verify_missing_on_start);
if (oss.tellp())
osd->clog->error() << oss.str();
if (force_rebuild_missing) {
dout(10) << __func__ << " forced rebuild of missing got "
<< pg_log.get_missing()
<< dendl;
}
// log any weirdness
log_weirdness();
}
在OSD::final_init中注册admin socket命令,这些命令格式为ceph daemon osd.X xxx,比如:
ceph daemon osd.0 dump_disk_perf
了解OSD的启动流程,对理解整个OSD模块很有帮助。在启动流程中基本涵盖了OSD工作流程中的各种组件和结构,因为篇幅所限很多地方没有展开,有些地方也存在一些疑问。希望后续能对内容继续深入,逐个击破。
原文:https://www.cnblogs.com/wujiajun1997/p/14916729.html