ceph QA IO 模型

背景:

Ceph QA 中使用了rados model 工具做端到端的检验,写数据下去,在读数据上来。对比osd 的数据跟客户端的数据是否一致。定位qa 数据问题绕不开rados model 流程。

使用方法:

ceph_test_rados --max-ops 4000 --objects 1024 --max-in-flight 64 --size 4000000 --min-stride-size 400000 --max-stride-size 800000 --max-seconds 3600 --op snap_remove 50 --op snap_create 50 --op rollback 50 --op setattr 25 --op read 100 --op clone_create 50 --op write 50 --op write_excl 50 --op flatten 50 --op rmattr 25 --op delete 50 --pool rbd

模块初始化:

初始化 RadosTestContext context;初始化rados 链接;

初始化WeightedTestGenerator;继承于TestOpGenerator,可以根据传入的各种op的权重生成op(gen_op)。

Loop context.loop(&gen);主要逻辑;

根据op权重,循环产生op,首先写op初始化对象。随机生成一个数,如果比op 累计权重小,gen_op。如果超过总的op个数结束。如果超过最大inflight的op个数,等待op 完成。

gen_op,会从RadosTestContext的oid_not_in_use中选出一个oid,生成op,然后从oid_not_in_use中删除该oid。等op完成后oid_not_in_use中再插入该oid。

单线程异步读写。

test op 类型:

TEST_OP_READ
TEST_OP_WRITE
TEST_OP_WRITE_EXCL
TEST_OP_WRITESAME
TEST_OP_DELETE
TEST_OP_SNAP_CREATE
TEST_OP_SNAP_REMOVE
TEST_OP_ROLLBACK
TEST_OP_CLONE_CREATE
TEST_OP_SETATTR
TEST_OP_RMATTR
TEST_OP_WATCH
TEST_OP_XCOPY
TEST_OP_APPEND
TEST_OP_APPEND_EXCL
TEST_OP_FLATTEN

重要的数据结构:

class RadosTestContext

1. map<uint64_t, CloneContext> clones;

CloneContext:

seq //最大快照

snaps //记录clone的快照ids

current_snap

2. map<uint64_t, Objects> pool_obj_cont; //clone : 对象,各个clone对象的各个snaps 对象

class Objects {

public:

map<int, map<string, ObjectDesc>> snaps; // 该map snapid : <oid_name, 对象>

bool is_cross_pool;

int cross_layers;

Objects(): is_cross_pool(false), cross_layers(0)

{}

};

3. cloned_snaps 创建clone的时候插入,保证snap remove的时候不选中,删除该snap;

clone 对象存储分数据和元数据,元数据在clones 中保存。

数据在pool_obj_cont中保存。

 

class ContDesc //内容的描述信息

int objnum; //对象id,跟seq num 一样

int cursnap; //当前快照

unsigned seqnum; //seq num,用于生成数据。

std::string prefix; //对象前缀

std::string oid; //对象名

class ObjectDesc

对象描述,保存ContDesc和数据生成器。 有生成数据和check 数据一致性的接口。

std::list<std::pair<ceph::shared_ptr<ContentsGenerator>, ContDesc>> layers; //数据层

std::map<std::string, ContDesc> attrs; //attrs 信息

bufferlist header; //header 数据信息 也是一种元数据,以"_"开头的。

class CloneContext 在建clone的时候会建一个对象。继承父的parents再加上本次的父快照和自己。

uint64_t seq;

map<int, uint64_t> snaps; //记录了本地快照和快照的映射关系

int current_snap; //本地当前快照id,相当于head 对象

class ContState //layer 遍历数据的时候,用的

interval_set<uint64_t> ranges; //数据有效范围,通过seqnum 产生

const uint64_t size;

public:

ContDesc cont; //内容描述

ceph::shared_ptr<ContentsGenerator> gen; //数据生成器

ContentsGenerator::iterator iter; //数据生成器 迭代器

std::list<std::pair<std::list<ContState>::iterator, StackState>> stack;

struct StackState

{

const uint64_t next; //下一段 有效数据的 start

const uint64_t size;

};

//主要用于读数据时,遇到空洞,需要向下层layer寻找有效数据时存放stack 使用。

核心数据操作:

写操作,如何生成数据?

读操作,怎么验证数据一致性?

数据生成器:

AppendGenerator append 数据生成器

AlignmentGenerator 对齐io的数据生成器

VarLenGenerator 非对齐io的数据生成器

VarLenZeroGenerator 产生0 的数据生成器

 append op 调用AppendGenerator生成数据,如果需要对齐则随机调用AlignmentGenerator 和 VarLenZeroGenerator生成数据。如果不需要对齐写则调用VarLenGenerator 和 VarLenZeroGenerator 生成数据。

生成数据过程:

cont_gen->get_ranges_map 生成数据

1.根据传入的ContDesc的seqnum,生成数据范围map <offset, len>。每次非读op, seqnum都会自增。

2.根据传入的max_stride_size 和 min_stride_size,生成多段位于max_stride_size 和 min_stride_size之间的数据范围。间隔加入有效数据范围(奇数次加入,偶数次不加入)。

ContentsGenerator::iterator gen_pos = cont_gen->get_iterator;

数据生成器的核心是伪随机数生成器,minstd_rand0 (minstd_rand0 线性同余法 : xi+1=(axi+c) mod m)。是std提供的配置好a,c,m的随机数生成器。只要提供第一个种子,后续数字都是确定的。这样也就提供了可以重复产生相同数据的基础。只需要保存种子,就可以每次产生相同的数据。

gen_pos.seek 等游标操作,基本都是调用rand() 向后移动。

context->update_object 更新数据,保存ContDesc 和 gen (数据生成器)到ObjectDesc的layers中。

写数据只会用到最新一层数据。写数据的时候会把父的数据直接插入自己的layers中。

写数据也是append only 模式,新插入一层数据layer,覆盖旧的部分数据。

生成器每次生成是有一个最大长度,该长度也是根据seqnum 生成的。

 

数据生成器,怎么实现append 数据操作呢?

AppendGenertor 与其他数据生成器不同的是,会记录一个offset。每个get_ranges_map 是从offset 开始算起的。

数据读取过程:

读数据则可能用到老数据(比如最新一层数据,某些offset len是空洞)。

 

可以参考:get_data() 接口;

ObjectDesc::gen_data:现使用的offset/len 都是0,就是获取该对象的 所有数据。

1. 初始化ObjectDesc::iterator

1.1 把 obj.layers 组成ContState 添加 到iterator 的layers。

1.2 初始化current指针为第一层layer。

1.3 调整stack adjust_stack

2. 获取数据 gen_bl_advance

读取数据时,以上层layer 数据为准,上层数据覆盖底层数据。

具体怎么实现呢?

每层数据都有一个有效区以intervals的形式存储。读的时候首先读最上层layer的数据,当数据不在有效区的时候,会尝试从下层数据读取数据。一种极端的情况是所有层在该位置都是空洞,则该位置填充0。

这里介绍几个基本概念,pos 表示当前读取数据的位置;next 表示本层layer 下一段有效数据的起始位置。cur_valid_till 表示本层数据如果pos 超过该位置,数据就不准确了,需要调整stack(比如pop stack 元素等);

下面介绍下正常情况,以上图为例:

当toplayer 在pos的位置是空洞时,把该层数据压入栈stack。如果下层数据也是空洞,满足一定条件时,也需要把该层数据压入stack(为什么是满足一定条件呢?并不是所有的layer 都需要压入stack,只有该层数据不包含pos,且next < 下一层数据的next的时候可以入stack)。比如找到了layer5 ,该layer 刚好有pos的数据,读取这一段数据。

什么时候出栈呢?

当pos 大于cur_valid_till的时候,表述该层数据不准确了,需要读取stack中下层数据。这时候就会把stack 顶端的layer 数据出栈。调整current 指针,然后读取本层数据的内容。

以此类推,每次遇到空洞都会设计到stack 操作,以保证读取正确layer的数据。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值