转自:https://www.ustack.com/blog/ceph-pg-fenlie/

1 pg分裂

Ceph作为一个scalable的分布式系统,集群规模会逐渐增大,为了保证数据分布的均匀性,好的做法是在扩容时根据集群规模适当调整pg_num和pgp_num的值。

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

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

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

2 实际测试

在集群处于高水位(磁盘占用均在80%以上)的情况下,主要是从实际操作的角度观察以下内容:

  • pg split是否可行
  • 对ceph集群和用户IO请求的影响
  • 如何降低操作带来的影响

2.1 测试环境

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

2.2 测试用例及结果

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

从实际的测试来看,增加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社区。

Ceph pg分裂流程及可行性分析的更多相关文章

  1. ceph PG数量调整/PG的状态说明

    优化: PG Number PG和PGP数量一定要根据OSD的数量进行调整,计算公式如下,但是最后算出的结果一定要接近或者等于一个2的指数.调整PGP不会引起PG内的对象的分裂,但是会引起PG的分布的 ...

  2. Ceph PG介绍及故障状态和修复

    1 PG介绍pg的全称是placement group,中文译为放置组,是用于放置object的一个载体,pg的创建是在创建ceph存储池的时候指定的,同时跟指定的副本数也有关系,比如是3副本的则会有 ...

  3. 利用火焰图分析ceph pg分布

    前言 性能优化大神Brendan Gregg发明了火焰图来定位性能问题,通过图表就可以发现问题出在哪里,通过svg矢量图来查看性能卡在哪个点,哪个操作占用的资源最多 在查看了原始数据后,这个分析的原理 ...

  4. 记一次ceph pg unfound处理过程

    今天检查ceph集群,发现有pg丢失,于是就有了本文~~~ 1.查看集群状态 [root@k8snode001 ~]# ceph health detail HEALTH_ERR 1/973013 o ...

  5. [转] 关于 Ceph PG

    本系列文章会深入研究 Ceph 以及 Ceph 和 OpenStack 的集成: (1)安装和部署 (2)Ceph RBD 接口和工具 (3)Ceph 物理和逻辑结构 (4)Ceph 的基础数据结构 ...

  6. ceph增加osd流程

    假如需要新增一个主机名:osd4 ip:192.168.0.110的OSD1.在osd4创建挂载目录及放置配置文件的目录 ssh 192.168.0.110 (这里是从mon主机ssh到osd4主机) ...

  7. Ceph源码解析:读写流程

    转载注明出处,整理也是需要功夫的,http://www.cnblogs.com/chenxianpao/p/5572859.html 一.OSD模块简介 1.1 消息封装:在OSD上发送和接收信息. ...

  8. Ceph中PG和PGP的区别

    http://www.zphj1987.com/2016/10/19/Ceph%E4%B8%ADPG%E5%92%8CPGP%E7%9A%84%E5%8C%BA%E5%88%AB/ 一.前言 首先来一 ...

  9. 分布式存储Ceph之PG状态详解

    https://www.jianshu.com/p/36c2d5682d87 1. PG介绍 继上次分享的<Ceph介绍及原理架构分享>,这次主要来分享Ceph中的PG各种状态详解,PG是 ...

随机推荐

  1. python函数回顾:dict()

    描述 dict() 函数用于创建一个字典. 语法 dict 语法: class dict(**kwarg) class dict(mapping, **kwarg) class dict(iterab ...

  2. java设计模式学习 ----- 工厂方法模式(Factory Method)

    工厂方法模式(Factory Method) 工厂方法模式分为三种:普通工厂模式.多个工厂方法模式.静态工厂方法模式 普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建. 关系图 ...

  3. 小程序发送 request请求失败 提示不在合法域名列表中的解决方法

    可以在小程序开发工具中设置不校验域名.

  4. 1.3 使用电脑测试MC20的电话语音功能

    需要准备的硬件 MC20开发板 1个 https://item.taobao.com/item.htm?id=562661881042 GSM/GPRS天线 1根 https://item.taoba ...

  5. 剑指offer 面试28题

    面试28题: 题目:对称的二叉树题: 请实现一个函数,用来判断一颗二叉树是不是对称的.注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的 解题思路: 可以定义一种遍历算法,先遍历右子节点再遍 ...

  6. Log level with log4j and Spark

    Log Level Usages OFF This is the most specific, which allows no logging at all FATAL This is the mos ...

  7. __init__和__new__

    一.__init__方法是什么 __init__方法通常用在初始化一个类实例的时候, class Person(object): """Silly Person" ...

  8. HackerRank - fibonacci-modified 【大数】

    思路 用PYTHON 或 JAVA 干掉 AC代码 a, b, n = map(int, input().split()) for i in range (2, n, 1) : temp = b b ...

  9. Loadrunder脚本篇——webservice接口测试(一)

    函数介绍 soap_request 函数执行一个SOAP请求 函数原型 int soap_request( const char *StepName, ExpectedResponse, URL, , ...

  10. 【转】dmesg 时间转换

    dmesg 时间转换 dmesg 输出的格式不易查看,可以通过命令进行转换. 记录如下: 时间查看: date -d "1970-01-01 UTC `echo "$(date + ...