【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集群和其中数据的所有访问功能:

集群的整体访问:包括连接集群、创建存储池、删除存储池和获取集群状态等等。

对象访问:是指对存储池中对象的访问,包括创建删除对象、向对象写数据或者追加数据和读对象数据等接口。

扫描二维码关注公众号,回复: 13770884 查看本文章

上述功能通过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 features

    vector<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 object

  object_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

猜你喜欢

转载自blog.csdn.net/bandaoyu/article/details/123861754