共計(jì) 10590 個(gè)字符,預(yù)計(jì)需要花費(fèi) 27 分鐘才能閱讀完成。
本篇內(nèi)容主要講解“BlueStore 事物狀態(tài)機(jī)是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓丸趣 TV 小編來帶大家學(xué)習(xí)“BlueStore 事物狀態(tài)機(jī)是什么”吧!
前言
BlueStore 可以理解為一個(gè)支持 ACID 的本地日志型文件系統(tǒng)。所有的讀寫都是以 Transaction 進(jìn)行,又因?yàn)橹С指采w寫,所以寫流程設(shè)計(jì)的相對(duì)復(fù)雜一些,涉及到一系列的狀態(tài)轉(zhuǎn)換。我們著重分析一下狀態(tài)機(jī)、延遲指標(biāo)以及如何保證 IO 的順序性和并發(fā)性。
狀態(tài)機(jī) queue_transactions
queue_transactions 是 ObjectStore 層的統(tǒng)一入口,KVStore、MemStore、FileStore、BlueStore 都相應(yīng)的實(shí)現(xiàn)了這個(gè)接口。state_t state 變量記錄了當(dāng)前時(shí)刻事物處于哪個(gè)狀態(tài)。在創(chuàng)建 TransactionContext 的時(shí)候會(huì)將 state 初始化為 STATE_PREPARE,然后在_txc_add_transaction 中會(huì)根據(jù)操作碼類型 (opcode) 進(jìn)行不同的處理。同時(shí)會(huì)獲取 PG 對(duì)應(yīng)的 OpSequencer(每個(gè) PG 有一個(gè) OpSequencer)用來保證 PG 上的 IO 串行執(zhí)行,對(duì)于 deferred-write 會(huì)將其數(shù)據(jù)寫入 RocksDB(WAL)。
以下階段就進(jìn)入 BlueStore 狀態(tài)機(jī)了,我們以寫流程為導(dǎo)向分析狀態(tài)機(jī)的每個(gè)狀態(tài)。
STATE_PREPARE
從 state_prepare 開始已經(jīng)進(jìn)入事物的狀態(tài)機(jī)了。這個(gè)階段會(huì)調(diào)用_txc_add_transaction 將 OSD 層面的事物轉(zhuǎn)換為 BlueStore 層面的事物;然后檢查是否還有未提交的 IO,如果還有就將 state 設(shè)置為 STATE_AIO_WAIT 并調(diào)用_txc_aio_submit 提交 IO,然后退出狀態(tài)機(jī),之后 aio 完成的時(shí)候會(huì)調(diào)用回調(diào)函數(shù) txc_aio_finish 再次進(jìn)入狀態(tài)機(jī);否則就進(jìn)入 STATE_AIO_WAIT 狀態(tài)。
_txc_aio_submit 函數(shù)調(diào)用棧:
bdev- aio_submit – KernelDevice::aio_submit – io_submit 將 aio 提交到內(nèi)核 Libaio 隊(duì)列。
主要工作:準(zhǔn)備工作,生成大小寫、初始化 TransContext、deferred_txn、分配磁盤空間等。
延遲指標(biāo):l_bluestore_state_prepare_lat,從進(jìn)入狀態(tài)機(jī)到 prepare 階段完成,平均延遲大概 0.2ms 左右。
STATE_AIO_WAIT
該階段會(huì)調(diào)用_txc_finish_io 進(jìn)行 SimpleWrite 的 IO 保序等處理,然后將狀態(tài)設(shè)置為 STATE_IO_DONE 再調(diào)用_txc_state_proc 進(jìn)入下一個(gè)狀態(tài)的處理。
主要工作:對(duì) IO 保序,等待 AIO 的完成。
延遲指標(biāo):l_bluestore_state_aio_wait_lat,從 prepare 階段完成開始到 AIO 完成,平均延遲受限于設(shè)備,SSD 0.03ms 左右。
STATE_IO_DONE
完成 AIO,并進(jìn)入 STATE_KV_QUEUED 階段。會(huì)根據(jù) bluestore_sync_submit_transaction 做不同處理。該值為布爾值,默認(rèn)為 false。
如果為 true,設(shè)置狀態(tài)為 STATE_KV_SUBMITTED 并且同步提交 kv 到 RocksDB 但是沒有 sync 落盤(submit_transaction),然后 applied_kv。
如果為 false,則不用做上面的操作,但是以下操作都會(huì)做。
最后將事物放在 kv_queue 里,通過 kv_cond 通知 kv_sync_thread 去同步 IO 和元數(shù)據(jù)。
主要工作:將事物放入 kv_queue,然后通知 kv_sync_thread,osr 的 IO 保序可能會(huì) block。
延遲指標(biāo):l_bluestore_state_io_done_lat,平均延遲在 0.004ms,通常很小主要耗在對(duì) SimpleWrite 的 IO 保序處理上。
STATE_KV_QUEUED
該階段主要在 kv_sync_thread 線程中同步 IO 和元數(shù)據(jù),并且將狀態(tài)設(shè)置為 STATE_KV_SUBMITTED。具體會(huì)在異步線程章節(jié)分析 kv_sync_thread 線程。
主要工作:從 kv_sync_thread 隊(duì)列中取出事物。
延遲指標(biāo):l_bluestore_state_kv_queued_lat,從事物進(jìn)入隊(duì)列到取出事物,平均延遲在 0.08ms,因?yàn)槭菃尉€程順序處理的,所以依賴于 kv_sync_thread 處理事物的速度。
STATE_KV_SUBMITTED
等待 kv_sync_thread 中 kv 元數(shù)據(jù)和 IO 數(shù)據(jù)的 Sync 完成,然后將狀態(tài)設(shè)置為 STATE_KV_DONE 并且回調(diào) finisher 線程。
主要工作:等待 kv 元數(shù)據(jù)和 IO 數(shù)據(jù)的 Sync 完成,回調(diào) finisher 線程。
延遲指標(biāo):l_bluestore_state_kv_committing_lat,從隊(duì)列取出事物到完成 kv 同步,平均延遲 1.0ms,有極大的優(yōu)化空間。
STATE_KV_DONE
如果是 SimpleWrite,則直接將狀態(tài)設(shè)置為 STATE_FINISHING;如果是 DeferredWrite,則將狀態(tài)設(shè)置為 STATE_DEFERRED_QUEUED 并放入 deferred_queue。
主要工作:如上。
延遲指標(biāo):l_bluestore_state_kv_done_lat,平均延遲 0.0002ms,可以忽略不計(jì)。
STATE_DEFERRED_QUEUED
主要工作:將延遲 IO 放入 deferred_queue 等待提交。
延遲指標(biāo):l_bluestore_state_deferred_queued_lat,通常不小,沒有數(shù)據(jù)暫不貼出。
STATE_DEFERRED_CLEANUP
主要工作:清理延遲 IO 在 RocksDB 上的 WAL。
延遲指標(biāo):l_bluestore_state_deferred_cleanup_lat,通常不小,沒有數(shù)據(jù)暫不貼出。
STATE_FINISHING
主要工作:設(shè)置狀態(tài)為 STATE_DONE,如果還有 DeferredIO 也會(huì)提交。
延遲指標(biāo):l_bluestore_state_finishing_lat,平均延遲 0.001ms。
STATE_DONE
主要工作:標(biāo)識(shí)整個(gè) IO 完成。
延遲指標(biāo):l_bluestore_state_done_lat。
延遲分析
BlueStore 定義了狀態(tài)機(jī)的多個(gè)延遲指標(biāo),由 PerfCounters 采集,函數(shù)為 BlueStore::_init_logger()。
可以使用 ceph daemon osd.0 perf dump 或者 ceph daemonperf osd.0 來查看對(duì)應(yīng)的延遲情況。
除了每個(gè)狀態(tài)的延遲,我們通常也會(huì)關(guān)注以下兩個(gè)延遲指標(biāo):
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 延遲主要花費(fèi)在 l_bluestore_state_kv_committing_lat 也即 c_l,大概 1ms 左右。
BlueStore 統(tǒng)計(jì)狀態(tài)機(jī)每個(gè)階段延遲的方法如下:
// 該階段延遲 = 上階段完成到該階段結(jié)束
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;
}
在塊存儲(chǔ)的使用場(chǎng)景中,除了用戶并發(fā) IO 外,通常用戶也會(huì)使用 dd 等串行 IO 的命令,此時(shí)便受限于讀寫的絕對(duì)延遲,擴(kuò)容加機(jī)器、增加線程數(shù)等橫向擴(kuò)展的優(yōu)化便是無效的,所以我們需要關(guān)注兩方面的延遲:并發(fā) IO 延遲、串行 IO 延遲。
并發(fā) IO 延遲優(yōu)化:kv_sync_thread、kv_finalize_thread 多線程化;自定義 WAL;async read。
串行 IO 延遲優(yōu)化:并行提交元數(shù)據(jù)、數(shù)據(jù);將 sync 操作與其他狀態(tài)并行處理。
IO 保序
保證 IO 的順序性以及并發(fā)性是分布式存儲(chǔ)必然面臨的一個(gè)問題。因?yàn)?BlueStore 使用異步 IO,后提交的 IO 可能比早提交的 IO 完成的早,所以更要保證 IO 的順序,防止數(shù)據(jù)發(fā)生錯(cuò)亂。客戶端可能會(huì)對(duì) PG 中的一個(gè) Object 連續(xù)提交多次讀寫請(qǐng)求,每次請(qǐng)求對(duì)應(yīng)一個(gè) Transaction,在 OSD 層面通過 PGLock 將并發(fā)的讀寫請(qǐng)求在 PG 層面串行化,然后按序依次提交到 ObjectStore 層,ObjectStore 層通過 PG 的 OpSequencer 保證順序處理讀寫請(qǐng)求。
BlueStore 寫類型有 SimpleWrite、DeferredWrite 兩種,所以我們分析一下 SimpleWrite、DeferredWrite 下的 IO 保序問題。
SimpleWrite
因?yàn)?STATE_AIO_WAIT 階段使用 Libaio,所以需要保證 PG 對(duì)應(yīng)的 OpSequencer 中的 txc 按排隊(duì)的先后順序依次進(jìn)入 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);
// 設(shè)置狀態(tài)機(jī)的 state 為 STATE_IO_DONE
txc- state = TransContext::STATE_IO_DONE;
// 清除 txc 正在運(yùn)行的 aio
txc- ioc.running_aios.clear();
// 定位當(dāng)前 txc 在 osr 的位置
OpSequencer::q_list_t::iterator p = osr- q.iterator_to(*txc);
while (p != osr- q.begin()) {
--p;
// 如果前面還有未完成 IO 的 txc,那么需要停止當(dāng)前 txc 操作,等待前面 txc 完成 IO。 // 目的是:確保之前 txc 的 IO 都完成。 if (p- state TransContext::STATE_IO_DONE) {
return;
}
// 前面的 txc 已經(jīng)進(jìn)入大于等于 STATE_KV_QUEUED 的狀態(tài)了,那么遞增 p 并退出循環(huán)。 // 目的是:找到狀態(tài)為 STATE_IO_DONE 的且在 osr 中排序最靠前的 txc。 if (p- state TransContext::STATE_IO_DONE) {
++p;
break;
}
}
// 依次處理狀態(tài)為 STATE_IO_DONE 的 tx
// 將 txc 放入 kv_sync_thread 的 kv_queue、kv_queue_unsubmitted 隊(duì)列
do { _txc_state_proc( *p++);
} while (p != osr- q.end() p- state == TransContext::STATE_IO_DONE);
......
}
DeferredWrite
DeferredWrite 在 IO 的時(shí)候也是通過 Libaio 提交到內(nèi)核 Libaio 隊(duì)列進(jìn)行寫數(shù)據(jù),也需要保證 IO 的順序性。
相應(yīng)的數(shù)據(jù)結(jié)構(gòu)如下:
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 內(nèi)部包含一個(gè)成員變量 deferred_queue;deferred_queue 隊(duì)列包含需要執(zhí)行 DeferredIO 的 OpSequencer;每個(gè) OpSequencer 包含 deferred_running 和 deferred_pending 兩個(gè) DeferredBatch 類型的變量;DeferredBatch 包含一個(gè) txc 數(shù)組。
如果 PG 有寫請(qǐng)求,會(huì)在 PG 對(duì)應(yīng)的 OpSequencer 中的 deferred_pending 中排隊(duì)加入 txc,待時(shí)機(jī)成熟的時(shí)候,一次性提交所有 txc 給 Libaio,執(zhí)行完成后才會(huì)進(jìn)行下一次提交,這樣不會(huì)導(dǎo)致 DeferredIO 亂序。
void BlueStore::_deferred_queue(TransContext *txc)
deferred_lock.lock();
// 排隊(duì) 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)
......
// 切換指針,保證每次操作完成后才會(huì)進(jìn)行下一次提交
osr- deferred_running = osr- deferred_pending;
osr- deferred_pending = nullptr;
......
while (true) {
......
// 準(zhǔn)備所有 txc 的寫 buffer
int r = bdev- aio_write(start, bl, b- ioc, false);
}
......
// 一次性提交所有 txc
bdev- aio_submit(b- ioc);
}
線程隊(duì)列
線程 + 隊(duì)列是實(shí)現(xiàn)異步操作的基礎(chǔ)。BlueStore 的一次 IO 經(jīng)過狀態(tài)機(jī)要進(jìn)入多個(gè)隊(duì)列并被不同的線程處理然后回調(diào),線程 + 隊(duì)列是 BlueStore 事物狀態(tài)機(jī)的重要組成部分。BlueStore 中的線程大致有 7 種。
mempool_thread:無隊(duì)列,后臺(tái)監(jiān)控內(nèi)存的使用情況,超過內(nèi)存使用的限制便會(huì)做 trim。
aio_thread:隊(duì)列為 Libaio 內(nèi)核 queue,收割完成的 aio 事件。
discard_thread:隊(duì)列為 discard_queued,對(duì) SSD 磁盤上的 extent 做 Trim。
kv_sync_thread:隊(duì)列為 kv_queue、deferred_done_queue、deferred_stable_queue,sync 元數(shù)據(jù)和數(shù)據(jù)。
kv_finalize_thread:隊(duì)列為 kv_committing_to_finalize、deferred_stable_to_finalize,執(zhí)行清理功能。
deferred_finisher:調(diào)用回調(diào)函數(shù)提交 DeferredIO 的請(qǐng)求。
finishers:多個(gè)回調(diào)線程 Finisher,通知用戶請(qǐng)求完成。
我們主要分析 aio_thread、kv_sync_thread、kv_finalize_thread。
aio_thread
aio_thread 比較簡(jiǎn)單,屬于 KernelDevice 模塊的,主要作用是收割完成的 aio 事件,并觸發(fā)回調(diào)函數(shù)。
void KernelDevice::_aio_thread() { while (!aio_stop) {
......
// 獲取完成的 aio
int r = aio_queue.get_next_completed(cct- _conf- bdev_aio_poll_ms, aio, max);
// 設(shè)置 flush 標(biāo)志為 true。 io_since_flush.store(true);
// 獲取 aio 的返回值
long r = aio[i]- get_return_value();
......
// 調(diào)用 aio 完成的回調(diào)函數(shù)
if (ioc- priv) { if (--ioc- num_running == 0) { aio_callback(aio_callback_priv, ioc- priv);
}
}
}
}
涉及延遲指標(biāo):state_aio_wait_lat、state_io_done_lat。
kv_sync_thread
當(dāng) IO 完成后,要么將 txc 放入隊(duì)列,要么將 dbh 放入隊(duì)列,雖然對(duì)應(yīng)不同隊(duì)列,但都是由 kv_sync_thread 執(zhí)行后續(xù)操作。
對(duì)于 SimpleWrite,都是寫新的磁盤 block(如果是 cow,也是寫新的 block,只是事務(wù)中 k / v 操作增加對(duì)舊的 block 的回收操作),所以先由 aio_thread 寫 block,再由 kv_sync_thread 同步元信息,無論什么時(shí)候掛掉,數(shù)據(jù)都不會(huì)損壞。
對(duì)于 DeferredWrite,在事物的 prepare 階段將需要 DeferredWrite 的數(shù)據(jù)作為 k / v 對(duì) (也稱為 WAL) 寫入基于 RocksDB 封裝的 db_transaction 中,此時(shí)還在內(nèi)存,kv_sync_thread 第一次的 commit 操作中,將 wal 持久化在了 k / v 系統(tǒng)中,然后進(jìn)行后續(xù)的操作,異常的情況,可以通過回放 wal,數(shù)據(jù)也不會(huì)損壞。
kv_sync_thread 主要執(zhí)行的操作為:在 Libaio 寫完數(shù)據(jù)后,需要通過 kv_sync_thread 更新元數(shù)據(jù) k /v,主要包含 object 的 Onode、擴(kuò)展屬性、FreelistManager 的磁盤空間信息等等,這些必須按順序操作。
涉及的隊(duì)列如下:
kv_queue:需要執(zhí)行 commit 的 txc 隊(duì)列。將 kv_queue 中的 txc 存入 kv_committing 中,并提交給 RocksDB,即執(zhí)行操作 db- submit_transaction,設(shè)置狀態(tài)為 STATE_KV_SUBMITTED,并將 kv_committing 中的 txc 放入 kv_committing_to_finalize,等待線程 kv_finalize_thread 執(zhí)行。
deferred_done_queue:已經(jīng)完成 DeferredIO 操作的 dbh 隊(duì)列,還沒有 sync 磁盤。這個(gè)隊(duì)列的 dbh 會(huì)有兩種結(jié)果: 1) 如果沒有做 flush 操作,會(huì)將其放入 deferred_stable_queue 待下次循環(huán)繼續(xù)處理 2) 如果做了 flush 操作,說明數(shù)據(jù)已經(jīng)落盤,即已經(jīng)是 stable 的了,直接將其插入 deferred_stable_queue 隊(duì)列。這里 stable 的意思就是數(shù)據(jù)已經(jīng) sync 到磁盤了,前面 RocksDB 中記錄的 wal 沒用可以刪除了。
deferred_stable_queue:DeferredIO 已經(jīng)落盤,等待清理 RocksDB 中的 WAL。依次操作 dbh 中的 txc,將 RocksDB 中的 wal 刪除,然后 dbh 入隊(duì)列 deferred_stable_to_finalize,等待線程 kv_finalize_thread 執(zhí)行。
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-
_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,有設(shè)置 bluefs_extents、刪除 wal 兩種操作
int r = cct- _conf- bluestore_debug_omit_kv_commit
? 0
: db- submit_transaction_sync(synct);
// 放入 finalize 線程隊(duì)列,并通知其處理。 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();
}
}
涉及延遲指標(biāo):state_kv_queued_lat、state_kv_committing_lat、kv_lat
kv_finalize_thread
清理線程,包含兩個(gè)隊(duì)列:
kv_committing_to_finalize:再次調(diào)用_txc_state_proc 進(jìn)入狀態(tài)機(jī),設(shè)置狀態(tài)為 STATE_KV_DONE,并執(zhí)行回調(diào)函數(shù)通知用戶 io 操作完成。
deferred_stable_to_finalize:遍歷 deferred_stable 中的 dbh,調(diào)用_txc_state_proc 進(jìn)入狀態(tài)機(jī),設(shè)置狀態(tài)為 STATE_FINISHING,繼續(xù)調(diào)用_txc_finish,設(shè)置狀態(tài)為 STATE_DONE,狀態(tài)機(jī)結(jié)束,事物完成。
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 隊(duì)列
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 = b- txcs.erase(p); // unlink here because
_txc_state_proc(txc); // this may destroy txc
}
delete b;
}
deferred_stable.clear();
}
}
涉及延遲指標(biāo):state_deferred_cleanup_lat、state_finishing_lat
IO 狀態(tài)
主要分為 SimpleWrite、DeferredWrite、SimpleWrite+DeferredWrite。
到此,相信大家對(duì)“BlueStore 事物狀態(tài)機(jī)是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是丸趣 TV 網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!