BlueStore事物状态机是什么

72次阅读
没有评论

共计 10590 个字符,预计需要花费 27 分钟才能阅读完成。

本篇内容主要讲解“BlueStore 事物状态机是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“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- 
 _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 = 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。

到此,相信大家对“BlueStore 事物状态机是什么”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计10590字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)