Ceph集群在系统扩容时触发rebalance的机制分析
一、概述。
Ceph系统扩容无非就是向集群中添加新的存储节点来扩充当前Ceph集群的容量。当集群中有新的存储节点加入后,整个Ceph集群中的OSDs节点数目就会发生变化(即:OSDMap发生变化),CRUSHMap也会发生变化(比如:新加入一个机柜到Ceph集群)。因此当前集群中的部分PGs就会根据最新的CRUSHMap和OSDMap重新计算primary和replicas节点,使得新加入集群的OSDs能够处理PGs的主副本,进而使得Ceph集群中的数据能够均匀的分布在整个Ceph集群中。
二、rebalance触发机制分析。
1、OSDMap变化解析。
当有新的OSD节点加入到Ceph集群时,需要从OSD的main()函数入手分析OSD是如何加入到Ceph集群中也就是说OSD节点如何添加到OSDMap中的。Ceph源代码调用流程如下:
ceph_osd.cc
|__OSD::init()
|__OSD::start_boot()
|__MonClient::get_version()向Monitor发送MMonGetVersion消息来获取当前OSDMap的最新和最旧的版本号。调用该函数是传入一个回调函数结构体C_OSD_GetVersion,在获取到OSDMap最新和最旧的版本号时调用该结构体中的void finish()方法,在该方法中调用OSD::_maybe_boot()函数做进一步处理。
OSD::_maybe_boot()
|__OSD::_send_boot()创建MOSDBoot消息且将该消息发送给Monitor节点。
Monitor节点上负责接收和处理Message的函数是Monitor::ms_dispatch()函数,该函数获取从其它节点上发送过来的消息。Monitor::ms_dispatch()函数处理流程如下:
Monitor::ms_dispatch()
|__Monitor::_ms_dispatch()
|__Monitor::dispatch_op()该函数是处理消息的路由函数,根据消息类型决定具体的处理函数。对于MOSDBoot消息由PaxosService::dispatch()函数做进一步处理。在该函数处理过程中会依次调用PaxosService::preprocess_query()方法和PaxosService::prepare_update()方法,这两个方法在PaxosService类中是虚函数。由于Monitor节点上负责处理OSDMap的类是OSDMonitor类,该类继承自PaxosService类,在OSDMonitor类中实现了具体处理流程。由于OSD节点是新加入的因此当前OSDMap上没有该节点的信息,因此OSDMonitor::preprocess_query()函数返回false,需要调用OSDMonitor::prepare_update()函数做进一步处理。
Monitor::dispatch_op()
|__PaxosService::dispatch()
|__OSDMonitor::preprocess_query()
|__OSDMonitor::prepare_update()
|__OSDMonitor::prepare_boot()在该函数中创建一个OSDMap的pending_inc结构,之后调用wait_for_finished_proposal()函数来申请一个提案,在Monitor集群中达成一致,在该提案达成一致后回调C_Booted::_finish()函数,该函数进一步调用OSDMonitor::_booted()函数。OSDMonitor::_booted()函数调用send_lastest()函数将当前最新的OSDMap发送给新加入的OSD节点。
2、OSD节点处理OSDMap过程解析。
OSD节点处理Monitor节点发送过来的OSDMap消息的处理流程如下:
OSD::_dispatch()
|__OSD::handle_osd_map()
|__OSD::consume_map()
|__PG::queue_null()
|__PG::queue_peering_event()
OSD::_dispatch()函数是消息处理的路由函数,根据消息类型调用具体的处理函数。对于处理Monitor节点发送过来的OSDMap消息,则由handle_osd_map()函数进行处理。在handle_osd_map()函数中首先对OSDMap消息进行解析且得到OSDMap且保存,之后调用consume_map()做进一步处理。在consume_map()函数中遍历该OSD节点上已有的PGs且统计出primary/replicas/stray的数量,其次唤醒等待OSDMap的PGs,最后遍历当前OSD节点上所有PGs且调用PG::queue_null()函数将OSD节点上所有PGs添加到peering队列中。