文档章节

ceph的数据存储之路(4) ----- rbd client 端的数据请求处理

一只小江
 一只小江
发布于 2015/11/19 15:21
字数 4088
阅读 7998
收藏 12

2016-11-01更新 start:--------------------------------------------------------------------------------------

最近比较多的人私下问我,改了ceph的源码,重新编译了,但是在使用本节提供的python脚本测试librbd的时候出现了错误,怎么解决出现的这个错误。这应该是个好现象,很多人都深入到代码层级了,这个也是开始代码之旅的重要环节,今天在这里更新说明下。之前在第二节https://my.oschina.net/u/2460844/blog/515353中讲述了怎么编译源码,但是在使用本节提供的脚本时会出现一些问题,这些问题来自于使用了源码编译后,在python脚本中调用失败。问题的原因是我忘记添加上了一个环节,重新编译后的代码要替换一些库文件,首先来看下提示的错误有哪些,错误如下:

1.ImportError: No module named rados

root@cephmon:~/ceph/ceph-0.94.2/python# python create_rbd.py 
Traceback (most recent call last):
  File "create_rbd.py", line 2, in <module>
    import sys,rados,rbd
ImportError: No module named rados

2.OSError: librados.so.2: cannot open shared object file: No such file or directory

root@cephmon:~/ceph/ceph-0.94.2/python# python create_rbd.py
Traceback (most recent call last):
  File "create_rbd.py", line 18, in <module>
    connectceph()
  File "create_rbd.py", line 4, in connectceph
    cluster = rados.Rados(conffile = '/root/ceph/ceph-0.94.2/src/ceph.conf')
  File "/usr/lib/python2.7/rados.py", line 215, in __init__
    self.librados = CDLL(library_path if library_path is not None else 'librados.so.2')
  File "/usr/lib/python2.7/ctypes/__init__.py", line 365, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: librados.so.2: cannot open shared object file: No such file or directory

出现这两个问题的原因是 源码编译后,python 脚本无法找到对应的库文件(或者python文件)。

针对问题1.拷贝源码包下面的python脚本 cp  ../ceph-0.94.2/src/pybind/*  /usr/lib/python2.7/

针对问题2.拷贝最新编译出来的的librados到/usr/lib/ 目录下即可, 这个最新编译出来的librados在目录../ceph-0.94.2/src/.lib/目录中,该目录是一个隐藏目录,容易被忽略。在该目录下找到librados.so.2 和librbd.so.1 拷贝到 /usr/lib/ 下。

解决了问题1和问题2,脚本就可以正常的运行了。

针对问题2,OSError: librados.so.2: cannot open shared object file: No such file or directory 网上经常有人在用源码部署ceph的时候都出现了这个问题,好像没人特别准确的回答这个问题。原因就是最新编译出的librados没有拷贝到/usr/lib下,脚本或者程序找不到这个库所以报错。解决办法如上即可。

2016-11-01更新 end:--------------------------------------------------------------------------------------

讲ceph的文章有很多,但是都是从高大尚的理论出发,看了很多这样的文章收获很多,但是总有一种不能实际抓住ceph的命门,不能切脉,很多时候可能看完就忘了。从这篇博客开始抛开高大尚的理论,从最接地气的方式开始,可以帮助那些需要开发ceph童鞋们,或者想深入了解ceph实现的童鞋们。这里用最接地气的方式讲述ceph背后的故事。

首先明白ceph就是用来存储的,这个系列的博客就讲述如何ceph的读写请求的一生,本节讲述数据写操作的生命开始。

首先看一下我们用python调用librbd 写rbd设备的测试代码:

#!/usr/bin/env python
import sys,rados,rbd
def connectceph():
      cluster = rados.Rados(conffile = '/root/xuyanjiangtest/ceph-0.94.3/src/ceph.conf')
      cluster.connect()
      ioctx = cluster.open_ioctx('mypool')
      rbd_inst = rbd.RBD()
      size = 4*1024**3 #4 GiB
      rbd_inst.create(ioctx,'myimage',size)
      image = rbd.Image(ioctx,'myimage')
      data = 'foo'* 200
      image.write(data,0)
      image.close()
      ioctx.close()
        cluster.shutdown()
 
if __name__ == "__main__":
        connectceph()

 

一、写操作数据request的孕育过程

在write request 请求开始之前,它需要准备点旅行的用品,往返的机票等。下面先看看前期准备了什么。

1. 首先cluster = rados.Rados(conffile = 'XXXX/ceph.conf'),用当前的这个ceph的配置文件去创建一个rados,这里主要是解析ceph.conf中写明的参数。然后将这些参数的值保存在rados中。

2. cluster.connect() ,这里将会创建一个radosclient的结构,这里会把这个结构主要包含了几个功能模块:消息管理模块Messager,数据处理模块Objector,finisher线程模块。这些模块具体的工作后面讲述。

3. ioctx = cluster.open_ioctx('mypool'),为一个名字叫做mypool的存储池创建一个ioctx ,ioctx中会指明radosclient与Objector模块,同时也会记录mypool的信息,包括pool的参数等。

4. rbd_inst.create(ioctx,'myimage',size) ,创建一个名字为myimage的rbd设备,之后就是将数据写入这个设备。

5. image = rbd.Image(ioctx,'myimage'),创建image结构,这里该结构将myimage与ioctx 联系起来,后面可以通过image结构直接找到ioctx。这里会将ioctx复制两份,分为为data_ioctx和md_ctx。见明知意,一个用来处理rbd的存储数据,一个用来处理rbd的管理数据。

通过上面的操作就会形成这样的结构(如下图)

 

图1-1 request孕育阶段

 

过程描述,首先根据配置文件创建一个rados,接下来为这个rados创建一个radosclient,radosclient包含了3个主要模块(finisher,Messager,Objector)。再根据pool创建对应的ioctx,ioctx中能够找到radosclient。再对生成对应rbd的结构image,这个image中复制了两个ioctx,分别成为了md_ioctx与data_ioctx。这时完全可以根据image入口去查找到前期准备的其他数据结构。接下来的数据操作完全从image开始,也是rbd的具体实例。

 

二、request的出生和成长。

1. image.write(data,0),通过image开始了一个写请求的生命的开始。这里指明了request的两个基本要素 buffer=data 和 offset=0。由这里开始进入了ceph的世界,也是c++的世界。

由image.write(data,0)  转化为librbd.cc 文件中的Image::write() 函数,来看看这个函数的主要实现

ssize_t Image::write(uint64_t ofs, size_t len, bufferlist& bl)
  {  
      //…………………
   ImageCtx *ictx = (ImageCtx *)ctx;
    int r = librbd::write(ictx, ofs, len, bl.c_str(), 0);
    return r;     
  }

 

2. 该函数中直接进行分发给了librbd::wrte的函数了。跟随下来看看librbd::write中的实现。该函数的具体实现在internal.cc文件中。

ssize_t write(ImageCtx *ictx, uint64_t off, size_t len, const char *buf, int op_flags)
 {
      ……………
    Context *ctx = new C_SafeCond(&mylock, &cond, &done, &ret);   //---a
    AioCompletion *c = aio_create_completion_internal(ctx, rbd_ctx_cb);//---b
   r = aio_write(ictx, off, mylen, buf, c, op_flags);  //---c
     ……………     
    while (!done)
           cond.Wait(mylock);  // ---d
      ……………
}

 

---a.这句要为这个操作申请一个回调操作,所谓的回调就是一些收尾的工作,信号唤醒处理。

---b。这句是要申请一个io完成时 要进行的操作,当io完成时,会调用rbd_ctx_cb函数,该函数会继续调用ctx->complete()。

---c.该函数aio_write会继续处理这个请求。

---d.当c句将这个io下发到osd的时候,osd还没请求处理完成,则等待在d上,直到底层处理完请求,回调b申请的 AioCompletion, 继续调用a中的ctx->complete(),唤醒这里的等待信号,然后程序继续向下执行。

3.再来看看aio_write 拿到了 请求的offset和buffer会做点什么呢?

int aio_write(ImageCtx *ictx, uint64_t off, size_t len, const char *buf,
           AioCompletion *c, int op_flags)
  {
      ………
      //将请求按着object进行拆分
      vector<ObjectExtent> extents;
     if (len > 0) 
      {
         Striper::file_to_extents(ictx->cct, ictx->format_string,
                        &ictx->layout, off, clip_len, 0, extents);   //---a
      } 
      //处理每一个object上的请求数据
      for (vector<ObjectExtent>::iterator p = extents.begin(); p != extents.end(); ++p) 
      {
               ……..
           C_AioWrite *req_comp = new C_AioWrite(cct, c); //---b
           ……..
           AioWrite *req = new AioWrite(ictx, p->oid.name, p->objectno, p- >offset,bl,….., req_comp);     //---c
           r = req->send();    //---d
           …….
      }
      ……
}

 

根据请求的大小需要将这个请求按着object进行划分,由函数file_to_extents进行处理,处理完成后按着object进行保存在extents中。file_to_extents()存在很多同名函数注意区分。这些函数的主要内容做了一件事儿,那就对原始请求的拆分。

一个rbd设备是有很多的object组成,也就是将rbd设备进行切块,每一个块叫做object,每个object的大小默认为4M,也可以自己指定。file_to_extents函数将这个大的请求分别映射到object上去,拆成了很多小的请求如下图。最后映射的结果保存在ObjectExtent中。

 

 

原本的offset是指在rbd内的偏移量(写入rbd的位置),经过file_to_extents后,转化成了一个或者多个object的内部的偏移量offset0。这样转化后处理一批这个object内的请求。

4. 再回到 aio_write函数中,需要将拆分后的每一个object请求进行处理。

---b.为写请求申请一个回调处理函数。

---c.根据object内部的请求,创建一个叫做AioWrite的结构。

---d.将这个AioWrite的req进行下发send().

5. 这里AioWrite 是继承自 AbstractWrite ,AbstractWrite 继承自AioRequest类,在AbstractWrite 类中定义了send的方法,看下send的具体内容.

int AbstractWrite::send() 

 {  ………………

    if (send_pre())           //---a

      ……………

}

#进入send_pre()函数中

bool AbstractWrite::send_pre()

{

      m_state = LIBRBD_AIO_WRITE_PRE;   // ----a

      FunctionContext *ctx =    //----b

           new FunctionContext( boost::bind(&AioRequest::complete, this, _1));

      m_ictx->object_map.aio_update(ctx); //-----c

}

 

---a.修改m_state 状态为LIBRBD_AIO_WRITE_PRE。

---b.申请一个回调函数,实际调用AioRequest::complete()

---c.开始下发object_map.aio_update的请求,这是一个状态更新的函数,不是很重要的环节,这里不再多说,当更新的请求完成时会自动回调到b申请的回调函数。

6. 进入到AioRequest::complete() 函数中。

void AioRequest::complete(int r)
 {
    if (should_complete(r))   //---a
        …….
}

 

---a.should_complete函数是一个纯虚函数,需要在继承类AbstractWrite中实现,来7. 看看AbstractWrite:: should_complete()

bool AbstractWrite::should_complete(int r)
{
    switch (m_state) 
  {
          case LIBRBD_AIO_WRITE_PRE:  //----a

      {

                     send_write(); //----b

 

----a.在send_pre中已经设置m_state的状态为LIBRBD_AIO_WRITE_PRE,所以会走这个分支。

----b. send_write()函数中,会继续进行处理,

7.1.下面来看这个send_write函数

void AbstractWrite::send_write()
{
      m_state = LIBRBD_AIO_WRITE_FLAT;   //----a

      add_write_ops(&m_write);    // ----b

      int r = m_ictx->data_ctx.aio_operate(m_oid, rados_completion, &m_write);

}

 

---a.重新设置m_state的状态为 LIBRBD_AIO_WRITE_FLAT。

---b.填充m_write,将请求转化为m_write。

---c.下发m_write  ,使用data_ctx.aio_operate 函数处理。继续调用io_ctx_impl->aio_operate()函数,继续调用objecter->mutate().

8. objecter->mutate()

ceph_tid_t mutate(……..) 
  {
    Op *o = prepare_mutate_op(oid, oloc, op, snapc, mtime, flags, onack, oncommit, objver);   //----d
    return op_submit(o);
  }

 

---d.将请求转化为Op请求,继续使用op_submit下发这个请求。在op_submit中继续调用_op_submit_with_budget处理请求。继续调用_op_submit处理。

8.1 _op_submit 的处理过程。这里值得细看

ceph_tid_t Objecter::_op_submit(Op *op, RWLock::Context& lc)
{
    check_for_latest_map = _calc_target(&op->target, &op->last_force_resend); //---a
    int r = _get_session(op->target.osd, &s, lc);  //---b
    _session_op_assign(s, op); //----c
    _send_op(op, m); //----d
}

 

----a. _calc_target,通过计算当前object的保存的osd,然后将主osd保存在target中,rbd写数据都是先发送到主osd,主osd再将数据发送到其他的副本osd上。这里对于怎么来选取osd集合与主osd的关系就不再多说,在《ceph的数据存储之路(3)》中已经讲述这个过程的原理了,代码部分不难理解。

----b. _get_session,该函数是用来与主osd建立通信的,建立通信后,可以通过该通道发送给主osd。再来看看这个函数是怎么处理的

9. _get_session

int Objecter::_get_session(int osd, OSDSession **session, RWLock::Context& lc)
{
    map<int,OSDSession*>::iterator p = osd_sessions.find(osd);   //----a
    OSDSession *s = new OSDSession(cct, osd); //----b
    osd_sessions[osd] = s;//--c
    s->con = messenger->get_connection(osdmap->get_inst(osd));//-d
    ………
}

 

----a.首先在osd_sessions中查找是否已经存在一个连接可以直接使用,第一次通信是没有的。

----b.重新申请一个OSDSession,并且使用osd等信息进行初始化。

---c. 将新申请的OSDSession添加到osd_sessions中保存,以备下次使用。

----d.调用messager的get_connection方法。在该方法中继续想办法与目标osd建立连接。

10. messager 是由子类simpleMessager实现的,下面来看下SimpleMessager中get_connection的实现方法

ConnectionRef SimpleMessenger::get_connection(const entity_inst_t& dest)
{
    Pipe *pipe = _lookup_pipe(dest.addr);     //-----a
    if (pipe) 
    {
        ……
    } 
    else 
    {
      pipe = connect_rank(dest.addr, dest.name.type(), NULL, NULL); //----b
    }
}

 

----a.首先要查找这个pipe,第一次通信,自然这个pipe是不存在的。

----b. connect_rank 会根据这个目标osd的addr进行创建。看下connect_rank做了什么。

11. SimpleMessenger::connect_rank

Pipe *SimpleMessenger::connect_rank(const entity_addr_t& addr,  int type, PipeConnection *con,    Message *first)

{
      Pipe *pipe = new Pipe(this, Pipe::STATE_CONNECTING, static_cast<PipeConnection*>(con));      //----a
      pipe->set_peer_type(type); //----b
      pipe->set_peer_addr(addr); //----c
      pipe->policy = get_policy(type); //----d
      pipe->start_writer();  //----e
      return pipe; //----f
}

 

----a.首先需要创建这个pipe,并且pipe同pipecon进行关联。

----b,----c,-----d。都是进行一些参数的设置。

----e.开始启动pipe的写线程,这里pipe的写线程的处理函数pipe->writer(),该函数中会尝试连接osd。并且建立socket连接通道。

目前的资源统计一下,写请求可以根据目标主osd,去查找或者建立一个OSDSession,这个OSDSession中会有一个管理数据通道的Pipe结构,然后这个结构中存在一个发送消息的处理线程writer,这个线程会保持与目标osd的socket通信。

12. 建立并且获取到了这些资源,这时再回到_op_submit 函数中

ceph_tid_t Objecter::_op_submit(Op *op, RWLock::Context& lc)
{
    check_for_latest_map = _calc_target(&op->target, &op->last_force_resend); //---a
    int r = _get_session(op->target.osd, &s, lc);  //---b
    _session_op_assign(s, op); //----c
    MOSDOp *m = _prepare_osd_op(op); //-----d
    _send_op(op, m); //----e
}

 

---c,将当前的op请求与这个session进行绑定,在后面发送请求的时候能知道使用哪一个session进行发送。

--d,将op转化为MOSDop,后面会以MOSDOp为对象进行处理的。

---e,_send_op 会根据之前建立的通信通道,将这个MOSDOp发送出去。_send_op 中调用op->session->con->send_message(m),这个方法会调用SimpleMessager-> send_message(m), 再调用_send_message(),再调用submit_message().在submit_message会找到之前的pipe,然后调用pipe->send方法,最后通过pipe->writer的线程发送到目标osd。

 

自此,客户就等待osd处理完成返回结果了。

总结客户端的所有流程和数据结构,下面来看下客户端的所有结构图。

 

 

通过这个全部的结构图来总结客户端的处理过程。

1.看左上角的rados结构,首先创建io环境,创建rados信息,将配置文件中的数据结构化到rados中。

2.根据rados创建一个radosclient的客户端结构,该结构包括了三个重要的模块,finiser 回调处理线程、Messager消息处理结构、Objector数据处理结构。最后的数据都是要封装成消息 通过Messager发送给目标的osd。

3.根据pool的信息与radosclient进行创建一个ioctx,这里面包好了pool相关的信息,然后获得这些信息后在数据处理时会用到。

4.紧接着会复制这个ioctx到imagectx中,变成data_ioctx与md_ioctx数据处理通道,最后将imagectx封装到image结构当中。之后所有的写操作都会通过这个image进行。顺着image的结构可以找到前面创建并且可以使用的数据结构。

5.通过最右上角的image进行读写操作,当读写操作的对象为image时,这个image会开始处理请求,然后这个请求经过处理拆分成object对象的请求。拆分后会交给objector进行处理查找目标osd,当然这里使用的就是crush算法,找到目标osd的集合与主osd。

6.将请求op封装成MOSDOp消息,然后交给SimpleMessager处理,SimpleMessager会尝试在已有的osd_session中查找,如果没有找到对应的session,则会重新创建一个OSDSession,并且为这个OSDSession创建一个数据通道pipe,把数据通道保存在SimpleMessager中,可以下次使用。

7.pipe 会与目标osd建立Socket通信通道,pipe会有专门的写线程writer来负责socket通信。在线程writer中会先连接目标ip,建立通信。消息从SimpleMessager收到后会保存到pipe的outq队列中,writer线程另外的一个用途就是监视这个outq队列,当队列中存在消息等待发送时,会就将消息写入socket,发送给目标OSD。

8. 等待OSD将数据消息处理完成之后,就是进行回调,反馈执行结果,然后一步步的将结果告知调用者。

上面是就rbd client处理写请求的过程,那么下面会在分析一个OSD是如何接到请求,并且怎么来处理这个请求的。请期待下一节。

© 著作权归作者所有

一只小江
粉丝 102
博文 21
码字总数 51352
作品 0
杭州
程序员
私信 提问
加载中

评论(23)

a
a45989876

引用来自“十年一刻”的评论

非常感谢。 object在osd上是一个文件, 如果我写入你提到的这两个文件(分别是1M和2M),是不是在OSD底层存的时候都存在Object.0这个文件中? 那么如果我要读第二个文件(2M这个文件)时,系统会指定哪个对象,偏移是多少,然后读出来?
在您最后提到的文件空洞,是因为之前在rbd写时指定的偏移造成的?谢谢:)

引用来自“一只小江”的评论

1. 需要先了解块存储与文件存储的区别。块存储主要是存储二进制数据,所以是必须要指明存储偏移,读取数据时也必须指定读取偏移,而文件系统是用来存储文件,文件的存储偏移由文件系统处理,所以不需要用户指定。rbd对于使用者来说,rbd是一个块设备,如果要使用必须指明存储偏移。。。。rbd内部的实现,是采用了映射成文件的方式。要区分开rbd的使用 和 内部实现、块存储 和 文件存储。 2. rbd 的读取必须要指明偏移,它不知道你要读取第几个文件。 3. 块设备任何位置都是可以被读取的(如果通过文件系统只能读取到文件的位置),设置文件空洞,就是避免对rbd设备读取时,读取到没有写过的区域。

引用来自“a45989876”的评论

你好,请教下,w2(offset=2M,length=1M),必须指定offset的话,怎么知道它是多少,在W1的1M之后?

引用来自“一只小江”的评论

你的这个问题说明对块设备的读写不太熟悉,建议着重看下这方面。比如别人给你一个块设备(大小为100M),那你可以在这个100M上任意offset位置写操作,所以i你想写哪里是你自己决定的(比如dd一个块设备)。如果在这个块设备做了文件系统ext4.并且可以把这个文件系统mount到 /mnt/block_ext4 目录。然后在这个目录下创建文件,该文件的位置由ext4文件系统去分配位置,你无需管理。如果你直接写块设备,那么没人负责给你分配位置,你必须自己指定offset。如果你不想使用文件系统,那你必须建立自己的元数据管理来分配块设备的空间,这样才能合理使用块设备存储。希望对你有所帮助
感谢大神回答我的问题。我刚接触这块东西,确实不太熟悉,正在学习中。
一只小江
一只小江 博主

引用来自“十年一刻”的评论

非常感谢。 object在osd上是一个文件, 如果我写入你提到的这两个文件(分别是1M和2M),是不是在OSD底层存的时候都存在Object.0这个文件中? 那么如果我要读第二个文件(2M这个文件)时,系统会指定哪个对象,偏移是多少,然后读出来?
在您最后提到的文件空洞,是因为之前在rbd写时指定的偏移造成的?谢谢:)

引用来自“一只小江”的评论

1. 需要先了解块存储与文件存储的区别。块存储主要是存储二进制数据,所以是必须要指明存储偏移,读取数据时也必须指定读取偏移,而文件系统是用来存储文件,文件的存储偏移由文件系统处理,所以不需要用户指定。rbd对于使用者来说,rbd是一个块设备,如果要使用必须指明存储偏移。。。。rbd内部的实现,是采用了映射成文件的方式。要区分开rbd的使用 和 内部实现、块存储 和 文件存储。 2. rbd 的读取必须要指明偏移,它不知道你要读取第几个文件。 3. 块设备任何位置都是可以被读取的(如果通过文件系统只能读取到文件的位置),设置文件空洞,就是避免对rbd设备读取时,读取到没有写过的区域。

引用来自“a45989876”的评论

你好,请教下,w2(offset=2M,length=1M),必须指定offset的话,怎么知道它是多少,在W1的1M之后?
你的这个问题说明对块设备的读写不太熟悉,建议着重看下这方面。比如别人给你一个块设备(大小为100M),那你可以在这个100M上任意offset位置写操作,所以i你想写哪里是你自己决定的(比如dd一个块设备)。如果在这个块设备做了文件系统ext4.并且可以把这个文件系统mount到 /mnt/block_ext4 目录。然后在这个目录下创建文件,该文件的位置由ext4文件系统去分配位置,你无需管理。如果你直接写块设备,那么没人负责给你分配位置,你必须自己指定offset。如果你不想使用文件系统,那你必须建立自己的元数据管理来分配块设备的空间,这样才能合理使用块设备存储。希望对你有所帮助
a
a45989876

引用来自“十年一刻”的评论

非常感谢。 object在osd上是一个文件, 如果我写入你提到的这两个文件(分别是1M和2M),是不是在OSD底层存的时候都存在Object.0这个文件中? 那么如果我要读第二个文件(2M这个文件)时,系统会指定哪个对象,偏移是多少,然后读出来?
在您最后提到的文件空洞,是因为之前在rbd写时指定的偏移造成的?谢谢:)

引用来自“一只小江”的评论

1. 需要先了解块存储与文件存储的区别。块存储主要是存储二进制数据,所以是必须要指明存储偏移,读取数据时也必须指定读取偏移,而文件系统是用来存储文件,文件的存储偏移由文件系统处理,所以不需要用户指定。rbd对于使用者来说,rbd是一个块设备,如果要使用必须指明存储偏移。。。。rbd内部的实现,是采用了映射成文件的方式。要区分开rbd的使用 和 内部实现、块存储 和 文件存储。 2. rbd 的读取必须要指明偏移,它不知道你要读取第几个文件。 3. 块设备任何位置都是可以被读取的(如果通过文件系统只能读取到文件的位置),设置文件空洞,就是避免对rbd设备读取时,读取到没有写过的区域。
你好,请教下,w2(offset=2M,length=1M),必须指定offset的话,怎么知道它是多少,在W1的1M之后?
a
a45989876
你好,问一下,偏移量要自己算的话,怎么知道要多少?如果要存一个10M的文件,如何分割object,计算偏移和存储?存好以后,取出这个文件,是什么流程?
g
guoys

引用来自“guoys”的评论

过了这么久不知道楼主是否能看见, 如果有时间就帮忙解答一下,我的理解是ceph存储是是通过CRUSH来指定OSD的,但是当一个文件比较大时会分为很多块, 是不是第一份数据的所有块都会落到一个OSD上?

引用来自“一只小江”的评论

1.建议你把这一系列的博客都看下,这里面有几个虚拟层:用户文件--->文件切块成为object(有很多中切割方法,可以配置)--->object 映射到pg----->pg通过crush算法找到osd----->最后保存在osd里面。
2.还有你的问题我不太懂? 文件切分很多块,第一份数据的所有块 是什么意思?两个块是不是同一个概念?
3. 用户文件切分成n个object,每个object都会通过hash算法映射到可能不同的pg,每个pg通过crush算法找到osd。pg找到的osd个数,通常会配置2~3个,不会只保存在一个osd上。

感谢你的回答。刚入门,可能某些问题太过肤浅,我会好好看一下你的博客(不得不说你的博客写的很赞)
关于2中的“第一份数据”是指当文件备份为3时,第一份数据。
虽然问的不太清楚,不过博主的1和3解决了我的问题。 我的问题基本上是:能否控制存入的数据存放在某个OSD上
一只小江
一只小江 博主

引用来自“guoys”的评论

过了这么久不知道楼主是否能看见, 如果有时间就帮忙解答一下,我的理解是ceph存储是是通过CRUSH来指定OSD的,但是当一个文件比较大时会分为很多块, 是不是第一份数据的所有块都会落到一个OSD上?
1.建议你把这一系列的博客都看下,这里面有几个虚拟层:用户文件--->文件切块成为object(有很多中切割方法,可以配置)--->object 映射到pg----->pg通过crush算法找到osd----->最后保存在osd里面。
2.还有你的问题我不太懂? 文件切分很多块,第一份数据的所有块 是什么意思?两个块是不是同一个概念?
3. 用户文件切分成n个object,每个object都会通过hash算法映射到可能不同的pg,每个pg通过crush算法找到osd。pg找到的osd个数,通常会配置2~3个,不会只保存在一个osd上。

g
guoys
过了这么久不知道楼主是否能看见, 如果有时间就帮忙解答一下,我的理解是ceph存储是是通过CRUSH来指定OSD的,但是当一个文件比较大时会分为很多块, 是不是第一份数据的所有块都会落到一个OSD上?
漂泊的灯

引用来自“漂泊的灯”的评论

1.“file_to_extents函数将这个大的请求分别映射到object上去,拆成了很多小的请求”,很多个请求在写的过程中失败一个会怎么处理呢?之前已经写入到osd的数据怎么处理?会销毁吗?
2.多客户端并发的情况,同一个OSD可能会积累一些数据需要处理,好像没有任务均衡,这里是不是性能的瓶颈呢?
请帮忙回答下,大神!0

引用来自“一只小江”的评论

1.用户的写入的是最大的请求R,然后根据object进行拆分(r1~rn),这时用户仍然只是关心R,如果其中一个r失败了,会将结果反映在R上,也就是R是不成功的,如果上层是文件系统,则不会记录该文件修改成功,也不会销毁已经写入的数据(已经写入磁盘的数据该怎么销毁?重新覆盖? 都是脏数据就无所谓了,所以不需要再次覆盖)。如果上层是块设备直接使用,也不需要销毁数据。 2. 分布式的存储很重要一方面就是解决单个节点压力过大。这里已经存在条带的划分处理,一个请求会被拆分成多个object请求,object又会分散到pg中,每个pg又随机选取了主osd。有效的分散了业务的压力。如果你在测试中出现了这种问题,可以把测试的情况发出来一起看一下。

引用来自“漂泊的灯”的评论

1. 还是第一个问题,我们现在使用的上层是块设备直接使用。按照你的说法是已经成功写入的数据无法改变。也就是说用户将会继续使用这个脏数据? 2. 用户增删改查的偏移量是谁计算的?对于修改而言,是不是把数据先读上来再覆盖的写下去?

引用来自“一只小江”的评论

1.如果是块设备直接使用,用户写入的数据部分成功的情况,已经写入的数据无法改变,用户要自己保证数据的正确性(比如说 用户在管理上可以认为这就是一块脏数据,不会读取这部分数据)。2.增删改查 是数据库等上层软件实现的逻辑,块设备不负责这部分实现,块设备提供基本的读写操作(任意位置都可以读写),上层软件实现修改操作时也不会事先读取数据,而是采用日志的形式保证数据的一致性。对于读写操作的偏移量必须由使用者指定,块设备对外提供容量,用户可以使用任意位置,块设备层不干预。
恩,明白了,谢谢你。很高兴拜读了您的文章,写的很好。我之前也是做C的,现在刚刚接触ceph。可能会问一下小白的问题,还希望能够帮忙解答下。 1. 对于对象存储,上面的说法有没有区别呢,对象存储也是这样对待脏数据的吗?我对对象存储和块存储在概率上没有本质的区分开。 2. 写入数据时,primary OSD故障了怎么办?有没有超时机制?按照之前的说法的话不管primary 还是secondary OSD写入的数据还是成为了脏数据?
一只小江
一只小江 博主

引用来自“漂泊的灯”的评论

1.“file_to_extents函数将这个大的请求分别映射到object上去,拆成了很多小的请求”,很多个请求在写的过程中失败一个会怎么处理呢?之前已经写入到osd的数据怎么处理?会销毁吗?
2.多客户端并发的情况,同一个OSD可能会积累一些数据需要处理,好像没有任务均衡,这里是不是性能的瓶颈呢?
请帮忙回答下,大神!0

引用来自“一只小江”的评论

1.用户的写入的是最大的请求R,然后根据object进行拆分(r1~rn),这时用户仍然只是关心R,如果其中一个r失败了,会将结果反映在R上,也就是R是不成功的,如果上层是文件系统,则不会记录该文件修改成功,也不会销毁已经写入的数据(已经写入磁盘的数据该怎么销毁?重新覆盖? 都是脏数据就无所谓了,所以不需要再次覆盖)。如果上层是块设备直接使用,也不需要销毁数据。 2. 分布式的存储很重要一方面就是解决单个节点压力过大。这里已经存在条带的划分处理,一个请求会被拆分成多个object请求,object又会分散到pg中,每个pg又随机选取了主osd。有效的分散了业务的压力。如果你在测试中出现了这种问题,可以把测试的情况发出来一起看一下。

引用来自“漂泊的灯”的评论

1. 还是第一个问题,我们现在使用的上层是块设备直接使用。按照你的说法是已经成功写入的数据无法改变。也就是说用户将会继续使用这个脏数据? 2. 用户增删改查的偏移量是谁计算的?对于修改而言,是不是把数据先读上来再覆盖的写下去?
1.如果是块设备直接使用,用户写入的数据部分成功的情况,已经写入的数据无法改变,用户要自己保证数据的正确性(比如说 用户在管理上可以认为这就是一块脏数据,不会读取这部分数据)。2.增删改查 是数据库等上层软件实现的逻辑,块设备不负责这部分实现,块设备提供基本的读写操作(任意位置都可以读写),上层软件实现修改操作时也不会事先读取数据,而是采用日志的形式保证数据的一致性。对于读写操作的偏移量必须由使用者指定,块设备对外提供容量,用户可以使用任意位置,块设备层不干预。
漂泊的灯

引用来自“漂泊的灯”的评论

1.“file_to_extents函数将这个大的请求分别映射到object上去,拆成了很多小的请求”,很多个请求在写的过程中失败一个会怎么处理呢?之前已经写入到osd的数据怎么处理?会销毁吗?
2.多客户端并发的情况,同一个OSD可能会积累一些数据需要处理,好像没有任务均衡,这里是不是性能的瓶颈呢?
请帮忙回答下,大神!0

引用来自“一只小江”的评论

1.用户的写入的是最大的请求R,然后根据object进行拆分(r1~rn),这时用户仍然只是关心R,如果其中一个r失败了,会将结果反映在R上,也就是R是不成功的,如果上层是文件系统,则不会记录该文件修改成功,也不会销毁已经写入的数据(已经写入磁盘的数据该怎么销毁?重新覆盖? 都是脏数据就无所谓了,所以不需要再次覆盖)。如果上层是块设备直接使用,也不需要销毁数据。 2. 分布式的存储很重要一方面就是解决单个节点压力过大。这里已经存在条带的划分处理,一个请求会被拆分成多个object请求,object又会分散到pg中,每个pg又随机选取了主osd。有效的分散了业务的压力。如果你在测试中出现了这种问题,可以把测试的情况发出来一起看一下。
1. 还是第一个问题,我们现在使用的上层是块设备直接使用。按照你的说法是已经成功写入的数据无法改变。也就是说用户将会继续使用这个脏数据? 2. 用户增删改查的偏移量是谁计算的?对于修改而言,是不是把数据先读上来再覆盖的写下去?
ceph的数据存储之路(1) ---rbd设备介绍

由这几个问题开始思考。。。 问题0,数据的存储设备? 数据的存储有3种形式,1种是直接以二进制数据的形式存储在裸设备(包括块设备)上,另外一种是以文件的形式经过文件系统管理进行存储。...

一只小江
2015/11/17
3.5K
1
ceph的数据存储之路(2) ----- rbd到osd的数据映射

说明:先建立一个ceph集群,这个集群有3个monitor节点、多个OSD节点,然后这个上面有个存储池,每个存储中的对象都保留3个副本。这时如果发下一个写request则会经过如下步骤。 A . 客户端的使...

一只小江
2015/11/17
4K
3
ceph块存储rbd介绍

ceph集群搭建起来后,可以在ceph集群上进行块存储、对象存储以及文件系统存储。从架构上来看,在ceph集群的上面是rados协议,该协议为使用ceph集群的用户提供必要的支持(ceph用户通过调用r...

linuxhunter
2015/12/10
5.2K
2
Ceph与OpenStack整合(与glance整合)

2. Ceph与OpenStack整合(与glance整合) 创建: linhaifeng,最新修改: 昨天4:18 下午 思路:1.ceph集群monitor节点创建存储池images-pool,创建访问该存储池的用户images,导出秘钥文件cep...

linhaifeng4573
2016/05/26
0
0
CEPH LIO iSCSI Gateway

参考文档: Ceph Block Device:http://docs.ceph.com/docs/master/rbd/ CEPH ISCSI GATEWAY:http://docs.ceph.com/docs/master/rbd/iscsi-overview/ USING AN ISCSI GATEWAY:https://acc......

Netonline
02/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

浅谈prototype原型模式

一、原型模式简介 原型(Prototype)模式是一种对象创建型模式,他采取复制原型对象的方法来创建对象的实例。使用原型模式创建的实例,具有与原型一样的数据。 原型模式的特点: 1、由原型对...

青衣霓裳
8分钟前
2
0
shell mysql 备份

#!/bin/bash time2=$(date "+%Y-%m-%d-%H:%M:%S") /usr/local/mysql/bin/mysqldump -uroot -p ad > /usr/local/mysql/backup/"$time2".sql 变量引用原来是这么用的。......

奋斗的小牛
15分钟前
3
0
Jmeter监控Linux服务器操作

系统:Win7 64位 工具:Jmeter 4.0 要准备好的插件:JMeterPlugins-Standard-1.4.0,ServerAgent-2.2.1 解压JMeterPlugins-Standard-1.4.0.zip,将其中\lib\ext\JMeterPlugins-Standard.jar......

魔鬼妹子
16分钟前
4
0
系列文章:云原生Kubernetes日志落地方案

在Logging这块做了几年,最近1年来越来越多的同学来咨询如何为Kubernetes构建一个日志系统或者是来求助在这过程中遇到一系列问题如何解决,授人以鱼不如授人以渔,于是想把我们这些年积累的经...

Mr_zebra
16分钟前
3
0
入门必备!快速学会用Aspose.Words在表格中插入和删除列!

Aspose.Words For .Net(点击下载)是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和打印文档,而无需在跨平台应用程序中直接使用Microsoft W...

mnrssj
21分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部