Ceph源碼解析:讀寫流程

来源:http://www.cnblogs.com/chenxianpao/archive/2016/06/09/5572859.html
-Advertisement-
Play Games

一、OSD模塊簡介 1.1 消息封裝:在OSD上發送和接收信息。 cluster_messenger -與其它OSDs和monitors溝通 client_messenger -與客戶端溝通 1.2 消息調度: Dispatcher類,主要負責消息分類 1.3 工作隊列: 1.3.1 OpWQ: 處 ...


一、OSD模塊簡介

1.1 消息封裝:在OSD上發送和接收信息。

cluster_messenger -與其它OSDs和monitors溝通
client_messenger -與客戶端溝通

1.2 消息調度

Dispatcher類,主要負責消息分類

1.3 工作隊列:

1.3.1 OpWQ: 處理ops(從客戶端)和sub ops(從其他的OSD)。運行在op_tp線程池。

1.3.2 PeeringWQ: 處理peering任務,運行在op_tp線程池。

1.3.3 CommandWQ:處理cmd命令,運行在command_tp。

1.3.4 RecoveryWQ: 數據修複,運行在recovery_tp。

1.3.5 SnapTrimWQ: 快照相關,運行在disk_tp。

1.3.6 ScrubWQ: scrub,運行在disk_tp。

1.3.7 ScrubFinalizeWQ: scrub,運行在disk_tp。

1.3.8 RepScrubWQ: scrub,運行在disk_tp。

1.3.9 RemoveWQ: 刪除舊的pg目錄。運行在disk_tp。

1.4 線程池:

有4種OSD線程池:

1.4.1 op_tp: 處理ops和sub ops

1.4.2 recovery_tp:處理修複任務

1.4.3 disk_tp: 處理磁碟密集型任務

1.4.4 command_tp: 處理命令

1.5 主要對象:

ObjectStore *store;

OSDSuperblock superblock; 主要是版本號等信息

OSDMapRef  osdmap;

1.6 主要操作流程: 參考文章

1.6.1 客戶端發起請求過程

1.6.2 op_tp線程處理數據讀取

1.6.3 對象操作的處理過程

1.6.4 修改操作的處理

1.6.5 日誌的寫入

1.6.6 寫操作處理

1.6.7 事務的sync過程

1.6.8 日誌恢復

1.7 整體處理過程圖

 

                      ba55bd649e315fc12986a2410b00b080

二、客戶端寫入數據大致流程及保存形式

2.1 讀寫框架

                                             image

                  imageimage

2.2 客戶端寫入流程

在客戶端使用 rbd 時一般有兩種方法:

  • 第一種 是 Kernel rbd。就是創建了rbd設備後,把rbd設備map到內核中,形成一個虛擬的塊設備,這時這個塊設備同其他通用塊設備一樣,一般的設備文件為/dev/rbd0,後續直接使用這個塊設備文件就可以了,可以把 /dev/rbd0 格式化後 mount 到某個目錄,也可以直接作為裸設備使用。這時對rbd設備的操作都通過kernel rbd操作方法進行的。 
  • 第二種是 librbd 方式。就是創建了rbd設備後,這時可以使用librbd、librados庫進行訪問管理塊設備。這種方式不會map到內核,直接調用librbd提供的介面,可以實現對rbd設備的訪問和管理,但是不會在客戶端產生塊設備文件。

應用寫入rbd塊設備的過程:

  1. 應用調用 librbd 介面或者對linux 內核虛擬塊設備寫入二進位塊。下麵以 librbd 為例。
  2. librbd 對二進位塊進行分塊,預設塊大小為 4M,每一塊都有名字,成為一個對象
  3. librbd 調用 librados 將對象寫入 Ceph 集群
  4. librados 向主 OSD 寫入分好塊的二進位數據塊 (先建立TCP/IP連接,然後發送消息給 OSD,OSD 接收後寫入其磁碟)
  5. 主 OSD 負責同時向一個或者多個次 OSD 寫入副本。註意這裡是寫到日誌(Journal)就返回,因此,使用SSD作為Journal的話,可以提高響應速度,做到伺服器端對客戶端的快速同步返回寫結果(ack)。
  6. 當主次OSD都寫入完成後,主 OSD 向客戶端返回寫入成功。
  7. 當一段時間(也許得幾秒鐘)後Journal 中的數據向磁碟寫入成功後,Ceph通過事件通知客戶端數據寫入磁碟成功(commit),此時,客戶端可以將寫緩存中的數據徹底清除掉了。
  8. 預設地,Ceph 客戶端會緩存寫入的數據直到收到集群的commit通知。如果此階段內(在寫方法返回到收到commit通知之間)OSD 出故障導致數據寫入文件系統失敗,Ceph 將會允許客戶端重做尚未提交的操作(replay)。因此,PG 有個狀態叫 replay:“The placement group is waiting for clients to replay operations after an OSD crashed.”。

                                                      

也就是,文件系統負責文件處理,librbd 負責塊處理,librados 負責對象處理,OSD 負責將數據寫入在Journal和磁碟中。

2.3 RBD保存形式

如下圖所示,Ceph 系統中不同層次的組件/用戶所看到的數據的形式是不一樣的:

                        

  • Ceph 客戶端所見的是一個完整的連續的二進位數據塊,其大小為創建 RBD image 是設置的大小或者 resize 的大小,客戶端可以從頭或者從某個位置開始寫入二進位數據。
  • librados 負責在 RADOS 中創建對象(object),其大小為 pool 的 order 決定,預設情況下 order = 22 此時 object 大小為 4MB;以及負責將客戶端傳入的二進位塊條帶化為若幹個條帶(stripe)。
  • librados 控制哪個條帶由哪個 OSD 寫入(條帶 ---寫入哪個----> object ----位於哪個 ----> OSD)
  • OSD 負責創建在文件系統中創建文件,並將 librados 傳入的數據寫入數據。

  Ceph client 向一個 RBD image 寫入二進位數據(假設 pool 的拷貝份數為 3):

(1)Ceph client 調用 librados 創建一個 RBD image,這時候不會做存儲空間分配,而是創建若幹元數據對象來保存元數據信息。

(2)Ceph client 調用 librados 開始寫數據。librados 計算條帶、object 等,然後開始寫第一個 stripe 到特定的目標 object。

(3)librados 根據 CRUSH 演算法,計算出 object 所對應的主 OSD ID,並將二進位數據發給它。

(4)主 OSD 負責調用文件系統介面將二進位數據寫入磁碟上的文件(每個 object 對應一個 file,file 的內容是一個或者多個 stripe)。

(5)主 ODS 完成數據寫入後,它使用 CRUSH 算啊計算出第二個OSD(secondary OSD)和第三個OSD(tertiary OSD)的位置,然後向這兩個 OSD 拷貝對象。都完成後,它向 ceph client 反饋該 object 保存完畢。

                                                        

(6)然後寫第二個條帶,直到全部寫入完成。全部完成後,librados 還應該會做元數據更新,比如寫入新的 size 等。

完整的過程(來源):

                              

該過程具有強一致性的特點:

  • Ceph 的讀寫操作採用 Primary-Replica 模型,Client 只向 Object 所對應 OSD set 的 Primary 發起讀寫請求,這保證了數據的強一致性。
  • 由於每個 Object 都只有一個 Primary OSD,因此對 Object 的更新都是順序的,不存在同步問題。
  • 當 Primary 收到 Object 的寫請求時,它負責把數據發送給其他 Replicas,只要這個數據被保存在所有的OSD上時,Primary 才應答Object的寫請求,這保證了副本的一致性。這也帶來一些副作用。相比那些只實現了最終一致性的存儲系統比如 Swift,Ceph 只有三份拷貝都寫入完成後才算寫入完成,這在出現磁碟損壞時會出現寫延遲增加。
  • 在 OSD 上,在收到數據存放指令後,它會產生2~3個磁碟seek操作:
    • 把寫操作記錄到 OSD 的 Journal 文件上(Journal是為了保證寫操作的原子性)。
    • 把寫操作更新到 Object 對應的文件上。
    • 把寫操作記錄到 PG Log 文件上。

三、客戶端請求流程(轉的一隻小江的博文,寫的挺好的)

RADOS讀對象流程

                                           image

RADOS寫對象操作流程

                                             image

例子:

#!/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()

1. 首先cluster = rados.Rados(conffile = '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的管理數據。

流程圖:

                            143540_OSPk_2460844

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處理完成返回結果了。

                  151617_dNP0_2460844

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將數據消息處理完成之後,就是進行回調,反饋執行結果,然後一步步的將結果告知調用者。

四、Ceph讀流程

OSD端讀消息分發流程

                                                   image

OSD端讀操作處理流程

                                             image

總體流程圖:

                                        4ac886ce405a7e638b2979b693802a8e

 

int read(inodeno_t ino,
             file_layout_t *layout,
             snapid_t snap,
             uint64_t offset,
             uint64_t len,
             bufferlist *bl,   // ptr to data
             int flags,
             Context *onfinish,
             int op_flags = 0)    --------------------------------Filer.h

Striper::file_to_extents(cct, ino, layout, offset, len, truncate_size, extents);//將要讀取數據的長度和偏移轉化為要訪問的對象,extents沿用了brtfs文件系統的概念
objecter->sg_read_trunc(extents, snap, bl, flags, truncate_size, truncate_seq, onfinish, op_flags);//向osd發起請求

對於讀操作而言:

1.客戶端直接計算出存儲數據所屬於的主osd,直接給主osd上發送消息。

2.主osd收到消息後,可以調用Filestore直接讀取處在底層文件系統中的主pg裡面的內容然後返回給客戶端。具體調用函數在ReplicatedPG::do_osd_ops中實現。

CEPH_OSD_OP_MAPEXT||CEPH_OSD_OP_SPARSE_READ

r = osd->store->fiemap(coll, soid, op.extent.offset, op.extent.length, bl);

CEPH_OSD_OP_READ

r = pgbackend->objects_read_sync(soid, miter->first, miter->second, &tmpbl);

五、Ceph寫流程

OSD端寫操作處理流程

                                   image

而對於寫操作而言,由於要保證數據寫入的同步性就會複雜很多:

1.首先客戶端會將數據發送給主osd,

2.主osd同樣要先進行寫操作預處理,完成後它要發送寫消息給其他的從osd,讓他們對副本pg進行更改,

3.從osd通過FileJournal完成寫操作到Journal中後發送消息告訴主osd說完成,進入5

4.當主osd收到所有的從osd完成寫操作的消息後,會通過FileJournal完成自身的寫操作到Journal中。完成後會通知客戶端,已經完成了寫操作。

5.主osd,從osd的線程開始工作調用Filestore將Journal中的數據寫入到底層文件系統中。

寫的邏輯流程圖如圖:

                     \

從圖中我們可以看到寫操作分為以下幾步:
1.OSD::op_tp線程從OSD::op_wq中拿出來操作如本文開始的圖上描述,具體代碼流是

                     \

    ReplicatePG::apply_repop中創建回調類C_OSD_OpCommit和C_OSD_OpApplied

    FileStore::queue_transactions中創建了回調類C_JournaledAhead

2.FileJournal::write_thread線程從FileJournal::writeq中拿出來操作,主要就是寫數據到具體的journal中,具體代碼流:

                \

3.Journal::Finisher.finisher_thread線程從Journal::Finisher.finish_queue中拿出來操作,通過調用C_JournalAhead留下的回調函數FileStore:_journaled_ahead,該線程開始工作兩件事:首先入底層FileStore::op_wq通知開始寫,再入FileStore::ondisk_finisher.finisher_queue通知可以返回。具體代碼流:

           \

4.FileStore::ondisk_finisher.finisher_thread線程從FileStore::ondisk_finisher.finisher_queue中拿出來操作,通過調用C_OSD_OpCommit留下來的回調函數ReplicatePG::op_commit,通知客戶端寫操作成功

                                     \

5.FileStore::op_tp線程池從FileStore::op_wq中拿出操作(此處的OP_WQ繼承了父類ThreadPool::WorkQueue重寫了_process和_process_finish等函數,所以不同於OSD::op_wq,它有自己的工作流程),首先調用FileStore::_do_op,完成後調用FileStore::_finish_op。

                \

6. FileStore::op_finisher.finisher_thread線程從FileStore::op_finisher.finisher_queue中拿出來操作,通過調用C_OSD_OpApplied留下來的回調函數ReplicatePG::op_applied,通知數據可讀。

                                   

具體OSD方面的源碼逐句解析可以參考一隻小江的博文

此文主要整理了參考資料里的ceph客戶端讀寫流程,OSD端讀寫流程等,使用了參考資料的內容,如果侵犯到參照資料作者的權益,請聯繫我,我會及時刪除相關內容。

參考資料:

http://blog.sina.com.cn/s/blog_c2e1a9c7010151xb.html

作者:ywy463726588  http://blog.csdn.net/ywy463726588/article/details/42676493

                             http://blog.csdn.net/ywy463726588/article/details/42679869

作者:劉世民(Sammy Liu)http://www.cnblogs.com/sammyliu/p/4836014.html

作者:一隻小江 http://my.oschina.net/u/2460844/blog/532755

                   http://my.oschina.net/u/2460844/blog/534390?fromerr=PnkKCbYU

感謝以上作者無私的分享!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如果直接用android的videoview。他是不允許你隨意的修改寬度和高度的,所以我們要重寫videoview! package com.hysmarthotel.view; import android.content.Context;import android.util.Attribute ...
  • [root@localhost ~]# su - oracle [oracle@localhost ~]$ sqlplus /nolog SQL> conn /as sysdba; SQL> show user; USER is "SYS" SQL> select name from v$dataf ...
  • 1. CREATE USER 語法: CREATE USER 'username'@'host' IDENTIFIED BY 'password'; 例子: CREATE USER 'dog'@'localhost' IDENTIFIED BY '123456'; CREATE USER 'pig' ...
  • 首先, 找一臺裝有SQL Server 2008的電腦, 將你的資料庫文件附加到這臺電腦里.附加成功後, 在SSMS的對象資源管理器視窗右鍵單擊剛剛附加的資料庫,依次選"任務>生成腳本...", 此時會彈出腳本嚮導對話框.點"下一步".在"選擇資料庫"對話框選中剛剛附加的資料庫, 同時將底部的"為所 ...
  • 對應關係表 SQL Server 2000 http://hovertree.com/menu/sqlserver/ C# CodeSmith 數據類型 取值範圍 數據類型 取值範圍 空值代替值 數據類型 bigint -2^63 (-9,223,372,036,854,775,807) 至 2^6 ...
  • 這篇博文,主要講解了Redis中的List(列表)的實現原理和命令。 ...
  • 前言:系統優化中一個很重要的方面就是SQL語句的優化。對於海量數據,劣質SQL語句和優質SQL語句之間的速度差別可達到上百倍,可見對於一個系統不是簡單的能實現其功能就可以了,而是要寫出高質量的SQL語句,提高系統的可用性。 在應用系統開發初期,由於開發資料庫數據比較少,對於查詢SQL語句,複雜視圖的 ...
  • 基本概念 數據:描述事物的符號稱為數據,是存儲在資料庫中的基本對象。 資料庫:資料庫是長期存儲在電腦上內的有組織、可共用的數據集合。 資料庫管理系統:用戶和操作系統之間的一層數據管理軟體。主要功能包括如下幾個方面: >1 數據定義功能:通過數據定義語言DDL(Data Definition Lan... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...