OSD绑定的Public Messenger 收到客户端发送的读写请求后,通过OSD注册的回调函数——ms_fast_dispatch
进行快速派发:
waiting_on_map
队列,获取OSDMap,,并将其与waiting_on_map
队列的所有op进行逐一比较——如果OSD当前的OSDMap的Epoch不小于op所携带的Epoch,则进一步将其派发至OSD的op_shardedwq
队列(OSD内部的工作队列);否则直接终止派发。waiting_on_map
不为空,说明至少存在一个op,其携带的Epoch比OSDMap更新,此时将其加入OSD全局session_waiting_for_map
集合,该集合汇集了当前所有需要等待OSD更新完OSDMap之后才能继续处理的会话上下文;否则将对应的会话从session_waiting_for_map
中移除。上面有些概念我们先抛砖引玉,下面我们具体讲
do_request
作为PG处理op的第一步,主要完成全局(PG级别的)检查:
Epoch——如果op携带的Epoch更新,那么需要等待PG完成OSDMap同步之后才能进行处理。
op能否被直接丢弃——一些可能的场景有:
PG自身的状态如果不为Active,op同样会被阻塞。
PG内部维护了许多不同类型的重试队列,保证请求按顺序被处理。当对应限制解除之后,op会重新进入op_shardedwq
,等待被PG执行。
do_op进行对象级别的检查:
1)按照op携带的操作类型,初始化op中各种标志位。
2)完成对op的合法性校验,不合法的情况包括:1.PG包含op所携带的对象;2.op之间携带可以并发执行的标志 ;3. 客户端权限不足;4. op携带对象名称、key或者命名空间长度超过最大限制(只有在FileStore下存在此限制)5.op对应客户端被纳入黑名单 6. op在集群被标记为Full之前发送 7. PG所在的OSD存储空间不足 8. op包含写操作并且企图访问快照对象 9. op包含写操作并且一次写入量太大(超过osd_max_write_size
)。
3)检查op携带的对象是否不可读或者处于降级状态或者正在被scrub(读取数据对象并重新计算校验和),加入相应队列。
4)检查op是否为重发(基于op的repid在当前的Log中查找,如果找到说明为重发)。
5)获取对象上下文,创建OpContext对op进行跟踪,并通过execute_ctx
真正开始执行op。
关于可用存储空间控制
Ceph使用四个配置项,
mon_osd_full_ratio
、mon_osd_nearfull_ratio
、osd_backfill_full_ratio
(OSD空间利用率大于此值,PG被拒绝以backfill方式迁入)、osd_failsafe_full_ratio
(防止OSD磁盘被100%写满的最后一道屏障)。每个OSD通过心跳机制周期性的检测自身空间利用率,并上报至Monitor。
osd_backfill_full_ratio
的存在意义是有些数据迁移是自动触发的,我们无法预料到自动数据平衡后数据会落到哪个磁盘,因此必须设置此项进行控制。关于对象上下文 (原书134页图4-3)
对象上下文主要保存了对象OI(Object Info)和SS(Snap Set)属性。同时表明对象是否仍然存在。查找head对象上下文相对简单,如果没有在缓存中命中,直接在磁盘中读取即可。然而如果op直接操作快照或者对象克隆,这个过程将变得很复杂。其难点在于一个克隆对象可以对应多个快照,因此需要根据快照序列号定位到某个特定的克隆对象,然后通过分析其位于OI中的snap属性才能进一步判断快照序列号是否位于克隆对象之中。
execute
是真正执行op的步骤。它首先基于当前的快照模式,更新OpContext中的快照上下文(SnapContext)——如果是自定义快照模式,直接基于op携带的快照信息更新;否则基于PGPool更新。
为了保证数据的一致性,所以包含修改操作的PG会预先由Primary通过prepare_transcation
封装为一个PG事务,然后由不同类型的PGBackend负责转化为OS(objectStore,笔者注)能够识别的本地事务,最后在副本间分发和同步。
针对多副本,因为每个副本保存的对象完全相同,所以由Primary生成的PG事务也可以直接作为每个副本的本地事务直接执行。引入纠删码之后,每个副本保存的都是独一无二的分片,所以需要对原始对象的整体操作(对应PG操作)和每个分片操作(对应OS事务)加以区分。
本节介绍如何基于op生成PG级别的事务,这个过程通过prepare_transaction
完成。
do_osd_ops
生成原始op对应的PG事务。head
对象操作,通过make_writable
检查是否需要预先执行克隆操作。finish_ctx
检查是否需要创建或者删除snapdir
对象,生成日志,并更新对象的OI及SS属性。下面我们具体介绍相关流程:
1.5.1 do_osd_ops
trancate_seq
,并和对象上下文保存的truncate_seq
比较从而判定客户端执行write操作和trimtrunc/truncate操作的真实顺序,并对write操作涉及的逻辑地址范围进行修正。osd_max_object_size
)。1.5.2 make_writable
如果op针对head对象进行修改
这里有一个特殊情况——如果当前操作为删除head对象,并且该对象自创建之后没有经历任何修改(此时SnapSet为空),也需要该head对象正常执行克隆后再删除,后续将创建一个snapdir对象来转移这些快照及相关的克隆信息。
clones
集合中记录当前克隆对象中最新快照序列号。clone_size
集合中更新当前克隆对象的大小——因为默认使用全对象克隆,所以克隆对象大小为执行克隆时head对象head对象的实时大小。clone_overlap
集合中记录当前克隆对象与前一个克隆对象之间的重合部分。1.5.3 finish_ctx
顾名思义,finish_ctx
完成事务准备阶段最后的清理工作。
1)如果创建head对象并且snapdir对象存在,则删除snapdir对象,同时生成一条删除snapdir对象的日志;如果删除head对象并且对象仍然被快照引用,则创建snapdir对象,同时生成一条创建snapdir对象的日志,并将head对象的OI和SS属性用snapdir’对象转存
2)如果对象存在,则更新对象OI属性——例如version、last_reqid、mtime等;进一步,如果是head对象,同步更新其SS属性。
3)生成一条op操作原始对象的日志,并追加至现有的OpContext中的日志集合中。
4)在OpContext关联的对象上下文中应用最新的对象状态和SS上下文。
PG事务准备后,如果是纯粹的读操作,如果是同步读(对于多副本),op已经执行完毕,此时可以直接向客户端应答;如果是异步读(针对纠删码),则将op挂入PG内部的异步读队列,等待异步读完成之后再向客户端应答。
如果是写操作,则注册如下几类回调函数:
on_commit
——执行时,向客户端发送写入完成应答;on_success
——执行时,进行与Watch\Notify相关的处理;on_finish
——执行时,删除OpContext。事务的分发与同步由Primary完成,具体而言是通过RepGather实现的。RepGather被提交到PGBackend,后者负责将PG事务转化为每个副本之间的本地事务之后再进行分发。
对于纠删码而言,当涉及覆盖写时,如果改写的部分不足一个完整条带(指写入的起始地址或者数据长度没有进行条带对齐),则需要执行RMW,这期间需要多次执行补齐读、重新生成完整条带并重新计算校验块、单独生成每个副本的事务并构造消息进行分发(Write)、同时在必要时执行范围克隆和对PG日志进行修正,以支持Peering期间的回滚操作。