【ceph】Rados的客户端RadosClient|MonClient|Osdc
本文介绍Ceph客户端方面的某些模块的实现。
(摘抄自:https://blog.csdn.net/CSND_PAN/article/details/78707756)
客户端主要是实现了接口,让外部可以调用实现访问操作。上层可以通过调用这些接口来访问Ceph存储。
Ceph的客户端通过一套名为librados的接口进行集群的访问,这里的访问包括:
1)对集群的整体访问
2)对象的访问
两类接口,这套接口(API)包括C、C++和Python常见语言的实现,接口通过网络实现对Ceph集群的访问。在用户层面,可以在自己的程序中调用该接口,从而集成Ceph集群的存储功能,或者在监控程序中实现对Ceph集群状态的监控。上述接口与Ceph集群的关系如图1所示。
RADOS的客户端API
上述接口几乎包括了对Ceph集群和其中数据的所有访问功能:
集群的整体访问:包括连接集群、创建存储池、删除存储池和获取集群状态等等。
对象访问:是指对存储池中对象的访问,包括创建删除对象、向对象写数据或者追加数据和读对象数据等接口。
上述功能通过Rados和IoCtx两个类实现,两个类的主要函数如图2所示(这里仅是示例,实际接口数量要多很多,具体参考源代码)。
为了了解如何使用这些API,我们这里给出一些代码片段。具体完整的代码大家可以参考Ceph官方的示例代码。
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");
Librados 与 Osdc 位于Ceph客户端中比较底层的位置,
Librados 提供了Pool的创建、删除、对象的创建、删除等基本接口;
Osdc则用于封装操作,计算对象的地址,发送请求和处理超时。
如图:
根据LIBRADOS架构图,叙述大概的事件流程。在 Ceph分布式存储实战中 这本书中有如下一段话:
先根据配置文件调用LIBRADOS创建一个RADOS,接下来为这个RADOS创建一个radosclient,radosclient包含3个主要模块(finisher、Message、Objector)。
再根据pool创建对应的ioctx,在ioctx中能够找到radosclient。在调用OSDC生成对应的OSD请求,与OSD进行通信响应请求。这从大体上叙述了librados与osdc在整个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的代码。
如图是核心流程的流程图,本文不详细介绍,具体细节可以按照该流程读对应源代码理解。
在这个流程中需要注意的是_op_submit函数会调用_calc_target和_get_session两个函数,两个函数的作用分别是获取目的OSD和对应的Session(连接),这个是后面发送数据的基础。
作者:SunnyZhang的IT世界
链接:https://www.jianshu.com/p/d7f69900ae8e
Librados
该模块包含两个部分,分别是RadosClient 模块和IoctxImpl。RadosClient处于最上层,是librados的核心管理类,管理着整个RADOS系统层面以及pool层面的管理。
而IoctxImpl则对于其中的某一个pool进行管理,如对 对象的读写等操作的控制。
RadosClient(Librados模块)
IoctxImpl(Librados模块)
Objecter(Osdc 模块)
RadosClient类
先看头文件 radosclient.h
ass librados::RadosClient : public Dispatcher //继承自Dispatcher(消息分发类)
{
public:
using Dispatcher::cct;
md_config_t *conf; //配置文件
private:
enum {
DISCONNECTED,
CONNECTING,
CONNECTED,
} state; //网络连接状态MonClient monclient; // monc
Messenger *messenger; //网络消息接口uint64_t instance_id;
//相关消息分发 Dispatcher类的函数重写
bool _dispatch(Message *m);
bool ms_dispatch(Message *m);
bool ms_get_authorizer(int dest_type, AuthAuthorizer **authorizer, bool force_new);
void ms_handle_connect(Connection *con);
bool ms_handle_reset(Connection *con);
void ms_handle_remote_reset(Connection *con);Objecter *objecter; // Osdc模块中的 用于发送封装好的OP消息
Mutex lock;
Cond cond;
SafeTimer timer; //定时器
int refcnt;
version_t log_last_version;
rados_log_callback_t log_cb;
void *log_cb_arg;
string log_watch;
int wait_for_osdmap();public:
Finisher finisher; // 回调函数的类explicit RadosClient(CephContext *cct_);
~RadosClient();
int ping_monitor(string mon_id, string *result);
int connect(); // 连接
void shutdown();
int watch_flush();
int async_watch_flush(AioCompletionImpl *c);
uint64_t get_instance_id();
int wait_for_latest_osdmap();
// 根据pool名字或id创建ioctx
int create_ioctx(const char *name, IoCtxImpl **io);
int create_ioctx(int64_t, IoCtxImpl **io);
int get_fsid(std::string *s);
// pool相关操作
int64_t lookup_pool(const char *name);
bool pool_requires_alignment(int64_t pool_id);
int pool_requires_alignment2(int64_t pool_id, bool *requires);
uint64_t pool_required_alignment(int64_t pool_id);
int pool_required_alignment2(int64_t pool_id, uint64_t *alignment);
int pool_get_auid(uint64_t pool_id, unsigned long long *auid);
int pool_get_name(uint64_t pool_id, std::string *auid);
int pool_list(std::list<std::pair<int64_t, string> >& ls);
int get_pool_stats(std::list<string>& ls, map<string,::pool_stat_t>& result);
int get_fs_stats(ceph_statfs& result);/*
-1 was set as the default value and monitor will pickup the right crush rule with below order:
a) osd pool default crush replicated ruleset
b) the first ruleset in crush ruleset
c) error out if no value find
*/
// 同步创建pool 和 异步创建pool
int pool_create(string& name, unsigned long long auid=0, int16_t crush_rule=-1);
int pool_create_async(string& name, PoolAsyncCompletionImpl *c, unsigned long long auid=0,
int16_t crush_rule=-1);
int pool_get_base_tier(int64_t pool_id, int64_t* base_tier);
//同步删除和异步删除
int pool_delete(const char *name);
int pool_delete_async(const char *name, PoolAsyncCompletionImpl *c);
int blacklist_add(const string& client_address, uint32_t expire_seconds);
//Mon命令处理,调用monclient.start_mon_command 把命令发送给Mon处理
int mon_command(const vector<string>& cmd, const bufferlist &inbl,
bufferlist *outbl, string *outs);
int mon_command(int rank,
const vector<string>& cmd, const bufferlist &inbl,
bufferlist *outbl, string *outs);
int mon_command(string name,
const vector<string>& cmd, const bufferlist &inbl,
bufferlist *outbl, string *outs);
//OSD命令处理,objector->osd_command 把命令发送给OSD处理
int osd_command(int osd, vector<string>& cmd, const bufferlist& inbl,
bufferlist *poutbl, string *prs);
//PG命令处理,objector->pg_command 把命令发送给PG处理
int pg_command(pg_t pgid, vector<string>& cmd, const bufferlist& inbl,
bufferlist *poutbl, string *prs);
void handle_log(MLog *m);
int monitor_log(const string& level, rados_log_callback_t cb, void *arg);
void get();
bool put();
void blacklist_self(bool set);
};
再来看看其中一些的函数
connect () 是RadosClient的初始化函数。
int librados::RadosClient::connect()
{
common_init_finish(cct);
int err;// already connected?
if (state == CONNECTING)
return -EINPROGRESS;
if (state == CONNECTED)
return -EISCONN;
state = CONNECTING;// get monmap
err = monclient.build_initial_monmap(); //通过配置文件获取初始化的Monitor
if (err < 0)
goto out;err = -ENOMEM;
messenger = Messenger::create_client_messenger(cct, "radosclient"); //创建通信模块
if (!messenger)
goto out;// require OSDREPLYMUX feature. this means we will fail to talk to
// old servers. this is necessary because otherwise we won't know
// how to decompose the reply data into its consituent pieces.
messenger->set_default_policy(Messenger::Policy::lossy_client(0, CEPH_FEATURE_OSDREPLYMUX));ldout(cct, 1) << "starting msgr at " << messenger->get_myaddr() << dendl;
ldout(cct, 1) << "starting objecter" << dendl;
//创建objecter
objecter = new (std::nothrow) Objecter(cct, messenger, &monclient,
&finisher,
cct->_conf->rados_mon_op_timeout,
cct->_conf->rados_osd_op_timeout);
if (!objecter)
goto out;
objecter->set_balanced_budget();
// mc添加 messenger
monclient.set_messenger(messenger);
// objecter 初始化
objecter->init();
// messenger添加 dispather
messenger->add_dispatcher_tail(objecter);
messenger->add_dispatcher_tail(this);
// messenger启动
messenger->start();
ldout(cct, 1) << "setting wanted keys" << dendl;
monclient.set_want_keys(CEPH_ENTITY_TYPE_MON | CEPH_ENTITY_TYPE_OSD);
ldout(cct, 1) << "calling monclient init" << dendl;
// mc 初始化
err = monclient.init();
if (err) {
ldout(cct, 0) << conf->name << " initialization error " << cpp_strerror(-err) << dendl;
shutdown();
goto out;
}err = monclient.authenticate(conf->client_mount_timeout);
if (err) {
ldout(cct, 0) << conf->name << " authentication error " << cpp_strerror(-err) << dendl;
shutdown();
goto out;
}
messenger->set_myname(entity_name_t::CLIENT(monclient.get_global_id()));
objecter->set_client_incarnation(0);
// objecter 启动
objecter->start();
lock.Lock();
// 定时器初始化
timer.init();
monclient.renew_subs();
//执行回调的完成类start
finisher.start();
// 更新 状态为已连接
state = CONNECTED;
instance_id = monclient.get_global_id();...
}
Mon OSD pg 命令操作
int librados::RadosClient::mon_command(const vector<string>& cmd,
const bufferlist &inbl,
bufferlist *outbl, string *outs)
{
// mc start_mon_command 发送到monitor
monclient.start_mon_command(cmd, inbl, outbl, outs,
new C_SafeCond(&mylock, &cond, &done, &rval));
}int librados::RadosClient::osd_command(int osd, vector<string>& cmd,
const bufferlist& inbl,
bufferlist *poutbl, string *prs)
{
// 发送到osd
int r = objecter->osd_command(osd, cmd, inbl, &tid, poutbl, prs,
new C_SafeCond(&mylock, &cond, &done, &ret));
}int librados::RadosClient::pg_command(pg_t pgid, vector<string>& cmd,
const bufferlist& inbl,
bufferlist *poutbl, string *prs)
{
// 发送到pg
int r = objecter->pg_command(pgid, cmd, inbl, &tid, poutbl, prs,
new C_SafeCond(&mylock, &cond, &done, &ret));
}
create_ioctx()用于创建一个pool相关的上下文信息 IoCtxImpl对象。
int librados::RadosClient::create_ioctx(const char *name, IoCtxImpl **io)
{
int64_t poolid = lookup_pool(name);
if (poolid < 0) {
return (int)poolid;
}*io = new librados::IoCtxImpl(this, objecter, poolid, CEPH_NOSNAP);
return 0;
}
mon_command()用于处理Monitor相关命令
void librados::RadosClient::mon_command_async(const vector<string>& cmd,
const bufferlist &inbl,
bufferlist *outbl, string *outs,
Context *on_finish)
{
lock.Lock();
monclient.start_mon_command(cmd, inbl, outbl, outs, on_finish);//把命令发送给Monitor处理
lock.Unlock();
}
osd_command()处理OSD相关命令
int librados::RadosClient::osd_command(int osd, vector<string>& cmd,
const bufferlist& inbl,
bufferlist *poutbl, string *prs)
{
Mutex mylock("RadosClient::osd_command::mylock");
Cond cond;
bool done;
int ret;
ceph_tid_t tid;if (osd < 0)
return -EINVAL;lock.Lock();
//调用objecter->osd_commandf 发送命令给OSD处理
objecter->osd_command(osd, cmd, inbl, &tid, poutbl, prs,
new C_SafeCond(&mylock, &cond, &done, &ret));
lock.Unlock();
mylock.Lock();
while (!done)
cond.Wait(mylock);
mylock.Unlock();
return ret;
}
IoctxImpl
该类是pool的上下文信息,一个pool对应一个IoctxImpl对象。librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。它的处理过程如下:
1)把请求封装成ObjectOperation 类(osdc 中的)
2)把相关的pool信息添加到里面,封装成Objecter::Op对像
3)调用相应的函数 objecter- >op_submit 发送给相应的OSD
4)操作完成后,调用相应的回调函数。
如rados_write
extern "C" int rados_write(rados_ioctx_t io, const char *o, const char *buf, size_t len, uint64_t off) { librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io; object_t oid(o); bufferlist bl; bl.append(buf, len); int retval = ctx->write(oid, bl, len, off); }
调用IoCtxImpl::write
int librados::IoCtxImpl::write(const object_t& oid, bufferlist& bl,
size_t len, uint64_t off)
{
::ObjectOperation op;
prepare_assert_ops(&op); // assert ops
bufferlist mybl;
mybl.substr_of(bl, 0, len);
op.write(off, mybl); // 封装到op.write Objecter.h ObjectOperation write
return operate(oid, &op, NULL); // IoCtxImpl::operate
}int librados::IoCtxImpl::operate(const object_t& oid, ::ObjectOperation *o,
ceph::real_time *pmtime, int flags)
{int op = o->ops[0].op.op;
Objecter::Op *objecter_op = objecter->prepare_mutate_op(oid, oloc,
*o, snapc, ut, flags,
NULL, oncommit, &ver);
objecter->op_submit(objecter_op);
}
AioCompletionImpl
Aio即Async IO,AioCompletion即Async Io Completion,也就是Async IO完成时的回调处理制作,librados设计AioCompletion就是为了提供一种机制对Aio完成时结果码的处理。而处理函数则由使用者来实现。AioCompletion是librados设计开放的库API,真正的设计逻辑在AioCompletionImpl中。
对于AIoCompletion实例的使用都是引用的pc,即AioCompletionImpl,因此具体来说应该是如何包装AioCompletionImpl。这里提一下,librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。而AioCompletionImpl是IO操作的回调,因为对于AioCompletionImpl的包装设计在IoCtxImpl模块中
详细的关于回调机制的分析见:ceph源代码分析之librados:1. AioCompletion回调机制分析
OSDC
该模块是客户端模块比较底层的模块,用于封装操作数据,计算对象的地址、发送请求和处理超时。
ObjectOperation 封装操作
该类用于将操作相关的参数统一封装在该类中,并且一次可以封装多个操作。代码太长了:https://www.yisu.com/zixun/561752.html
struct ObjectOperation {
vector<OSDOp> ops;//操作集合
int flags;
int priority;vector<bufferlist*> out_bl;//输出缓存队列
vector<Context*> out_handler;//回调函数队列
vector<int*> out_rval;//操作结果队列ObjectOperation() : flags(0), priority(0) {}
~ObjectOperation() {
while (!out_handler.empty()) {
delete out_handler.back();
out_handler.pop_back();
}
}
...
...
...
}
类OSDop封装对象的一个操作。结构Ceph_osd_op 封装一个操作码和相关的输入输出参数:
struct OSDop{
ceph_osd_op op;//操作码和操作参数
sobject_t soid;
bufferlist indata,outdata
int32_t rval;//操作结果
}
op_target 封装PG信息
该结构封装了对象所在的而PG,以及PG对应的OSD列表等信息。
struct op_target_t {
int flags = 0;epoch_t epoch = 0; ///< latest epoch we calculated the mapping
object_t base_oid;//读取的对象
object_locator_t base_oloc;//对象的pool信息
object_t target_oid;//最终读取的目标对象
object_locator_t target_oloc;//最终该目标对象的pool信息。///< true if we are directed at base_pgid, not base_oid
bool precalc_pgid = false;///< true if we have ever mapped to a valid pool
bool pool_ever_existed = false;///< explcit pg target, if any
pg_t base_pgid;pg_t pgid; ///< last (raw) pg we mapped to
spg_t actual_pgid; ///< last (actual) spg_t we mapped to
unsigned pg_num = 0; ///< last pg_num we mapped to
unsigned pg_num_mask = 0; ///< last pg_num_mask we mapped to
vector<int> up; ///< set of up osds for last pg we mapped to
vector<int> acting; ///< set of acting osds for last pg we mapped to
int up_primary = -1; ///< last up_primary we mapped to
int acting_primary = -1; ///< last acting_primary we mapped to
int size = -1; ///< the size of the pool when were were last mapped
int min_size = -1; ///< the min size of the pool when were were last mapped
bool sort_bitwise = false; ///< whether the hobject_t sort order is bitwise
bool recovery_deletes = false; ///< whether the deletes are performed during recovery instead of peering
...
...
...
};
Op 封装操作信息
该结构体封装了完成一个操作的相关上下文信息,包括target地址信息、连接信息等。
struct Op : public RefCountedObject {
OSDSession *session;//OSD的相关Session 信息,session是关于connect的信息
int incarnation;op_target_t target;//地址信息
ConnectionRef con; // for rx buffer only
uint64_t features; // explicitly specified op featuresvector<OSDOp> ops;//多个操作
snapid_t snapid;快照ID
SnapContext snapc;
ceph::real_time mtime;bufferlist *outbl;
vector<bufferlist*> out_bl;
vector<Context*> out_handler;
vector<int*> out_rval;
分片 Striper
当一个文件到对象的映射时,对象有分片,则使用这个类来分片,并保存分片信息。
class Striper {
public:
/*
* map (ino, layout, offset, len) to a (list of) ObjectExtents (byte
* ranges in objects on (primary) osds)该函数完成file到对象stripe后的映射。
*/
static void file_to_extents(CephContext *cct, const char *object_format,
const file_layout_t *layout,
uint64_t offset, uint64_t len,
uint64_t trunc_size,
map<object_t, vector<ObjectExtent> >& extents,
uint64_t buffer_offset=0};
其中 ObjectExtent保存的是对象内的分片信息。
class ObjectExtent {
public:
object_t oid; // object id
uint64_t objectno;//分片序号
uint64_t offset; // 对象内的偏移
uint64_t length; // 长度
uint64_t truncate_size; // in objectobject_locator_t oloc; // object locator (pool etc)位置信息 如在哪个pool
};
原文链接:https://blog.csdn.net/CSND_PAN/article/details/78707756
Ceph读写流程源码走读
Ceph读写流程源码走读 :http://events.jianshu.io/p/90cb8127c20f
MonClient中的函数
MonClient中的函数:https://blog.csdn.net/litianze99/article/details/86086463