概述
EC由于需要对原始数据进行编码、解码,覆盖写时必须保证边界对齐。实际情况下客户端发起覆盖写入时传入的offset和length是随机的,因此EC执行时需要先将原始offset和length按stripe进行对齐,读取对齐后对应的数据片段,根据客户传入的新数据对读取的数据进行修改,完成后写入底层,整个流程为read-modify-write(rmw)。
内部实现
使用EC时,ECBackend
负责每个pg的EC相关操作。由于覆盖写入经常需要先读取数据,因此在ECBackend
中实现了一个cache:ExtenCache
,用来管理每个pg已经读到本地内存中的extent,当一次PGTransaction完成后,这些extent会释放掉。
完成一次PGTransaction需要在ECBackend
中生成一个Op
,在不同阶段,Op
位于不同队列中
-
Op
初始化后放在waiting_state
队列,等待进一步处理 -
Op
被从waiting_state
队列取出后,直接放入waiting_reads
队列,并向其他shard发起读取请求 - 所有读取请求都完成后,
Op
被移至waiting_commit
队列,并向其他shard发起写入请求 - 发往每个shard的写入请求完成后,
Op
从waiting_commit
队列移除,至此,一个Op
才算完成
在上面这些步骤中 EC 完成了 read-modify-write ,下面详细介绍。
-
READ
为了实现read,ECBackend
在每个Op
中放入了一个ECTransaction::WritePlan
,write_plan 负责记录当前需要写入的extent(WritePlan::will_write
)和由于对齐原因需要读取的extent(WritePlan::to_read
)。将Op
的write_plan 初始化后直接放入了waiting_state
队列。计算 write_plan 过程并不复杂,根据用户的PGTransaction信息和目标对象的HashInfo
扩展属性即可计算出为了按 stripe 对齐,还需要读取哪些数据。
有了 write_plan 后,就可以查找ExtentCache
中是否包含 WritePlan::to_read
中的 extent,对于不在 cache 中的 extent,放入Op::remote_read
中,表示需要从各个shard读取的数据(这些数据是stripe对齐的,和用户要写入的数据范围有重叠),而Op::remote_read_result
则用来存放最终读取结果。
向各个 shard 发起读请求是通过两个回调类ClientAsyncReadStatus
和CollectionContext
以及两个关键数据结构read_request_t
和ReadOp
完成,ECBackend::handle_sub_op_reply
时会将结果放在ReadOp::complete
中,需要的 shard 读取都完成后,通过ECBackend::complete_read_op()
将 shard 的数据解码,最终放入Op::remote_read_result
。
-
MODIFY
modify 由ECTransaction::generate_transactions()
实现,此函数根据PGTransaction
对已经读到的数据进行修改(这些是没按stripe对齐的extent请求),已经 stripe 对齐的 extent 请求只需正常写入即可。最终生成发往每个shard 的ObjectStore::Transaction
,返回给调用者一个写入后的最新数据,调用者据此更新cache。
-
WRITE
write 动作只是将已经生成好的ObjectStore::Transaction
发送给各个shard,写入请求都返回后对cache进行清理,释放掉前面使用过的extent。