Client端代碼分析
通過之前的研究我們知道IoCtxImpl是client端真正實現(xiàn)寫入的方法驻啤,在client端會將所有的操作封裝成ObjectOperation對象。而對應的異步操作,處理更加的復雜一些租幕,如下圖:
同樣的一個操作票堵,在異步接口中先構(gòu)造一個異步的回調(diào)函數(shù)粘茄,然后構(gòu)建異步的op對象先朦,之后將op對象提交到一個隊列中次洼,在rocksdb中主要用到的是同步的接口,所以我們優(yōu)先分析同步接口辛萍。
我們再回到同步接口的分析中悯姊,雖然同步與異步都是構(gòu)建operation對象,但是兩個op是完全不同的:
以上的幾個圖片我們可以看到在同步接口中贩毕,主要使用的是ObjectOperation對象悯许,對這個對象進行一些初步的分析:
在這個對象中主要有如上的一些屬性,以及對象的工具方法辉阶,由于該類也沒有注釋岸晦,從外部來看主要有flags標識符,priority優(yōu)先級睛藻,out_bl是一個bufferlist的集合,這個集合應該是存儲數(shù)據(jù)的主要結(jié)構(gòu)邢隧,ops是在osd_type.h中定義的一個結(jié)構(gòu)店印,這個對象也沒有明確的注釋,估計是存放了對象的object的操作類型倒慧,比如讀按摘,寫,追加等等纫谅,其他兩個屬性的用處還無法猜測炫贤,只能繼續(xù)來進行學習。
按照接下來的邏輯付秕,將op通過prepare構(gòu)造好兰珍,并使用write_full將對象的數(shù)據(jù)寫入op中后,就調(diào)用operate方法來繼續(xù)進行處理询吴,operate這個方法也是所有的同步接口都需要走的一個方法掠河,所以說這個方法是關鍵的方法亮元。
在operate方法中,在開始的地方初始化了一些時間唠摹,并進行了一些校驗工作爆捞,然后就是很關鍵的一個條件變量鎖的使用,這個鎖是一個很典型的鎖勾拉,之前用的很少煮甥,而且這個鎖又很關鍵,因此對這個鎖的一些用法也進行了相關的研究藕赞。
首先條件變量鎖成肘,對應的linux底層函數(shù)為pthread_cond_wait,其實就是上圖中的cond.wait()這個方法找默,這里有一些區(qū)別的原因是ceph對linux底層的函數(shù)進行了封裝艇劫,使之更加易用和安全,條件變量為C_SafeCond這個對象惩激,在這個對象中我們可以看到如下代碼:
在這個方法中店煞,我們看到了條件變量中的另一個關鍵的函數(shù),發(fā)送信號风钻,即cond->signal()顷蟀,這個函數(shù)對應的linux的pthread_cond_signal與pthread_cond_broadcast這兩個函數(shù)。
這個鎖保證了骡技,方法在提交了op之后鸣个,只有op處理結(jié)束,調(diào)用了finish方法布朦,該方法才會繼續(xù)執(zhí)行囤萤,正常結(jié)束,我的理解是ceph在該方法中雖然也用到了隊列是趴,緩存等方法涛舍,但是通過這種方式達到了同步的目的,因此這些接口都是同步接口唆途。
那么這段邏輯大致就可以理解為富雅,方法將op提交到隊列后立即對互斥鎖加鎖,而wait方法執(zhí)行時第一件事就是對互斥鎖解鎖肛搬,然后等待信號量没佑,此時當finish方法獲取鎖之后發(fā)送信號激活主線程,讓主線程繼續(xù)執(zhí)行温赔。
在理解了這段代碼的工作方式后蛤奢,我們需要繼續(xù)看另外一個關鍵的方法,如下圖:
即將初始化好的op對象進行提交。
提交之后远剩,ceph進行了很多層的方法調(diào)用扣溺,這些方法一層一層會記錄一些操作的大體軌跡,流程為op_submit->_op_submit_with_budget->_op_submit瓜晤,其中在_op_submit_with_budget這個方法中有一段代碼值得關注:
這段我的理解是锥余,代碼中osd_timeout是一個配置項,當該配置項大于0時痢掠,代碼會添加一個超時的事件驱犹,當操作執(zhí)行超時會報錯,而我們目前的默認配置就是0足画,也就意味著雄驹,如果有操作超時,那么這個操作就會一直阻塞淹辞,如過是我理解的這樣的話医舆,這個配置項可以優(yōu)化,說不定可以解決我們的丟數(shù)據(jù)問題象缀。
這里為了弄清此處的問題蔬将,需要對ceph中的timer進行一個大概的了解央星,這個方法中的timer類在ceph_timer.h中,這個類也是一個比較復雜的類莉给,調(diào)用機制比較繁瑣毙石,但是從類的注釋中,我們大概可以知道這個類的功能與我們構(gòu)想的類似
接下來就是_op_submit這個函數(shù)的調(diào)用颓遏,這個函數(shù)中做了client端的主要的工作徐矩,
這個方法的開始會根據(jù)op的信息計算出這個對象的目標osd節(jié)點,當計算出目標節(jié)點后叁幢,使用get_session方法獲取一個鏈接來和對應的osd通信丧蘸。
接下來的一大段邏輯判斷,我的理解是主要是對osdmap的同步與校驗遥皂,防止osdmap因某些問題與實際情況不符合,這部分代碼分支很多刽漂,也沒有注釋演训,我認為比較好的定位方法是,當丟數(shù)據(jù)時將這里的日志打印一下贝咙,因為這些異常分支從代碼層面上看都是有異常信息打印的样悟。
另外提一下OSDMap這個類,這個類定義在OSDMap.h這個頭文件中,如下圖:
我們可以看出這個類中記錄了集群中osd的全部信息窟她,里面的數(shù)據(jù)很多陈症,具體的含義我們可以通過字面的意思進行初步的分析,本次分析遠源碼的重點不在這里震糖,因此先繼續(xù)向后面查看代碼的邏輯录肯。
我們繼續(xù)之前的邏輯,我們發(fā)現(xiàn)在經(jīng)過前面一系列的判斷校驗之后主要還是通過send_op這個方法將構(gòu)建好的op發(fā)出去吊说,如下圖:
這個方法的內(nèi)部最關鍵的邏輯如下:
即通過相關的鏈接將消息發(fā)出去论咏。這里會產(chǎn)生一個問題,在ceph中發(fā)送消息都是由Messenger這個對象來進行的颁井,而ceph的Messenger有SimpleMessenger有AsyncMessenger等厅贪,雖然說在比較新的版本都使用了AsyncMessenger這個接口,但是在client端到底時如何操作的雅宾,我們可以從下圖的邏輯代碼來分析
我們之前分析過养涮,在客戶端使用的時候會初始化Rados這個類中會創(chuàng)建一個RadosClient類,而Rados的connect實際上就是RadosClient的connect()而message就是在這個過程中初始化的眉抬。
在這里我有一個疑問贯吓,按上面分析的邏輯消息是發(fā)出去了,但是發(fā)出去后改方法也沒有什么返回值吐辙,那么發(fā)送的消息client端到底是怎么確定消息是否成功的呢宣决?我比較疑惑,但是也不知道代碼要從哪里去看昏苏。
在這里我嘗試從AsyncMessenger這個類去分析發(fā)送的源碼尊沸,因為從這個圖中我們可以看出該調(diào)用并沒有明確的返回值,那么發(fā)送成功與失敗到底如何保證呢贤惯?其中op->session->con其實是一個Messenger類洼专,而從前面的分析與我們對版本12.2.4版本的了解,可以知道這個Messenger其實就是AsyncMessenger孵构,而AsyncMessenger的send_message實現(xiàn)如下
這里的代碼邏輯比較長也非常復雜屁商,流程大致為send_messenger--_send_messenger----submit_messenger-------------con->send_message,流程走到這里似乎斷了颈墅,但是其實這個con就是AsyncConnection蜡镶,因此我們要去AsyncConnection中去找send_message
在AsyncConnection中也確實存在send_message這個類,在這部分代碼中我沒有找到類似于網(wǎng)絡發(fā)送的代碼恤筛,反而找到的是如下的類似入隊的代碼官还,
如上紅圈所示,當代碼看到這里毒坛,我感覺這里的發(fā)送沒那么簡單望伦,要想搞清楚這部分內(nèi)容林说,可能需要學習一下AsyncMessenger的相關技術與代碼,這里也是ceph代碼中很大的一塊屯伞。
AsyncMessenger引出的通信模塊的一些學習
關鍵的幾個概念
Ceph的網(wǎng)絡層從上來說是有一套宏觀的抽象類的腿箩,我們研究的AsyncMessenger只是一種抽象的網(wǎng)絡類型,這個類型是近些年ceph開始主要使用的劣摇,在之前ceph一直使用simple這種類型珠移,而這種類型因為諸多的弊端,現(xiàn)在已經(jīng)被Async替代饵撑,但是因為ceph良好的代碼封裝剑梳,上層的接口沒有變化,因此我們先對上層的一些概念進行理解滑潘。
-
Messenger:這個也就是AsyncMessenger的基類垢乙,按我的理解這個類有兩個作用,對下將消息發(fā)送給Connection语卤,由Connection將消息通過網(wǎng)絡發(fā)走追逮。對上,他會將接到的消息進行分類然后分發(fā)給不同的Dispatcher粹舵,Dispathcer的作用我們下面介紹钮孵,Messenger里面包含了Dispatcher的集合,也包含了一些connection眼滤。
每個Messenger中還包含Processor這個關鍵的對象
當Messenger初始化是執(zhí)行了bind接口巴席,這個類中包含一個監(jiān)聽的socket會處理收到的請求
在processor的bind方法里面會有如下代碼
這段代碼中work監(jiān)聽了制定的socket,而這里的worker是個抽象類诅需,具體的實現(xiàn)如下
這里實現(xiàn)了設置網(wǎng)絡參數(shù)并實現(xiàn)監(jiān)聽漾唉。 - Dispathcer:我的理解是這個角色不涉及網(wǎng)絡,他其實是將接到的消息發(fā)送給具體處理消息的邏輯堰塌。
- Connection:這個是AsyncConnection的基類赵刑,是ceph的連接實例,負責維護同客戶端建立的socket連接以及ceph的協(xié)議棧操作接口场刑,向上提供發(fā)送消息接口及轉(zhuǎn)發(fā)底層消息給DispathcQueue或者消息管理器般此,向下發(fā)送和接收消息。
- Message:這個概念比較好理解牵现,這個是所有消息的基類铐懊。
理解了以上幾個關鍵的概念之后,接下來說一下核心的模塊
- 發(fā)送消息:首先是發(fā)送消息瞎疼,消息發(fā)送者按照消息類型將消息封裝好科乎,然后使用Messenger接口的send_message方法就可以將消息發(fā)送出去,我們在client端看到的方法就是使用了AsyncMessenger的send_message方法丑慎。
- 接收消息:Messenger是如何將消息轉(zhuǎn)給下部具體處理消息的邏輯呢?我的理解是這里主要靠分發(fā)器,即Dispatcher竿裂,Messenger設計了兩個分發(fā)器管理成員:dispatchers和fast_dispatchers玉吁,用來處理不同類型的請求處理,目前來說我也無法很清楚的說出這兩個分發(fā)器的區(qū)別腻异,但是從大體上看进副,fast_dispather對應的這種分發(fā)器應該是忽略了一些底層流程使得分發(fā)可以更快一些(比如跳過入隊操作)。應用層按需求將不同的分發(fā)器注冊給Messenger悔常,進而Messenger接收到底層來的消息時影斑,會將消息分發(fā)給已經(jīng)注冊的兩個dispatchers。Dispatcher類是一個基類机打,里面設計封裝了應用同Messenger交互的接口(每個具體的Dispatcher派生類自行去實現(xiàn)更具體的消息處理矫户,一般都是再根據(jù)消息類型來分開處理消息)。Dispatcher并不是所有的接口封裝都是為了轉(zhuǎn)發(fā)消息残邀,它更深層次的含義是提供一個應用層和底層的通信接口皆辽,而這個接口的橋梁是Messenger消息管理器。 當?shù)讓佑邢⒌絹頃r芥挣,Messenger會將消息轉(zhuǎn)給dispatcher對于的ms_*系列的接口驱闷,最常用的是ms_dispatch接口,因此你可以看到像monitor空免,osd這些應用的核心消息處理都在ms_dispatch接口里面實現(xiàn)空另。
除了上面這些東西,還需要大致理解一下Dispatcher的使用: - 比較簡單的用法是下層邏輯直接作為Dispatcher的派生類蹋砚,Messenger直接與相關的處理邏輯關聯(lián)扼菠,比如osd,monitor都弹,mgr都是應用組件本身作為Dispatcher的派生類娇豫。
-
申請一個Dispatcher的派生類實例,做為應用的模塊注冊給Messenger畅厢,比如RadosClient里面會注冊各個Client給Messenger冯痢,而這些Client都是Dispatcher的派生類。
除了以上的內(nèi)容框杜,再對Message進行一些說明浦楣,Message里面有一個關鍵的屬性type,這些type定義在Message.h中咪辱。
如上圖所示振劳,定義很多,我這里只截取一部分油狂。
Async的學習
上面的一些概念历恐,是ceph對網(wǎng)絡宏觀上的抽象寸癌,Async這種類型的Messenger是怎樣的工作原理呢?再了解該模塊的開始我們先看以下該模塊的源碼目錄弱贼,所包含的類如下:
從上面的類中我們可以看到前面所說的AsyncMessenger與AsyncConnection類蒸苇,還有一些Event*之類的類不明白是干什么的,為了能夠更好的學習這部分知識吮旅,因此先對這些內(nèi)容做了一些學習镐依。
首先是五種I/O類型
1.blocking I/O
2.nonblocking I/O
3.I/O multiplexing (select and poll)
4.signal driven I/O (SIGIO)
5.asynchronous I/O (the POSIX aio_functions)
這幾種IO類型我們在這里不做詳細的分析匠襟,我們直入主題棍弄,直接分析ceph中使用的IO類型脊阴,即類型3。
類型3的模型圖如下:
這種模型其實就是select和poll模型责嚷,select會先阻塞鸳兽,當內(nèi)核中有數(shù)據(jù)準備好后,select才會返回再层,這是后通知處理邏輯來對數(shù)據(jù)進行處理贸铜。而我們在源碼目錄下看到的Epoll與Kqueue其實就是更高級的select,高級在哪呢聂受?主要是這兩種調(diào)用直接使用callback來代替了輪詢蒿秦,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間蛋济。如果能給套接字注冊某個回調(diào)函數(shù)棍鳖,當他們活躍時,自動完成相關操作碗旅,那就避免了輪詢渡处,這正是epoll與kqueue做的。而epoll與kqueue的區(qū)別祟辟,以我的理解医瘫,主要是看操作系統(tǒng)的支持,像我們使用的linux服務器主要支持的就是epoll旧困,所以為了抓住主要問題醇份,我們最關鍵的就是要研究Epoll這種機制。
epoll可以理解為event poll吼具,不同于select的忙輪詢和無差別輪詢僚纷,epoll之會把哪個流發(fā)生了怎樣的I/O事件通知我們。此時我們對這些流的操作都是有意義的拗盒。(復雜度降低到了O(k)怖竭,k為產(chǎn)生I/O事件的流的個數(shù),也有認為O(1))陡蝇。
對于這個機制有了大概的理解之后我們先結(jié)合ceph的源碼痊臭,看看ceph的源碼是如何與epoll這種機制關聯(lián)上的哮肚,在該模塊的源碼目錄下首先我們看到的是Event.h這個類,進入這個類中我們看到了如下的內(nèi)容:
這個頭文件中首先包含了一個事件回調(diào)類广匙,這個類與epoll的回調(diào)是否能關聯(lián)起來绽左,還不好確定。
這個類少有的添加了一些注釋艇潭,從注釋來看,這個類封裝了不同操作系統(tǒng)之間的事件機制戏蔑,也就是epoll蹋凝,kqueue,select总棵,注釋中也說明了linux下主要使用的是epoll所以在后面的研究中我們只看epoll鳍寂。
看到這里我們很容易聯(lián)想到該模塊源碼目錄下的其他幾個類,如下圖
這幾個類其實就是EventDriver的具體實現(xiàn)
經(jīng)過查看確實如此情龄。
除了上面的EventDriver之外迄汛,還定義了一個EventCenter
這個類的注釋也寫明了,該類的作用是維護文件描述符然后操作注冊的事件骤视,雖然看到這幾個類之后感覺從字面上都知道這幾個上層類是干什么用的鞍爱,但其實這幾個類之間到底什么原理,一點也不懂专酗,還是需要再繼續(xù)研究睹逃。
此時我們再回到之前分析的AsyncConnection的send_message處
在之前我們已經(jīng)研究到發(fā)送邏輯中有一個入隊操作,有一個分發(fā)回調(diào)句柄的操作祷肯。
隊列的結(jié)構(gòu)如上圖沉填,分發(fā)回調(diào)句柄的實現(xiàn)如下
這里的邏輯其實是喚醒epoll_wait,那么這個操作到底在哪里呢佑笋?因為此處代碼是斷層的因此只能通篇的找翼闹。通過很長事件的概覽代碼,發(fā)現(xiàn)這里大致的流程是這樣的
上面這些圖就是發(fā)送消息時蒋纬,所走的大概邏輯猎荠,看著很容易其實花了很長的時間,而代碼看到這里問題又來了cb是一個EventCallBack實例颠锉,那么這個東西的具體實現(xiàn)到底是什么法牲?
這時我們在回憶之前AsyncConnection的代碼我們注冊了一個write_handler這個回調(diào),這個回調(diào)在初始化時有琼掠,如下圖
因此我們可以得知拒垃,上面的do_request是C_handle_write的實現(xiàn),實現(xiàn)代碼如下
這個也比較簡單瓷蛙,調(diào)用了AsyncConnection的handle_write()方法悼瓮,該方法的實現(xiàn)如下圖
在這個try_send中戈毒,終于看到了類似于發(fā)送的東西,而這個cs的類如下
進入到這個類中發(fā)現(xiàn)這個類也是個抽象的父類横堡,查找相關子類的具體實現(xiàn)埋市,如下
到了這層邏輯,已經(jīng)到了內(nèi)核網(wǎng)絡接口的調(diào)用命贴,也就到底了道宅。分析到這一步,我大概理解了一些東西胸蛛,當ceph將數(shù)據(jù)發(fā)出去后污茵,使用epoll技術可以要么發(fā)成功,如果不成功報錯葬项,他能夠保證發(fā)送端健康發(fā)送泞当,但是數(shù)據(jù)發(fā)送到server端后,server端在經(jīng)過處理后民珍,假設處理有問題襟士,client端怎么知道呢?因為我們研究的write_full接口是一個同步的接口嚷量,按我們對同步的一般理解陋桂,如果發(fā)送成功,server端是需要告知是否處理成功的蝶溶,這部分ceph是怎么做的章喉,還需要接著看。
Client端對于回調(diào)的處理
在經(jīng)過了上面一章的分析身坐,我們基本能夠知道client端發(fā)送消息的邏輯秸脱,但是還是有一個關鍵問題,沒有分析清楚部蛇,通過前面的分析我們知道write_full這類接口是一個同步的接口摊唇,也就是說,當消息發(fā)送之后涯鲁,client端需要等待消息處理的結(jié)果
根據(jù)我們之前代碼的分析巷查,肯定需要有一個操作,來調(diào)用這個finish方法抹腿,從而觸發(fā)條件變量讓這個操作結(jié)束岛请,但是這個方法到底在哪里調(diào)用的不得而知,而c++的代碼又不能看調(diào)用的關系警绩,很多地方因為使用的是抽象父類的方法崇败,所以搜finish這個關鍵次根本沒用,所以只能繼續(xù)通篇來閱讀源碼。
在繼續(xù)閱讀源碼過程中我發(fā)現(xiàn)在RadosClient類的connet方法中有一些初始化的操作
這里面初始化的這個Objecter是一個Dispatcher后室,通過前面幾章的學習我們知道繼承了Dispatcher類的實例缩膝,是用來處理網(wǎng)絡回應的,如果server端將回應發(fā)回來岸霹,很有可能就是在這個類中來處理的疾层。
我們找到objector中的ms_dispatch方法,發(fā)現(xiàn)在其中有如下的處理邏輯
這個分支就是來處理osd回應的操作的贡避,我們進入到這個方法中去痛黎,接下來有一大部分異常處理,類似于接到錯誤的消息后重發(fā)的操作刮吧,如下
這些邏輯沒有注釋舅逸,也很瑣碎,代碼很長皇筛,拋開這些邏輯來看,我們從這段邏輯中最終找到了我們想找的邏輯坠七,如下圖
也就是回調(diào)函數(shù)的執(zhí)行水醋,這個方法就會調(diào)用到前面回調(diào)類的finish方法,讓條件變量觸發(fā)從而結(jié)束整個寫入的流程彪置。
到這里拄踪,按我的理解,整個client端的代碼流程邏輯算是完成了一個閉環(huán)拳魁,當然client端里面的各種if分支實在是種類繁多惶桐,沒有注釋的情況下,想要一一弄明白潘懊,我覺得不現(xiàn)實姚糊,因此梳理到這一步,我打算繼續(xù)來進行server端代碼的研究授舟。
另外救恨,按照我的理解,client端雖然是外層邏輯释树,但是通過分析已經(jīng)能夠得出一些結(jié)論肠槽,首先正常的流程發(fā)送了消息后,通過async接口保證將數(shù)據(jù)發(fā)出去奢啥,不如不發(fā)處會有錯誤秸仙,而后端接到消息后,一定會給client端一個回應桩盲,client端要么接到錯的回應重發(fā)寂纪,要么阻塞,這里我們確實有個配置項配置的不合理赌结,這個在前面有提到弊攘,如果我們將那個超時的配置增加上抢腐,杜絕一直阻塞的問題,那么按我的理解襟交,client端就能做到迈倍,要么發(fā)送成功,要么失敗捣域,目前有種可能是一直阻塞啼染。
Server端代碼學習
在server端源碼學習的過程中,因為時間的關系焕梅,我們直接來看流程相關的代碼迹鹅,首先osd的server端的啟動都在ceph_osd.cc類中,這個類是osd進程的主類贞言,osd相關的初始化工作都是在這個類中進行的斜棚。
在ceph_osd這個主進程中處理osd相關請求的邏輯主要封裝在OSD這個類中,OSD類的初始化代碼如下
這個類構(gòu)造之前還有一些相關準備工作的代碼该窗,我們暫時不詳細描述弟蚀,先看關鍵的代碼,首先是OSD類酗失,這個類同樣的繼承了Dispatcher這個抽象類
根據(jù)之前代碼的學習义钉,我們知道繼承了這個抽象接口的類一般對相關邏輯的處理都在ms_dispatch或者ms_fast_dispatch之中,研究代碼發(fā)現(xiàn)在ms_dispatch之中主要處理的是一些PG或者OSD級的消息规肴,沒發(fā)現(xiàn)對于請求處理方面的邏輯捶闸,如下圖
所以,我們繼續(xù)看ms_fast_dispatch,在這個邏輯中首先進行了一些校驗拖刃,并將消息組裝成op結(jié)構(gòu)删壮。
之后下面跟了一個邏輯判斷,根據(jù)判斷條件兑牡,我們應該走到下圖紅圈處的邏輯
在這里我們找到了op的入隊操作
看到這個操作醉锅,我們大概可以分析出,osd端接到消息之后會將消息入隊緩存发绢,在初始化時應該有線程啟動硬耍,會一直從這個隊列中獲取op請求來進行處理,這個線程時從哪個地方初始化并一步一步調(diào)用到出隊這個方法的边酒,這里我們暫時不考慮经柴,我們直接從出隊列這個方法來看,因為這個方法就在enqueue_op方法定義的下端墩朦。
我們看到入隊方法下面緊接著就是出隊方法的定義坯认,我們可以直接基于這個方法來進行分析。這個方法中的代碼比較少,能夠很方便的找到主要的流程牛哺,如下圖
這里是對op的處理邏輯陋气,此時問題又出現(xiàn)了這個dp_request方法跳不進去,我們需要找到PG的實現(xiàn)類引润,這里的PG是如下圖的一個類
這個類雖說是個父類巩趁,但是大概有2000多行的代碼,類的dp_request是個虛函數(shù)如下圖淳附,需要子類去實現(xiàn)
通過全局的搜索议慰,我們找到了Pg的一個子類為PrimaryLogPG
該子類的具體實現(xiàn)類如下:
在方法實現(xiàn)的剛開始,大概有200多行的代碼奴曙,這部分代碼按我的理解是做了一些osdmap于session的準備工作别凹,而關于op的核心處理是在一個標準的switch…case語句中,如下圖
這個switch根據(jù)op的不同類型對數(shù)據(jù)進行了不同的處理洽糟。在這個流程中有兩個關鍵的邏輯調(diào)用炉菲,一個是reply_op_error即如果出錯了,向client端返回響應坤溃,相關的實現(xiàn)邏輯如下
在這個方法中我們看到了明確的網(wǎng)絡消息調(diào)用拍霜。
在上面switch…case中另外一個主要的流程是do_op
這個流程貫徹了ceph的一貫風格,做了很多的正確性與安全行的校驗浇雹,我覺得向ceph里面寫數(shù)據(jù),安全歸安全屿讽,但是這種正確性可靠性的校驗勢必會影響性能;這個方法做了近500多行op的處理工作昭灵,內(nèi)部包含的邏輯判斷在這里不進行詳細的分析,我們接下來直接找op的處理流程伐谈。在這里op會被封裝成一個新的對象來繼續(xù)處理烂完。
封裝成改對象之后,對context的主要處理邏輯封裝在execute_ctx函數(shù)中诵棵,該函數(shù)的大致實現(xiàn)邏輯如下圖
這個方法中將ctx進行了再一次的封裝抠蚣,變成了transaction即事務這個概念,
在整個execute_ctx的流程中履澳,處理的邏輯也比較多嘶窄,根據(jù)這次的目的,我認為主要還是看距贷,在處理流程中假設有些過程遇到異常柄冲,這個處理有沒有正確的向client端返回,根據(jù)對代碼的分析忠蝗,在這個流程中主要是通過MOSDOpReply構(gòu)造這個類來向client端進行回應的现横,如下圖
這個是眾多異常邏輯中的一個異常邏輯的處理,這個部分并不難理解,在處理流程中如果遇到異常戒祠,就構(gòu)造MOSDOpReply對象骇两,將結(jié)果記錄到這個對象中去,在合適的時候?qū)⑦@個reply發(fā)送出去姜盈,上面代碼段的發(fā)送邏輯就封裝在第二個紅圈處低千,如下圖
因為這個方法中處理的op有讀有寫,我們接下來著重來看寫相關的流程贩据,與寫相關的關鍵的邏輯調(diào)用為如下的代碼
這個操作向各個副本發(fā)送同步操作的請求栋操,剛看這里時有個令我很疑惑的點,就是這個repop是什么意思饱亮,在ceph的源碼中有很多有這種單詞的變量與方法矾芙,從字面意思根本解釋不通,經(jīng)過反復的琢磨近上,我覺得比較合理的解釋應該是這個repop是個縮寫剔宪,rep是replication的縮寫,op就是前面封裝的op對象壹无,所以這個方法的字面意思就是將op發(fā)布到備份節(jié)點上葱绒,這個解釋比較合理一些。在大概理解了這個方法要干的事情后斗锭,我的理解是目前的代碼邏輯地淀,與我們之前的相關邏輯的猜想是相符的,即數(shù)據(jù)通過client端發(fā)送過來之后岖是,主osd的server端對數(shù)據(jù)進行相關的處理之后帮毁,將數(shù)據(jù)同步到備份節(jié)點上,這個issue_repop主要就是干這個事情的豺撑。在這個方法中ceph一口氣構(gòu)造了3個回調(diào)
在構(gòu)造了這幾個回調(diào)之后烈疚,就進入了后端的事務處理流程,如下圖
在這里我們先不進行submit_transation方法的分析聪轿,因為這個處理又是一個新的流程爷肝,我比較關心的是issue_repop這個方法是如何確定成功的,因為從代碼上看這個方法是沒有返回值的陆错,那么執(zhí)行了相關的操作后ceph如何確定相關的操作有沒有成功呢灯抛?就比如說這個submit_transation方法。在這部分代碼中音瓷,我們又看到了老面孔牧愁,即這個回調(diào),按照預計外莲,ceph應該也是根據(jù)這個回調(diào)來確定操作確實處理完了猪半,我們跳出這個方法繼續(xù)往下看兔朦,發(fā)現(xiàn)了eval_repop這個函數(shù),進入到這個函數(shù)中磨确,果然發(fā)現(xiàn)了回調(diào)相關的判斷沽甥。
在這個方法中我們看到了對回調(diào)的處理,如果向備份節(jié)點發(fā)送op正確或者異常都會在這里進行處理乏奥。
接下來我們掉過頭來繼續(xù)分析寫的流程摆舟,關于寫的流程這里首先要跳回到prepare_transaction這個函數(shù),為什么要回到這個函數(shù)呢邓了,因為這個函數(shù)看似是個準備恨诱,但其實在這個函數(shù)中處理了相當多的邏輯,比較簡單的read流程骗炉,更是直接在這個函數(shù)中就做完了揪胃,而寫的流程因為比較復雜還涉及到要和備份的OSD同步數(shù)據(jù)叁巨,所以這里只做了一部分工作野来,另一部分工作就在issue_repop這里做的廊遍。
這個函數(shù)中關鍵的調(diào)用是上圖紅圈處的邏輯
這個函數(shù)非常長,涉及到了很多類型的OP的處理乍丈,其中就有讀的和寫的剂碴,讀的流程我們暫不關注,但是還是能看出這種按照類型的處理邏輯
我們重點關注寫的流程代碼轻专,代碼大致如下
在寫這個過程比較關鍵的代碼就是下圖所示的這個write方法
這個方法是向transations中填充數(shù)據(jù)
這一部分是很容易理解的忆矛,就是說把操作碼設置為OP_WRITE,記錄好要寫入的object请垛,將offset 和length設置正確催训,同時將要寫入的data紀錄下來,后續(xù)ObjectStore部分(更具體地說是filestore)叼屠,就可以根據(jù)上述信息瞳腌,完成寫入底層對象存儲的動作绞铃。
上述內(nèi)容僅僅是一個部分镜雨,之前也提過,除了data儿捧,還有PGLog荚坞,這部分內(nèi)容是為了紀錄各個副本之間的寫入情況,預防異常發(fā)生菲盾。prepare_transaction函數(shù)的最后颓影,會調(diào)用finish_ctx函數(shù),finish_ctx函數(shù)里就會調(diào)用ctx->log.push_back就會構(gòu)造pg_log_entry_t插入到vector log里懒鉴。
梳理完這部分關鍵的邏輯之后诡挂,我們繼續(xù)來看主OSD與備份OSD通信的相關流程碎浇,上面提到過回調(diào),如下圖
在看到這幾個回調(diào)之后璃俗,大家估計都會有疑問奴璃,什么是commit,什么是applied這兩個過程是什么城豁?經(jīng)過源碼分析與一些網(wǎng)上資料的查詢苟穆,我的理解是這樣的,client端只會和Primary OSD交互唱星,而Primary OSD會和Replica OSD交互雳旅,當Replica OSD完成寫入后,要發(fā)消息給Primary OSD间聊,當Primary OSD將各個Replica OSD的消息匯總之后攒盈,再給client發(fā)送回應。
在這里甸饱,需要了解的一個概念是無論Primary還是Replica沦童,他們在寫入數(shù)據(jù)的時候,都分為兩部叹话,首先是寫Journal日志偷遗,然后是數(shù)據(jù)落磁盤,而這個過程中Replica會給Primary發(fā)送兩次消息驼壶,第一次消息在Journal日志寫完之后發(fā)送氏豌,第二次消息在數(shù)據(jù)落盤后發(fā)送給Primary,在每個階段Primary收到所有Replica的回應后热凹,都會發(fā)相應的消息告知client泵喘。
關于這些狀態(tài),都在submit_transaction方法中記錄
關于submit_transaction方法般妙,我們先粗略的介紹一下纪铺,首先PGBackend是一個抽象類,類的說明如下
類的功能在注釋上說的比較明確碟渺,這個類有兩個抽象的實現(xiàn)鲜锚,如下圖
這兩個實現(xiàn)在源碼的osd目錄下,這兩個實現(xiàn)類都比較好理解苫拍,一個代表的是糾刪碼的方式芜繁,一個代表的是備份的方式,我們基本上不用糾刪碼這種方式绒极,所以我們以研究備份這種方式為主骏令,下面我們就進入到ReplicatedBackend這種方式的submit_transaction方法中。在這個代碼中我們發(fā)現(xiàn)了如下的數(shù)據(jù)結(jié)構(gòu)
這個數(shù)據(jù)結(jié)構(gòu)就是用來記錄前面提到的那些狀態(tài)的垄提。在這個結(jié)構(gòu)中維護著兩個集合榔袋,如下圖
這兩個集合中第一個集合中維護著尚未完成第一階段工作的OSD的集合周拐,第二個集合存放著尚未完成第二階段工作的OSD的集合。
有了這兩個集合凰兑,必然會涉及到這兩個集合狀態(tài)的更新速妖,很明顯,當Replica OSD或者Primary OSD完成第一階段或者第二階段任務的時候聪黎,都必然會通知到Primary OSD罕容,更新這兩個集合中的元素: 如何更新?這里就又用到了我們經(jīng)掣迨危看到的回調(diào)锦秒,我感覺回調(diào)這種機制在ceph的代碼中貫徹始終,用的很多喉镰。
我們先不談回調(diào)旅择,繼續(xù)來走邏輯,在有了inprogressop這個結(jié)構(gòu)后侣姆,我們往下走就會看到如下的邏輯
這個邏輯就是向Replica OSD發(fā)送消息的邏輯生真,我們看一下這個邏輯的主體代碼
我們很容易就能看到類似于發(fā)送消息的代碼邏輯。我們看到了捺宗,在循環(huán)體中柱蟀,會遍歷所有的Replica OSD,向?qū)腛SD發(fā)送消息蚜厉,而消息體的組裝长已,是在generate_subop函數(shù)中,我們進入該函數(shù)昼牛。
進入該函數(shù)后我們可以看出該函數(shù)的主體是這個MOSDRepOp對象术瓮,這個函數(shù)的構(gòu)造函數(shù)如下
在上圖中我們可以看出這個消息的類型是MSG_OSD_REPOP,在Primary發(fā)送了這種消息之后悬垃,Replica OSD會和Primary前面的邏輯一樣载庭,進入到隊列靖榕,然后從osd.op_wq中取出消息進行處理践剂。當走到do_request函數(shù)之后逊脯,并沒有機會執(zhí)行do_op,或者do_sub_op之類的函數(shù)扇住,而是被handle_message函數(shù)攔截了屑迂,我們回過頭來看一下這里的邏輯
先進入dequeue的do_request,在這個request方法中我們需要注意如下的代碼
這個方法會將primary發(fā)送給replication的方法攔截住兄裂,使得這次的方法不會走到primary之前走到的switch..case語句中晰奖,我們看一下這個handle_message的實現(xiàn)
這個父類的handle_message會處理兩周類型的消息前翎,外加調(diào)用_handle_message這個方法,而實現(xiàn)的類的具體實現(xiàn)的方法如下
在這里我們看到了剛才的那種消息類型畅涂,并且使用do_repop方法對這個消息進行了處理港华,進入這個方法我們發(fā)現(xiàn)這個方法比較長,其中有部分代碼和之前primary的代碼很相似如下圖
上面這個圖是備份節(jié)點的午衰,下面這個圖是主節(jié)點的立宜,很像,原因是很簡單的臊岸,即Primary OSD 和Replica OSD 本身要執(zhí)行的操作橙数,原本是一樣的,只不過存在Primary OSD肩負著和Client通信的責任帅戒,而Replica 并沒有這種責任灯帮,但是Replica需要及時向Primary OSD匯報進度。
這部分都有兩個回調(diào)逻住,一個回調(diào)表示數(shù)據(jù)寫入了journal日志钟哥,另一個回調(diào)表示數(shù)據(jù)落盤,這兩個回調(diào)每個完成后都要給主OSD發(fā)消息瞎访。其中commit表示數(shù)據(jù)已經(jīng)寫入到了osd的journal腻贰,apply表示數(shù)據(jù)已經(jīng)寫入到磁盤的data partition。我們挑一個回調(diào)函數(shù)的實現(xiàn)看一下扒秸,就會發(fā)現(xiàn)這個函數(shù)主要工作就是給主OSD發(fā)送消息播演。
在完成了這一系列流程后,整個寫入的流程算是完整了伴奥,接下來就是OS層對數(shù)據(jù)的處理了写烤。
關于slow request日志的一些代碼學習
rocksdb丟數(shù)據(jù)時ceph集群一般伴隨著slow request的狀態(tài),因此在看代碼過程中全局搜索了slow request的相關代碼拾徙,看看有沒有什么進展
這個實在PGMap.cc類中找到的一個slow request其中涉及的參數(shù)在實際集群中如下
也就是說請求已經(jīng)阻塞超過了32秒,這個類之前還有這樣一段邏輯代碼
翻了翻這段代碼的上下文洲炊,感覺這段代碼主要工作時判斷一些PG的狀態(tài),而發(fā)生這些狀態(tài)的原因騎士并不在這里,比如這個slow request從字面上來看我們已經(jīng)能夠理解這個狀態(tài)的大致含義选浑,但是后端阻塞的原因我們在這里看不出什么。途中的這個osd_sum是一個osd_stat_t類型的結(jié)構(gòu)體玄叠,這個結(jié)構(gòu)體內(nèi)部記錄了一些metric信息