【Ceph】Ceph Client

librados

librados是RADOS对象存储系统访问的接口层,它提供了pool的创建、删除、对象的创建、删除、读写等基本操作接口

在最上层是RadosClient,它是librados的核心管理类,处理整个RADOS系统层面以及pool层面的管理。类IoctxImpl实现单个pool 层的对象读写等操作

OSDC模块实现了请求的封装和通过网络模块发送请求的逻辑,其核心类Objecter完成对象的地址计算、消息的发送等工作

librados主要接口:

1.集群句柄创建

librados::Rados对象是用来操纵ceph集群的句柄,使用init来创建RadosClient,之后读取指定的ceph配置文件,获取monitor的 ip和端口号。RadosClient里面有与monitor通信的MonClient 和用于与OSD通信的Messenger。

init a.创建一个RadosClient对象 reinterpret_cast<rados_t>(new librados::RadosClient(cct));

2.集群连接

初始化集群句柄之后,就可以使用这个句柄来连接集群了,RadosClient::connect完成了连接操作:

a)调用monclient.build_initial_monmap,从配置文件中检查是否有初始化的monito的地址信息

b)创建网络通信模块messenger,并设置相关的Policy信息

c)创建Objecter对象并初始化

d)调用monclient.init()函数初始化monclient

e)Timer定时器初始化,Finisher对象初始化

3.IO上下文环境初始化

使用句柄创建好存储池后,还需要创建与存储池相关联的IO上下文句柄

rados.ioctx_create(pool_name, io_ctx)

4.对象读写

创建对象并写入数据:

io_ctx.create_full(object_name, bl)

读取对象中的数据到bufferlist中。对象读取有同步读取和异步读取两种接口,分别是io_ctx.read和io_ctx.aio_read

同步读取
...
io_ctx.read(object_name, read_bl, read_len, 0);
...

异步读取(需要指定完成读取数据后的回调,用于检查读取是否完成)
...
librados::AioCompletion *read_completion = librados::Rados::aio_create_completion();
io_ctx.aio_read(object_name, read_completion, &read_buff, read_len, 0);

read_completion->wait_for_complete();
同时,还要获取返回值,得到读取对象的字节数:

...

5.IO上下文关闭

io_ctx.close()

6.集群句柄关闭

rados.shutdown()

OSDC

OSDC是客户端底层的模块,其核心在于封装操作数据,计算对象地址、发送请求和处理超时。 OSDC是osd client的意思,该目录下封装了各类操作osd的方法。objecter最终会创建一个操作,并且把它提交。 可以从代码中看出,检查pool是否已经存在,是使用osdmap, 也就是说创建的pool信息是至少有部分在osdmap中的。最后会交由 MonClient发送消息给Monitor,走的是PaxosService。如果要继续追溯,前提条件是对Ceph的消息发送机制熟悉,这样才能在 Monitor端接收消息的地方,继续追踪下去。

Objecter:

RadosClient::connect: set_balanced_budget init set_client_incarnation start

RadosClient::shutdown Objecter: shutdown

RadosClient::watch_flush objecter: linger_callback_flush

RadosClient::wait_for_osdmap objecter: get_osdmap_read put_osdmap_read

RadosClient::wait_for_latest_osdmap objecter: wait_for_latest_osdmap

RadosClient::get_pool_stats objecter: get_pool_stats

RadosClient::get_fs_stats objecter: get_fs_stats

RadosClient::create_pool objecter create_pool

RadosClient::delete_pool objecter delete_pool

RadosClient::blacklist_self objecter blacklist_self

RadosClient::osd_command objecter osd_command

RadosClient::pg_command objecter pg_command

cls

cls是ceph的一个扩展模块,它允许自定义对象的操作接口和实现方法,为用户提供了一种比较方便的接口扩展方式。

#define CLS_NAME(name) \
int __cls_name__## name = 0; \
const char *__cls_name = #name;

int __cls_name__name = 0;
const char *__cls_name = "name";

cls提供了服务端接口和客户端接口,以共享库的形式提供接口。编译后生成libcls_xx.so和libcls_client_xx.so,服务端的 接口主要供OSD调用,在OSD初始化的时候会注册共享库中指定要注册的模块和以及C或者C++接口,然后以名称和函数指针的形式存在。 当客户端需要调用服务端提供的接口时,必须先实例化一个IoCtx对象,通过IoCtx类提供的exec方法去调用服务端提供的接口。比如 cls中的hello这个例子,在cls_hello.cc中的__cls_init()这个初始化函数中,注册了hello这个模块和say_hello这个函数, 注册完后,客户端就能够实用IoCtx实例的exec方法来调用这个模块所提供的方法了。

通过这种方式,提高了OSD模块的可扩展性,要想实现操作rados对象或者rbd块设备的扩展功能,只需要在cls中添加相应的实现即可。 这样,就不必改动OSD模块原有的功能,同时也降低了耦合度。

cls中主要的类: 服务端: ClassHandler ClassHandler这个类用来管理所有的扩展模块,其中包含了两个主要的内部类ClassData和ClassMethod以及加载和注册扩展库模块 的方法。

客户端: 要调用cls提供的扩展模块的方法很简便,在使用librados库时初始化IoCtx对象,然后执行IoCtx::exec方法即可。

librbd

librbd是ceph对外提供的块存储接口的抽象,可以供qemu虚拟机, fio测试程序的rbd engine等程序使用。它提供c和c++两种接口, 对于c++,最主要的两个类就是RBD和Image。RBD主要负责创建,删除,克隆镜像等操作, 而Image类负责镜像的读写等操作。

  {
    librbd::RBD rbd;
    librbd::Image image;

    int order = 22;
    unsigned long long size = 512 * (1<<20lu);
    const char *image_name = "hello_format2.1.img";

    //int ret = rbd.create(io_ctx, image_name, size, &order);
    int ret = rbd.create3(io_ctx, image_name, size, 3, &order, 0, 0);
    if (ret < 0)
    {
      std::cerr << "failed create image: " << image_name << std::endl;
      goto out;
    }

    ret = rbd.open(io_ctx, image, image_name);
    if (ret < 0)
    {
      std::cerr << "rbd: failed to open image" << std::endl;
      goto out;
    }
    std::string image_data("hello_world!");
    bufferlist outbl;
    outbl.append(image_data);
    librbd::RBD::AioCompletion *write_comp = new librbd::RBD::AioCompletion(NULL, NULL);
    if (!outbl.is_zero())
    {
      image.aio_write(0, outbl.length(), outbl, write_comp);
    }

    std::vector<std::string> image_list;
    ret = rbd.list(io_ctx, image_list);
    if (ret < 0)
    {
      std::cerr << "failed to list image" << std::endl;
      goto out;
    }
    std::cout << "all images:" << std::endl;
    for (auto& item : image_list)
    {
      std::cout << item << " ";
    }
    std::cout << std::endl;
  }

RBD元数据:

rbd_directory

  • 映射镜像名称至id,反之亦然

rbd_children

  • pool中克隆的列表,通过parent来索引

rbd_id.$image_name

  • 内部id,只需要使用用户指定的镜像名称定位

rbd_header.$image_name

  • 每个镜像的元数据

RBD数据:

rbd_data.*objects

  • 基于在镜像中偏移来命名

  • 不存在从哪开始

  • 对象中的纯数据

  • rados负责快照

  • 通常是稀疏的

RBD的相关对象: rbd_directory: 每个pool都有该对象,保存该pool下所有RBD的目录信息 format1时:rbd_directory文件中保存的是该pool下所有的RBD信息 format2时:可以用rados listomapvals rbd_directory列出RBD信息

Format1: Hammer和Hammer之前默认都是这种格式,rbd_default_features=3 Format2: Jewel及之后的版本默认的格式, rbd_default_features=61

rbd镜像的features详情请看后面一节。

image_name --> oid --> fd_cache(the collection of fd) --> lfn_open(*path->path())

librbd简介 RBD image internal in Ceph Ceph librbd create image Ceph librbd flatten rbd块设备驱动揭秘 QEMU QoS特性及原理分析和Librbd QoS定义 Object的attr和omap操作

在ceph中,不管是块设备、对象存储还是文件存储,最后都转化成了底层的Object。这个Object包含有三个元素:data、xattr和omap。 data是保存对象的数据;xattr是保存对象的扩展属性,每个对象文件都可以设置文件的扩展属性

块存储设备的元数据管理: 对于块存储,在存储过程中主要是块的相关信息。块的元数据管理,创建一个块设备后会创建一个名为.rbd的元数据文件,它不是 用来存储数据的,而是用来保存元数据信息的。NOTE: ?真实的元数据信息不回保存在这个文件中,而是保存在这个文件的xattr或者omap中?

rbd_directory对象保存了所有创建的块设备名字。

format1: 写镜像时使用librados的write接口 format为1时,striping不可用,features为0

uint64_t features = old_format ? 0 : cct->_conf->rbd_default_features

数据结构: ceph_file_layout:

format2: 写镜像时使用cls扩展模块

Create: format1:rbd.create format2:rbd.create3

rbd扩展模块: 客户端编码,服务端解码:这样设计是为了让客户端传递参数时可以将多个参数编码到一个bufferlist中,然后传递给服务端,服务端 再将输入的bufferlist解码获得客户端传递的参数。还有个好处是保持了服务端接口的一致性,所有服务端的接口都设计为接收3个参数 (method上下文,传入参数和传出参数)

Hammer版rbd克隆问题 rbd克隆问题修复

RBD import Test 1.创建池后

__head_00000000__12c  rbd\udirectory__head_30A98C1C__12c

2.导入rbd镜像后

a.img.rbd__head_D43CB243__12c  rb.0.1d5c19.74b0dc51.000000000000__head_84EDD092__12c  rbd\udirectory__head_30A98C1C__12c
__head_00000000__12c           rb.0.1d5c19.74b0dc51.000000000001__head_06A3542D__12c

3.新增加的rados对象为

a.img.rbd
rb.0.1d5c19.74b0dc51.000000000000
rb.0.1d5c19.74b0dc51.000000000001

4.a.img.rbd__head_D43CB243__12c中的内容

[root@cephnode2 300.0_head]# hexdump -C a.img.rbd__head_D43CB243__12c
00000000  3c 3c 3c 20 52 61 64 6f  73 20 42 6c 6f 63 6b 20  |<<< Rados Block |
00000010  44 65 76 69 63 65 20 49  6d 61 67 65 20 3e 3e 3e  |Device Image >>>|
00000020  0a 00 00 00 00 00 00 00  72 62 2e 30 2e 31 64 35  |........rb.0.1d5|
00000030  63 31 39 2e 37 34 62 30  64 63 35 31 00 00 00 00  |c19.74b0dc51....|
00000040  52 42 44 00 30 30 31 2e  30 30 35 00 16 00 00 00  |RBD.001.005.....|
00000050  50 ca 7f 00 00 00 00 00  00 00 00 00 00 00 00 00  |P...............|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000070

5.rbd_directory中的内容为

[root@cephnode2 300.0_head]# hexdump  -C rbd\\udirectory__head_30A98C1C__12c
00000000  00 00 00 00 01 00 00 00  05 00 00 00 61 2e 69 6d  |............a.im|
00000010  67 00 00 00 00                                    |g....|
00000015

librbd主要数据结构

1.AioCompletion

AioCompletion是单个rbd I / O请求的全部completion。它可能会包含多个AioObjectRequest, 每个都会请求单个object。

单独的一个请求重试在更低一层已经被处理,所以所有的AioCompletion关心的只是未完成的请求数量。 期望的请求数量应该在请求之前使用set_request_count进行初始化。这确保了completion不会在 调用者的线程执行中被完成(而是通过librados上下文或者线程池上下文进行缓存读取命中)。

aio的请求类型:

  • AIO_TYPE_NODE

  • Aio_TYPE_OPEN

  • AIO_TYPE_CLOSE

  • AIO_TYPE_READ

  • AIO_TYPE_WRITE

  • AIO_TYPE_DISCARD

  • AIO_TYPE_FLUSH

aio的一些状态:

  • STATE_PENDING

  • STATE_CALLBACK

  • STATE_COMPLETE

2.AioImageRequest

3.AioObjectRequest

该类表示对单个RBD数据对象的IO操作,它的子类封装了处理layering特性的IO逻辑。

快照

snap_info_t

typedef struct {
  uint64_t id;
  uint64_t size;
  std::string name;
} snap_info_t;

获取snap信息

创建snap:

添加一个snap到一个rbd header上面 创建snap时,会创建header对象的omap属性,snap_seq为当前snapset的最大id,snapshot的具体 key为snapshot_+snap_id,value为snapname。

Test

create pool:
[root@cephnode2 45.0_head]# ls
__head_00000000__2d

use ceph-kvstore-tool get omap info:
_HOBJTOSEQ_:...head.2d.00000000
_SYS_:HEADER
_USER_0000000000000057_USER_:_biginfo
_USER_0000000000000057_USER_:_epoch
_USER_0000000000000057_USER_:_info
_USER_0000000000000057_USER_:_infover
_USER_0000000000000057_USER_:can_rollback_to
_USER_0000000000000057_USER_:rollback_info_trimmed_to

rbd import a 4M image
[root@cephnode2 45.0_head]# ls
__head_00000000__2d  rb.0.d2f63.74b0dc51.000000000000__head_2FED9A61__2d
rbd\udirectory__head_30A98C1C__2d  zealoussnow.rbd__head_78F40C0D__2d

get omap info:
_HOBJTOSEQ_:...head.2d.00000000
_SYS_:HEADER
_USER_0000000000000057_USER_:0000000391.00000000000000000001
_USER_0000000000000057_USER_:0000000391.00000000000000000002
_USER_0000000000000057_USER_:0000000391.00000000000000000003
_USER_0000000000000057_USER_:0000000391.00000000000000000004
_USER_0000000000000057_USER_:0000000391.00000000000000000005
_USER_0000000000000057_USER_:_biginfo
_USER_0000000000000057_USER_:_epoch
_USER_0000000000000057_USER_:_info
_USER_0000000000000057_USER_:_infover
_USER_0000000000000057_USER_:can_rollback_to
_USER_0000000000000057_USER_:rollback_info_trimmed_to

omap属性里,保存所有的RBD的设备名和id信息,key值为“name“和设备名的拼接,value为“id_”和RBD的id的拼接 (如何获取这些值呢?)

每个RBD设备所创建的对象

format v2

rbd_id.<name>: RBD的id_obj对象,其名字保存了RBD的name信息,其对象的内容保存了该RBD的id信息
[root@cephnode2 303.0_head]# hexdump  -C "rbd\\uid.hello\\uformat2.img__head_57893838__12f"
00000000  0e 00 00 00 31 64 38 33  33 37 37 34 62 30 64 63  |....1d833774b0dc|
00000010  35 31                                             |51|
00000012

rbd_header.<id>: RBD的header_obj对象,其omap保存了RBD相关的元数据信息
[root@cephnode2 src]# ./rados -c ceph.conf -p hello_world_pool listomapvals rbd_header.1d833774b0dc51 2>/dev/null
features
value (8 bytes) :
0000 : 03 00 00 00 00 00 00 00                         : ........

object_prefix
value (27 bytes) :
0000 : 17 00 00 00 72 62 64 5f 64 61 74 61 2e 31 64 38 : ....rbd_data.1d8
0010 : 33 33 37 37 34 62 30 64 63 35 31                : 33774b0dc51

order
value (1 bytes) :
0000 : 16                                              : .

size
value (8 bytes) :
0000 : 00 00 00 20 00 00 00 00                         : ... ....

snap_seq
value (8 bytes) :
0000 : 00 00 00 00 00 00 00 00                         : ........


rbd_object_map.<id>: 保存了其对象和父iamge对象的映射信息

数据对象:
rbd.data.<id>.00000000
rbd.data.<id>.00000001
...

NOTE: need read

Rados可以同步或异步的方式交互,一旦应用有了IO上下文,读写操作只需要你知道对象/扩展文件属性的名字。Crush算法封装在librados 中,使用ClusterMap来确认合适的OSD。

librados初始化: librados.h rados_t (typedef void *rados_t) 同rados集群交互的句柄,封装了所有RADOS客户端配置,包括用户名,认证key,日志和调试

rados_ioctx_t (typedef void *rados_ioctx_t)

Rados::init rados_create rados_create_cct 新建一个RadosClient对象

monclient发送给monitor的消息(helloworld): 认证: auth(proto 0 30 bytes epoch 0) v1 auth(proto 2 32 bytes epoch 0) v1 auth(proto 2 165 bytes epoch 0) v1

monmap和osdmap订阅: mon_subscribe({monmap=0+}) v2 mon_subscribe({monmap=2+,osdmap=0}) v2 mon_subscribe({monmap=2+,osdmap=0}) v2

创建pool:

pool_op(create pool 0 auid 0 tid 1 name hello_world_pool v0) v4
mon_subscribe({monmap=2+,osdmap=337}) v2
OSDMonitor::prepare_new_pool
librados::Rados::pool_create
librados::RadosClient::pool_create
Objecter::create_pool // op->pool_op = POOL_OP_CREATE
    Objecter::pool_op_submit
        new MPoolOp --> CEPH_MSG_POOLOP
    Objecter::_pool_op_submit
    monc->send_mon_message
MonClient::send_mon_message
    MonClient::_send_mon_message
    cur_con->send_message
PipeConnection::send_message
    static_cast<SimpleMessenger*>(msgr)->send_message
SimpleMessenger::send_message
    SimpleMessenger::_send_message
    SimpleMessenger::submit_message
Pipe::_send
    cond.Signal() ---> 激活写线程,发送消息

OSDMonitor接收消息:

删除池:

pool_op(delete pool 38 auid 0 tid 4 name delete v337) v4
mon_subscribe({monmap=2+,osdmap=339}) v2
Objecter::delete_pool
        Objecter::_do_delete_pool // op->pool_op = POOL_OP_DELETE

写入对象: osd_op(client.754098.0:2 hello_object [writefull 0~12] 38.bbbfcc6c ondisk+write+known_if_redirected e337) v5

1.IoCtx的创建 Rados::ioctx_create rados_ioctx_create RadosClient::create_ioctx new librados::IoCtxImpl 最终创建一个IoCtxImpl实例

从对象到pg的寻址: hash 从pg到到OSD: crush算法

crush算法:

Write Object

write_full:

ObjectOperation op
prepare_assert_ops ==> op
op.write_full(bl)
IoCtxImpl::operate
    objecter->prepare_mutate_op
    objecter->op_submit
Objecter::op_submit
    _op_submit_with_budget
    _op_submit
Objecter::_op_submit ==> 完成了关键的地址寻址和发送工作
    _calc_target
    _get_session ==> 获取目标OSD连接
    _send_op_account
    _session_op_assign
    _send_op
Objecter::_send_op
op->session->con->send_message(m)
PipeConnection::send_message
SimpleMessenger::_send_message

读取对象: op.op = CEPH_OSD_OP_READ Rados::aio_create_completion

设置对象属性: osd_op(client.754098.0:3 hello_object [setxattr helloworld_version (1)] 38.bbbfcc6c ondisk+write+known_if_redirected e337) v5

所有的pool,对象和属性操作都是通过调用Objecter::op_submit来完成的

Entity NAME: MON 1 MDS 2 OSD 4 CLIENT 8 AUTH 20

rbd镜像属性详解

配置项为rbd_default_features=[3 or 61],这个值是由几个属性加起来的:

Features编号:

Only applies to format 2 images: +1 for layering +2 for striping v2 +4 for exclusive lock +8 for object map +16 for fast-diff +32 for deep-flatten +64 for journaling

在J版中,rbd镜像的默认属性是61(1+4+8+16+32),即开启了layering,exclusive lock,objct mapfast-diffdeep-flatten这几个属性。可以新建一个rbd块设备来查看其属性:

# rbd info test/test
rbd image 'test':
        size 500 GB in 128000 objects
        order 22 (4096 kB objects)
        block_name_prefix: rbd_data.104b2a6b8b4567
        format: 2
        features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
        flags:

layering

Ceph支持创建针对一个块设备快照的许多COW克隆。快照layering可以让rbd客户端非常快速地创建镜像。 比如:可以创建一个Linux VM的块设备镜像;然后对这个镜像做快照,保护快照,创建许多COW克隆。一个 快照是只读的,因此,克隆一个快照从语义上来说是很精简的——使得快速克隆称为可能。

+--------------------+                  +----------------+
|       Snapshot     |   Child refers   |  COW Clone     |
|        of Image    | <----------------| of Snapshot    |
|   (read only)      |     to Parent    |    (Writeable) |
+--------------------+                  +----------------+

注意:上图中的parent表示的意思是一个块设备的快照,child表示的是从快照克隆的相关镜像。

每个克隆的镜像(child)存储了它的父镜像的一个引用,允许克隆的镜像打开父快照并读取它。

举一个实际的例子,比如ceph里有一个base image,我们要克隆到一个新的instance:

  • 先给这个base image创建一个快照

  • 对这个快照进行克隆

快照在克隆之前要设置为protect状态。

如果这个快照被克隆了,那么在child没有被删除或者没有进行flatten操作之前,这个快照的protect 状态是取消不掉的,因此它不会被删除。只有它的children都被删除或flatten之后(即不再被依赖), 它才可以删除。

layering的实现中,并没有track每个object在克隆中是否存在,而是读到不存在的object的时候就去 parent那里找。对于写操作,首先要检查object是否存在,不存在的话要先从parent那里读出来再做写 操作。

striping

exclusive-lock

object-map

ObjectMap这个属性自从J版开始默认支持,它是一个位图实现,每个镜像都会有一个ObjectMap,根据object_no 来记录每个object的存在状态。

一个Image的ObjectMap对象的名称为rbd_object_map.<oid>,如果有快照,快照的ObjectMap 名称为rbd_object_map.<oid>.<snap_id>

数据结构:

class ObjectMap {
...
private:
  ImageCtx &m_image_ctx;
  ceph::BitVector<2> m_object_map;
  uint64_t m_snap_id;
};

字段解释:

  • m_image_ctx: 使用该ObjectMap的镜像上下文

  • m_object_map: 位图结构

  • m_snap_id: 镜像的snap id

转自;Ceph-Client.md · 王洪旭/CephKnowledge - Gitee.comhttps://gitee.com/wanghongxu/cephknowledge/blob/master/Ceph-Client.md

posted on 2022-10-04 01:21  bdy  阅读(148)  评论(0编辑  收藏  举报

导航