Ceph的客户端接口及架构分析

图1 客户端与Ceph集群示意图
Ceph的客户端通过一套名为librados的接口进行集群的访问,这里的访问包括对集群的整体访问和对象的访问两类接口。这套接口(API)包括C、C++和Python常见语言的实现,接口通过网络实现对Ceph集群的访问。在用户层面,可以在自己的程序中调用该接口,从而集成Ceph集群的存储功能,或者在监控程序中实现对Ceph集群状态的监控。上述接口与Ceph集群的关系如图1所示。

RADOS的客户端API

上述接口几乎包括了对Ceph集群和其中数据的所有访问功能。其中所谓集群的整体访问包括连接集群、创建存储池、删除存储池和获取集群状态等等。所谓对象访问是之对存储池中对象的访问,包括创建删除对象、向对象写数据或者追加数据和读对象数据等接口。上述功能通过Rados和IoCtx两个类实现,两个类的主要函数如图2所示(这里仅是示例,实际接口数量要多很多,具体参考源代码)。
图2 访问接口类图
为了了解如何使用这些API,我们这里给出一些代码片段。具体完整的代码大家可以参考Ceph官方的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
librados::IoCtx io_ctx;
const char *pool_name = "test";
/* 创建进行IO处理的上下文,其实就是用于访问Ceph的对象 */
cluster.ioctx_create(pool_name, io_ctx);

/* 同步写对象 */
librados::bufferlist bl;
bl.append("Hello World!"); /* 对象的内容 */
/*写入对象itworld123*/
ret = io_ctx.write_full("itworld123", bl);

/* 向对象添加属性,这里的属性与文件系统
* 中文件的扩展属性类似。 */
librados::bufferlist attr_bl;
attr_bl.append("en_US");
io_ctx.setxattr("itworld123", "test_attr", attr_bl);

/* 异步读取对象内容 */
librados::bufferlist read_buf;
int read_len = 1024;
/* 创建一个异步完成类对象 */
librados::AioCompletion *read_completion = librados::Rados::aio_create_completion();
/* 发送读请求 */
io_ctx.aio_read("itworld123", read_completion, &read_buf, read_len, 0);
/* 等待请求完成 */
read_completion->wait_for_complete();
read_completion->get_return_value();

/* 读取对象属性 */
librados::bufferlist attr_res;
io_ctx.getxattr("itworld123", "test_attr", attr_res);

/* 删除对象的属性 */
io_ctx.rmxattr("itworld123", "test_attr");

/* 删除对象 */
io_ctx.remove("itworld123");

本文主要目的是让大家了解客户端的接口,因此仅仅给出了基本用法,并没有对错误等情况进行处理。Ceph官方有完整的代码,大家可以参考。

客户端软件架构概述

librados客户端基本架构如图3所示,主要包括4层,分别是API层、IO处理层、对象处理层和消息收发层。其中API层是一个抽象层,为上层提供统一的接口。API层提供的原生接口包括C和C++两种语言的实现外,还有Python的实现。
图3 RADOS客户端基本架构
IO处理层用于实现IO的简单封装,其通过一个名为ObjectOperation类实现,该类主要包括的是读写操作的数据信息。之后在IO处理层在IoCtxImpl::operate函数中将ObjectOperation转换为Objecter::Op类的对象,并将该对象提交到对象处理层进行进一步的处理。
对象处理层包括了Ceph对象处理所需要的信息,包括通信管道、OSDMap和MonMap等内容。因此,在这里,根据对象的信息可以计算出对象存储的具体位置,最终找到客户端与OSD的连接信息(Session)。
消息收发层的接口会被对象处理层调用,此时消息会传递到本层,并且通过本层的线程池发送到具体的OSD。这里需要注意的是,消息收发层与服务端的消息收发公用Messager的代码。
如图是核心流程的流程图,本文不详细介绍,具体细节可以按照该流程读对应源代码理解。
2.png
在这个流程中需要注意的是_op_submit函数会调用_calc_target_get_session两个函数,两个函数的作用分别是获取目的OSD和对应的Session(连接),这个是后面发送数据的基础。