IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Ceph pg分裂流程及可行性分析

    夏 艳发表于 2016-02-14 03:35:52
    love 0

    6626886_101538582163_2

    1 pg分裂

    PG作为Ceph数据流过程的中间层,它的数目pg_num和另一个值pgp_num(二者一般是相等的)直接影响集群的数据分布情况。pgp_num决定了多少pg会拿来存放数据,也就是说并不是所有创建出来的pg都会存放数据。理论上来说,它们的值越大,数据分布越均匀,但也意味着消耗更多的资源比如内存,所以生产环境中,它们的值不是随意设置的,社区建议按照一个计算公式pg_num=(osd num*100)/pool size来设置。

    Ceph作为一个scalable的分布式系统,集群规模会逐渐增大,为了保证数据分布的均匀性,好的做法是在扩容时根据集群规模适当调整pg_num和pgp_num的值。调整pg的数目会触发相关pg的分裂操作(但不会数据迁移,因为分裂后的pg会map到其父pg对应的osd集合),调整pgp_num会导致集群内发生数据迁移,算是一个比较重的操作,一般在实际生产环境鲜有人会进行这个操作,所以相关的代码逻辑没有经过大规模生产环境的验证。但当Ceph集群数据分布出现问题时,调整pg_num/pgp_num是一种解决办法。

    本文结合实际测试和理论分析两方面来证明pg split在生产环境是否可以操作?

    2 实际测试

    在集群处于高水位(磁盘占用均在80%以上)的情况下,主要是从实际操作的角度观察以下内容:
    1)pg split是否可行
    2)对ceph集群和用户IO请求的影响
    3)如何降低操作带来的影响

    2.1 测试环境
    目前所有测试均在一个测试环境上完成,其包括3个节点,共9个SSD OSDs,对应的Ceph版本是Firefly,OS是CentOS 7.1。测试之前会使用fio将集群写到高水位,在测试时启动fio产生较大的workload。

    2.2 测试用例及结果
    测试用例涵盖了目前我们可能出现pg增长的case。

    屏幕快照 2016-02-14 上午11.18.01

    从实际的测试来看,增加pg_num时,集群1~2s内即可恢复,增加pgp_num时,集群会出现数据迁移,并且IO会出现一些抖动恢复时间随着调整的幅度不同而不同。当调整的幅度较大时,还会导致osd出现down的情况。整体上从影响和可控性来说,针对pg_num和pgp_num的值一次增加一个对集群的影响最小,即集群状态恢复到正常所需的时间最短,并且对IO的影响也较小。

    3 代码分析

    PG split整个过程分为两步操作,修改pg_num和pgp_num。

    1)修改pg_num和pgp_num的操作实际上会转化为OSDmap的变化,Leader Monitor会负责更新map。

    //monitor收到命令

    Pipe::reader() /* read msgs from socket */

    ->Pipe::read_message()

    ->Message::decode_message()

    ->new MCommand() (MSG_MON_COMMAND)

    ->DispatchQueue::fast_dispatch();(Or queued)

    ->Messanger::ms_fast_dispatch();

     

    //monitor处理命令

    Monitor::handle_command()  Monitor.cc

    ->PaxosService::dispatch()

    ->OSDMonitor::preprocess_query() (MSG_MON_COMMAND)

    ->OSDMonitor::preprocess_command()

    ->OSDMonitor::prepare_update()

    ->OSDMonitor::prepare_command()

    ->OSDMonitor::prepare_command_impl(osd pool set)

    ->prepare_command_pool_set()

    Monitor的操作只是完成了ceph的pg_num/pgp_num的修改,并在Mon之间同步好新的OSD map,然后发布map。

    2) Monitor更新完map后,会发布新的map给相应的OSD

    monc 和mon通信,然后扔到osd的_dispatch()里面进行统一处理(OSD::_dispatch)

    -> OSD::handle_osd_map()

    -> OSD::consume_map()里面处理pg split.

    -> OSDService::expand_pg_num()来进一步处理拆分流程。

    -> OSDService::_maybe_split_pgid() //pg的拆分以及拆哪个都是通过spg_t pg_t,

    -> queue_null()把pg扔到peering队列。

    这一步主要是根据mask和seed检查得出哪些pg需要split。

    3)OSD开始处理peering队列里的事件

    ThreadPool::worker()

    -> _void_process() BatchWorkQueue

    ->_process() OSD::PeeringWQ处理pg的状态机

    ->OSD::process_peering_events()

    ->OSD::advance_pg()

    -> OSD::split_pgs()

    -> OSD::split_colls()

    -> OSD::split_into()

    collections就是一个objects的集合,其实就是一个pg。从上面的代码来看,pg split()操作的核心就是split_colls()和split_into()两个函数,下面接着它们。

    4)split_colls()函数

    该函数包括两步:创建collection和分裂collection

    A)create_collection() (OP_MKCOLL)

    OpWQ::store->_do_op() (FileStore::_do_op)

    ->FileStore::_do_transactions()

    ->FileStore::_create_collection()

    -> ::mkdir()   //创建文件夹

    -> init_index()   //为目录创建索引

    -> _set_replay_guard()

     

    B) split_collection() (OP_SPLIT_COLLECTION2)

    OpWQ::store->_do_op() (FileStore::_do_op)

    ->FileStore::_do_transactions()

    ->FileStore::_split_collection()

    ->FileStore::_set_global_replay_guard()

    ->sync_filesystem(“/var/lib/ceph/osd/ceph-*/”)

    ->chain_fsetxattr(fd,GLOBAL_REPLAY_GUARD_XATTR)

    ->::fsync(fd)

    ->FileStore::_set_replay_guard(srcfd)

    ->::fsync(fd)

    ->object_map->sync(hoid, &spos)

    ->chain_fsetxattr(fd,REPLAY_GUARD_XATTR)//record what we did

    ->::fsync(fd);

    ->FileStore::_set_replay_guard(dstfd)

    ->from->split();

     

    HashIndex::_split()

    HashIndex::col_split_level()

    a)找出需要移动的sub目录以及object

    b)create相应的目录

    c)srccol.start_col_split();

    InProgressOp op_tag(InProgressOp::COL_SPLIT, path);

    op_tag.encode(bl);

    add_attr_path();

    fsync_dir();

    d)dstcol.start_col_split();

    e)move_subdir(from, to, path);

    f)move_object(from, to, path);

    g)set_info(to_info)/set_info(from_info);

    h)end_split_or_merge(path);

     

    5)split_into()

    A)更新pg以及pg log相关信息包括,last_complete,last_update, last_user_version, log_tail

    /**

    * map a pg to its acting set as well as its up set. You must use

    * the acting set for data mapping purposes, but some users will

    * also find the up set useful for things like deciding what to

    * set as pg_temp.

    * Each of these pointers must be non-NULL.

    */

    B)pg_to_up_acting_osds()//设置active osd set和up osd set,并选出对应的primary

    -> _pg_to_osds() //执行crush算法,算出的osd列表作为raw osd,并找出primary

    -> crush->find_rule()

    -> crush->do_rule()

    -> _apply_primary_affinity()

    -> _raw_to_up_osds() //从raw中找出up的osd集合

    -> _apply_primary_affinity() //处理设置了primary affinity的osd

    -> _get_temp_osds()

    C)init_primary_up_acting()//

    D)OSDMap::calc_pg_role() //根据acting osd集合,计算出pg的角色

    E)split_ops(child)

    -> split_replay_queue()

    -> osd->dequeue_pg(),  将pg存入waiting_for_peered队列中,

    -> OSD::split_list(waiting_for_peered)

    -> OSD::split_list(waiting_for_map)

    如果m_seed & mask,则将request插入到child中执行。

    4 结论

    总的来说,在firefly版本通过小幅度增加pg_num和pgp_num,进行pg 分裂操作是可行的。修改pg_num时,会通过计算得出哪些pg需要split,创建相应的collection并在OSD内移动数据。修改pgp_num时,会导致pg执行Crush算法,数据会在整个集群范围内进行平衡。

    关于作者:

    乔建峰,UnitedStack有云存储Team的PTL,四年的传统存储驱动程序开发经验,熟悉SCSI、FC、iSCSI、iSER协议,目前主要专注于Ceph、OpenStack社区。

     



沪ICP备19023445号-2号
友情链接