BlueStore原始碼分析之事物狀態機
前言
BlueStore可以理解為一個支援ACID的本地日誌型檔案系統。所有的讀寫都是以
Transaction
進行,又因為支援覆蓋寫,所以寫流程設計的相對複雜一些,涉及到一系列的狀態轉換。我們著重分析一下狀態機、延遲指標以及如何保證IO的順序性和併發性。
目錄
狀態機
queue_transactions
queue_transactions
是ObjectStore層的統一入口,KVStore、MemStore、FileStore、BlueStore都相應的實現了這個介面。
state_t state
變數記錄了當前時刻事物處於哪個狀態。在建立TransactionContext的時候會將
state
初始化為
STATE_PREPARE
,然後在
_txc_add_transaction
中會根據操作碼型別(opcode)進行不同的處理。同時會獲取PG對應的OpSequencer(每個PG有一個OpSequencer)用來保證PG上的IO序列執行,對於deferred-write會將其資料寫入RocksDB(WAL)。
以下階段就進入BlueStore狀態機了,我們以寫流程為導向分析狀態機的每個狀態。
STATE_PREPARE
從state_prepare開始已經進入事物的狀態機了。這個階段會呼叫
_txc_add_transaction
將OSD層面的事物轉換為BlueStore層面的事物;然後檢查是否還有未提交的IO,如果還有就將state設定為
STATE_AIO_WAIT
並呼叫
_txc_aio_submit
提交IO,然後退出狀態機,之後aio完成的時候會呼叫回撥函式
txc_aio_finish
再次進入狀態機;否則就進入
STATE_AIO_WAIT
狀態。
_txc_aio_submit函式呼叫棧:
bdev->aio_submit –> KernelDevice::aio_submit –> io_submit
將aio提交到核心Libaio佇列。
主要工作:準備工作,生成大小寫、初始化TransContext、deferred_txn、分配磁碟空間等。
延遲指標:
l_bluestore_state_prepare_lat
,從進入狀態機到prepare階段完成,平均延遲大概0.2ms左右。
STATE_AIO_WAIT
該階段會呼叫
_txc_finish_io
進行SimpleWrite的IO保序等處理,然後將狀態設定為
STATE_IO_DONE
再呼叫_txc_state_proc進入下一個狀態的處理。
主要工作:對IO保序,等待AIO的完成。
延遲指標:
l_bluestore_state_aio_wait_lat
,從prepare階段完成開始到AIO完成,平均延遲受限於裝置,SSD 0.03ms左右。
STATE_IO_DONE
完成AIO,並進入
STATE_KV_QUEUED
階段。會根據
bluestore_sync_submit_transaction
做不同處理。該值為布林值,預設為false。
如果為true,設定狀態為
STATE_KV_SUBMITTED
並且同步提交kv到RocksDB但是沒有sync落盤(submit_transaction),然後
applied_kv
。
如果為false,則不用做上面的操作,但是以下操作都會做。
最後將事物放在
kv_queue
裡,透過
kv_cond
通知
kv_sync_thread
去同步IO和後設資料。
主要工作:將事物放入
kv_queue
,然後通知
kv_sync_thread
,osr的IO保序可能會block。
延遲指標:
l_bluestore_state_io_done_lat
,平均延遲在0.004ms,通常很小主要耗在對SimpleWrite的IO保序處理上。
STATE_KV_QUEUED
該階段主要在
kv_sync_thread
執行緒中同步IO和後設資料,並且將狀態設定為
STATE_KV_SUBMITTED
。具體會在非同步執行緒章節分析
kv_sync_thread
執行緒。
主要工作:從
kv_sync_thread
佇列中取出事物。
延遲指標:
l_bluestore_state_kv_queued_lat
,從事物進入佇列到取出事物,平均延遲在0.08ms,因為是單執行緒順序處理的,所以依賴於
kv_sync_thread
處理事物的速度。
STATE_KV_SUBMITTED
等待
kv_sync_thread
中kv後設資料和IO資料的Sync完成,然後將狀態設定為
STATE_KV_DONE
並且回撥finisher執行緒。
主要工作:等待kv後設資料和IO資料的Sync完成,回撥finisher執行緒。
延遲指標:
l_bluestore_state_kv_committing_lat
,從佇列取出事物到完成kv同步,平均延遲1.0ms,有極大的最佳化空間。
STATE_KV_DONE
如果是SimpleWrite,則直接將狀態設定為
STATE_FINISHING
;如果是DeferredWrite,則將狀態設定為
STATE_DEFERRED_QUEUED
並放入
deferred_queue
。
主要工作:如上。
延遲指標:
l_bluestore_state_kv_done_lat
,平均延遲0.0002ms,可以忽略不計。
STATE_DEFERRED_QUEUED
主要工作:將延遲IO放入
deferred_queue
等待提交。
延遲指標:
l_bluestore_state_deferred_queued_lat
,通常不小,沒有資料暫不貼出。
STATE_DEFERRED_CLEANUP
主要工作:清理延遲IO在RocksDB上的WAL。
延遲指標:
l_bluestore_state_deferred_cleanup_lat
,通常不小,沒有資料暫不貼出。
STATE_FINISHING
主要工作:設定狀態為
STATE_DONE
,如果還有DeferredIO也會提交。
延遲指標:
l_bluestore_state_finishing_lat
,平均延遲0.001ms。
STATE_DONE
主要工作:標識整個IO完成。
延遲指標:
l_bluestore_state_done_lat
。
延遲分析
BlueStore定義了狀態機的多個延遲指標,由PerfCounters採集,函式為
BlueStore::_init_logger()
。
可以使用
ceph daemon osd.0 perf dump
或者
ceph daemonperf osd.0
來檢視對應的延遲情況。
除了每個狀態的延遲,我們通常也會關注以下兩個延遲指標:
b.add_time_avg(l_bluestore_kv_lat, "kv_lat",
"Average kv_thread sync latency", "k_l",
b.add_time_avg(l_bluestore_commit_lat, "commit_lat",
"Average commit latency", "c_l",
BlueStore延遲主要花費在
l_bluestore_state_kv_committing_lat
也即
c_l
,大概1ms左右。
BlueStore統計狀態機每個階段延遲的方法如下:
// 該階段延遲 = 上階段完成到該階段結束
void log_state_latency(PerfCounters *logger, int state) {
utime_t lat, now = ceph_clock_now();
lat = now - last_stamp;
logger->tinc(state, lat);
last_stamp = now;
}
在塊儲存的使用場景中,除了使用者併發IO外,通常使用者也會使用
dd
等序列IO的命令,此時便受限於讀寫的絕對延遲,擴容加機器、增加執行緒數等橫向擴充套件的最佳化便是無效的,所以我們需要關注兩方面的延遲:
併發IO延遲、
序列IO延遲。
併發IO延遲最佳化:kv_sync_thread、kv_finalize_thread多執行緒化;自定義WAL;async read。
序列IO延遲最佳化:並行提交後設資料、資料;將sync操作與其他狀態並行處理。
IO保序
保證IO的順序性以及併發性是分散式儲存必然面臨的一個問題。因為BlueStore使用非同步IO,後提交的IO可能比早提交的IO完成的早,所以更要保證IO的順序,防止資料發生錯亂。 客戶端可能會對PG中的一個Object連續提交多次讀寫請求,每次請求對應一個Transaction,在OSD層面透過PGLock將併發的讀寫請求在PG層面序列化,然後按序依次提交到ObjectStore層,ObjectStore層透過PG的OpSequencer保證順序處理讀寫請求。
BlueStore寫型別有SimpleWrite、DeferredWrite兩種,所以我們分析一下SimpleWrite、DeferredWrite下的IO保序問題。
SimpleWrite
因為
STATE_AIO_WAIT
階段使用Libaio,所以需要保證PG對應的OpSequencer中的txc按排隊的先後順序依次進入kv_queue被
kv_sync_thread
處理,也即txc在OpSequencer中的順序和在kv_queue中的順序是一致的。
void BlueStore::_txc_finish_io(TransContext *txc)
{
// 獲取txc所屬的OpSequencer,並且加鎖,保證互斥訪問osr
OpSequencer *osr = txc->osr.get();
std::lock_guard<std::mutex> l(osr->qlock);
// 設定狀態機的state為STATE_IO_DONE
txc->state = TransContext::STATE_IO_DONE;
// 清除txc正在執行的aio
txc->ioc.running_aios.clear();
// 定位當前txc在osr的位置
OpSequencer::q_list_t::iterator p = osr->q.iterator_to(*txc);
while (p != osr->q.begin()) {
--p;
// 如果前面還有未完成IO的txc,那麼需要停止當前txc操作,等待前面txc完成IO。
// 目的是:確保之前txc的IO都完成。
if (p->state < TransContext::STATE_IO_DONE) {
return;
}
// 前面的txc已經進入大於等於STATE_KV_QUEUED的狀態了,那麼遞增p並退出迴圈。
// 目的是:找到狀態為STATE_IO_DONE的且在osr中排序最靠前的txc。
if (p->state > TransContext::STATE_IO_DONE) {
++p;
break;
}
}
// 依次處理狀態為STATE_IO_DONE的tx
// 將txc放入kv_sync_thread的kv_queue、kv_queue_unsubmitted佇列
do {
_txc_state_proc(&*p++);
} while (p != osr->q.end() && p->state == TransContext::STATE_IO_DONE);
......
}
DeferredWrite
DeferredWrite在IO的時候也是透過Libaio提交到核心Libaio佇列進行寫資料,也需要保證IO的順序性。
相應的資料結構如下:
class BlueStore {
typedef boost::intrusive::list<
OpSequencer, boost::intrusive::member_hook<
OpSequencer, boost::intrusive::list_member_hook<>,
&OpSequencer::deferred_osr_queue_item>>
deferred_osr_queue_t;
// osr's with deferred io pending
deferred_osr_queue_t deferred_queue;
}
class OpSequencer {
DeferredBatch *deferred_running = nullptr;
DeferredBatch *deferred_pending = nullptr;
}
struct DeferredBatch {
OpSequencer *osr;
// txcs in this batch
deferred_queue_t txcs;
}
BlueStore內部包含一個成員變數deferred_queue;deferred_queue佇列包含需要執行DeferredIO的OpSequencer;每個OpSequencer包含deferred_running和deferred_pending兩個DeferredBatch型別的變數;DeferredBatch包含一個txc陣列。
如果PG有寫請求,會在PG對應的OpSequencer中的deferred_pending中排隊加入txc,待時機成熟的時候,一次性提交所有txc給Libaio,執行完成後才會進行下一次提交,這樣不會導致DeferredIO亂序。
void BlueStore::_deferred_queue(TransContext *txc)
{
deferred_lock.lock();
// 排隊osr
if (!txc->osr->deferred_pending && !txc->osr->deferred_running) {
deferred_queue.push_back(*txc->osr);
}
// 追加txc到deferred_pending中
txc->osr->deferred_pending->txcs.push_back(*txc);
_deferred_submit_unlock(txc->osr.get());
......
}
void BlueStore::_deferred_submit_unlock(OpSequencer *osr)
{
......
// 切換指標,保證每次操作完成後才會進行下一次提交
osr->deferred_running = osr->deferred_pending;
osr->deferred_pending = nullptr;
......
while (true) {
......
// 準備所有txc的寫buffer
int r = bdev->aio_write(start, bl, &b->ioc, false);
}
......
// 一次性提交所有txc
bdev->aio_submit(&b->ioc);
}
執行緒佇列
執行緒+佇列是實現非同步操作的基礎。BlueStore的一次IO經過狀態機要進入多個佇列並被不同的執行緒處理然後回撥,執行緒+佇列是BlueStore事物狀態機的重要組成部分。BlueStore中的執行緒大致有7種。
-
mempool_thread
:無佇列,後臺監控記憶體的使用情況,超過記憶體使用的限制便會做trim。 -
aio_thread
:佇列為Libaio核心queue,收割完成的aio事件。 -
discard_thread
:佇列為discard_queued,對SSD磁碟上的extent做Trim。 -
kv_sync_thread
:佇列為kv_queue、deferred_done_queue、deferred_stable_queue,sync後設資料和資料。 -
kv_finalize_thread
:佇列為kv_committing_to_finalize、deferred_stable_to_finalize,執行清理功能。 -
deferred_finisher
:呼叫回撥函式提交DeferredIO的請求。 -
finishers
:多個回撥執行緒Finisher,通知使用者請求完成。
我們主要分析
aio_thread
、
kv_sync_thread
、
kv_finalize_thread
。
aio_thread
aio_thread比較簡單,屬於KernelDevice模組的,主要作用是收割完成的aio事件,並觸發回撥函式。
void KernelDevice::_aio_thread() {
while (!aio_stop) {
......
// 獲取完成的aio
int r = aio_queue.get_next_completed(cct->_conf->bdev_aio_poll_ms, aio, max);
// 設定flush標誌為true。
io_since_flush.store(true);
// 獲取aio的返回值
long r = aio[i]->get_return_value();
......
// 呼叫aio完成的回撥函式
if (ioc->priv) {
if (--ioc->num_running == 0) {
aio_callback(aio_callback_priv, ioc->priv);
}
}
}
}
涉及延遲指標:
state_aio_wait_lat
、
state_io_done_lat
。
kv_sync_thread
當IO完成後,要麼將txc放入佇列,要麼將dbh放入佇列,雖然對應不同佇列,但都是由
kv_sync_thread
執行後續操作。
對於SimpleWrite,都是寫新的磁碟block(如果是cow,也是寫新的block,只是事務中k/v操作增加對舊的block的回收操作),所以先由aio_thread寫block,再由kv_sync_thread同步元資訊,無論什麼時候掛掉,資料都不會損壞。
對於DeferredWrite,在事物的prepare階段將需要DeferredWrite的資料作為k/v對(也稱為WAL)寫入基於RocksDB封裝的db_transaction中,此時還在記憶體,kv_sync_thread第一次的commit操作中,將wal持久化在了k/v系統中,然後進行後續的操作,異常的情況,可以透過回放wal,資料也不會損壞。
kv_sync_thread主要執行的操作為:在Libaio寫完資料後,需要透過kv_sync_thread更新後設資料k/v,主要包含object的Onode、擴充套件屬性、FreelistManager的磁碟空間資訊等等,這些必須按順序操作。
涉及的佇列如下:
-
kv_queue
:需要執行commit的txc佇列。將kv_queue中的txc存入kv_committing中,並提交給RocksDB,即執行操作db->submit_transaction,設定狀態為STATE_KV_SUBMITTED,並將kv_committing中的txc放入kv_committing_to_finalize,等待執行緒kv_finalize_thread執行。 -
deferred_done_queue
:已經完成DeferredIO操作的dbh佇列,還沒有sync磁碟。這個佇列的dbh會有兩種結果: 1) 如果沒有做flush操作,會將其放入deferred_stable_queue待下次迴圈繼續處理 2) 如果做了flush操作,說明資料已經落盤,即已經是stable的了,直接將其插入deferred_stable_queue佇列。這裡stable的意思就是資料已經sync到磁碟了,前面RocksDB中記錄的wal沒用可以刪除了。 -
deferred_stable_queue
:DeferredIO已經落盤,等待清理RocksDB中的WAL。依次操作dbh中的txc,將RocksDB中的wal刪除,然後dbh入佇列deferred_stable_to_finalize,等待執行緒kv_finalize_thread執行。
void BlueStore::_kv_sync_thread() {
while (true) {
// 交換指標
kv_committing.swap(kv_queue);
kv_submitting.swap(kv_queue_unsubmitted);
deferred_done.swap(deferred_done_queue);
deferred_stable.swap(deferred_stable_queue)
// 處理 deferred_done_queue
if (force_flush) {
// flush/barrier on block device
bdev->flush();
// if we flush then deferred done are now deferred stable
deferred_stable.insert(deferred_stable.end(),
deferred_done.begin(),
deferred_done.end());
deferred_done.clear();
}
// 處理 kv_queue
for (auto txc : kv_committing) {
int r = cct->_conf->bluestore_debug_omit_kv_commit
? 0
: db->submit_transaction(txc->t);
_txc_applied_kv(txc);
}
// 處理 deferred_stable_queue
for (auto b : deferred_stable) {
for (auto &txc : b->txcs) {
get_deferred_key(wt.seq, &key);
synct->rm_single_key(PREFIX_DEFERRED, key);
}
}
// submit synct synchronously (block and wait for it to commit)
// 同步kv,有設定bluefs_extents、刪除wal兩種操作
int r = cct->_conf->bluestore_debug_omit_kv_commit
? 0
: db->submit_transaction_sync(synct);
// 放入finalize執行緒佇列,並通知其處理。
std::unique_lock<std::mutex> m(kv_finalize_lock);
kv_committing_to_finalize.swap(kv_committing);
deferred_stable_to_finalize.swap(deferred_stable);
kv_finalize_cond.notify_one();
}
}
涉及延遲指標:
state_kv_queued_lat
、
state_kv_committing_lat
、
kv_lat
kv_finalize_thread
清理執行緒,包含兩個佇列:
-
kv_committing_to_finalize
:再次呼叫_txc_state_proc進入狀態機,設定狀態為STATE_KV_DONE,並執行回撥函式通知使用者io操作完成。 -
deferred_stable_to_finalize
:遍歷deferred_stable中的dbh,呼叫_txc_state_proc進入狀態機,設定狀態為STATE_FINISHING,繼續呼叫_txc_finish,設定狀態為STATE_DONE,狀態機結束,事物完成。
void BlueStore::_kv_finalize_thread() {
while (true) {
// 交換指標
kv_committed.swap(kv_committing_to_finalize);
deferred_stable.swap(deferred_stable_to_finalize);
// 處理kv_committing_to_finalize佇列
while (!kv_committed.empty()) {
TransContext *txc = kv_committed.front();
_txc_state_proc(txc);
kv_committed.pop_front();
}
// 處理deferred_stable_to_finalize
for (auto b : deferred_stable) {
auto p = b->txcs.begin();
while (p != b->txcs.end()) {
TransContext *txc = &*p;
p = b->txcs.erase(p); // unlink here because
_txc_state_proc(txc); // this may destroy txc
}
delete b;
}
deferred_stable.clear();
}
}
涉及延遲指標:
state_deferred_cleanup_lat
、
state_finishing_lat
IO狀態
主要分為SimpleWrite、DeferredWrite、SimpleWrite+DeferredWrite。
已有文章寫的不錯,這裡便不再詳細描述(寫文章+畫圖真是體力活: ),可參見: Ceph BlueStore Write Type章節。
最後YY
BlueStore原始碼分析的系列文章就到此結束了,也算是學習BlueStore的一個總結。貌似還差BlueFS的分析,已有大神寫出 BlueStore-先進的使用者態檔案系統《二》-BlueFS。
由於個人水平有限,對BlueStore也僅限於粗淺的理解,如有錯誤,還請指出。
參考資源
作者:史明亞
為研發提效,全是技術乾貨的滴滴雲技術沙龍報名中!
馬上關注滴滴雲公眾號:
回覆「上課」獲取免費報名資格
回覆「伺服器」免費獲得雲伺服器入門1個月體驗
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559758/viewspace-2690750/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- BlueStore原始碼分析之FreelistManager原始碼
- BlueStore原始碼分析之Stupid分配器原始碼
- [Android] 狀態機 StateMachine 原始碼剖析AndroidMac原始碼
- SpringBoot系列——狀態機(附完整原始碼)Spring Boot原始碼
- React Fiber原始碼分析 第三篇(非同步狀態)React原始碼非同步
- Fabric 1.0原始碼分析(19) Ledger #statedb(狀態資料庫)原始碼資料庫
- useSyncExternalStoreExports 狀態原始碼解釋Export原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- React原始碼分析與實現(二):狀態、屬性更新 -> setStateReact原始碼
- 原始碼分析之 HashMap原始碼HashMap
- 原始碼分析之 LinkedList原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- 直播app原始碼,狀態列和導航欄設定成透明狀態APP原始碼
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- Android 原始碼分析之 EventBus 的原始碼解析Android原始碼
- VNPY 交易所返回委託和交易狀態到策略的原始碼分析原始碼
- SpringStateMachine狀態機之八-整合測試SpringMac
- lodash原始碼分析之isArguments原始碼
- Envoy原始碼分析之Dispatcher原始碼
- Fresco原始碼分析之DraweeView原始碼View
- Netty原始碼分析之LengthFieldBasedFrameDecoderNetty原始碼
- RecyclerView之SnapHelper原始碼分析View原始碼
- tornado 原始碼之 coroutine 分析原始碼
- lodash原始碼分析之isObjectLike原始碼Object
- OpenGL 之 GPUImage 原始碼分析GPUUI原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- 原始碼分析Kafka之Producer原始碼Kafka
- DRF之Response原始碼分析原始碼
- Spring AOP之原始碼分析Spring原始碼
- JUC之ReentrantLock原始碼分析ReentrantLock原始碼
- JUC之CountDownLatch原始碼分析CountDownLatch原始碼
- Fresco原始碼分析之Hierarchy原始碼
- Dubbo之SPI原始碼分析原始碼
- MongoDB原始碼分析之MongosXFMongoDB原始碼
- Flutter原始碼分析之InheritedWidgetFlutter原始碼
- Redux原始碼分析之createStoreRedux原始碼