【Ceph RADOS】客户端写路径:从 librados 到副本应答与 stale read 边界
内容提要
本文介绍Ceph RADOS客户端写路径:librados通过Objecter计算目标PG和Primary OSD,发送MOSDOp请求;Primary执行事务并协调副本应答,默认等待持久化后确认。读操作依赖readable_until租约防止stale read,跨Primary切换时通过LAGGY/WAIT状态阻塞请求。客户端通过epoch追赶和reqid去重保证一致性。
延伸解读
写确认的持久化语义
默认情况下,客户端收到写成功回复时,数据已写入所有副本的WAL或journal,但未必已fsync到磁盘。BlueStore的deferred write机制允许在保证持久性的前提下延迟实际落盘,从而提升吞吐。理解这一语义对评估数据安全至关重要:若在确认后立即断电,数据可能仍在WAL中,但Ceph的恢复机制可保证一致性。
读租约与故障检测的权衡
read_lease_interval默认约为osd_heartbeat_grace的0.8倍,意图是在MON标记OSD down之前租约已过期,防止旧Primary继续服务读。调大heartbeat_grace可减少误判,但会拉长旧Primary可读的窗口,增加stale read风险。在跨AZ或Stretch模式部署时,需权衡故障检测延迟与一致性窗口。
副本读与一致性模型
默认Primary-only读与读租约模型一致,保证强一致。若启用BALANCE_READS或LOCALIZE_READS,读可能落到副本,一致性弱化,需应用自行处理可能读到旧数据。RBD默认不走副本读,跨AZ场景若用LOCALIZE_READS换延迟,必须明确可接受的数据新鲜度,并理解租约与map的配合,这属于一致性模型变更而非简单开关。
Q&A
Ceph 客户端写路径中,librados 是如何确定目标 OSD 的?
librados 将操作委托给 Objecter,Objecter 维护 OSDMap 缓存,通过 CRUSH 计算目标 PG 和 Primary OSD。具体步骤:对对象名进行哈希,映射到 PG,再通过 CRUSH 计算 acting set,acting[0] 即为 Primary OSD。
Ceph 写操作中,客户端默认等待什么条件才认为写成功?
默认情况下,客户端等待所有副本的 journal/WAL 持久化后才收到确认,而不是等待 fsync 完成。这由 MOSDOp 的 flags 中的 CEPH_OSD_FLAG_ONDISK 决定,Squid 版本默认等待副本 WAL 持久化。
Ceph 中 stale read 是什么?如何防止?
stale read 是指被隔离的旧 Primary OSD 在新区已经接受写之后,仍为落后的客户端提供读服务,导致读到旧数据。防止机制是读租约(readable_until),Primary 和副本维护租约,租约过期后进入 LAGGY 状态阻塞读请求,直到租约续期或等待足够时间。
Ceph 中 LAGGY 和 WAIT 状态有什么区别?
LAGGY 状态发生在 Active 期间租约过期仍收到请求时,读请求被阻塞,租约续上后重入队;WAIT 状态发生在 Peering 已完成但 prior interval 的 OSD 仍可能可读时,阻塞客户端请求直至等待足够或提前退出。LAGGY 是租约过期,WAIT 是等待旧租约失效。
Ceph 写路径中,Primary OSD 如何保证副本一致性?
Primary OSD 是单点序列化者,它分配版本号(eversion),副本不能修改版本号,只能接受或拒绝。Primary 向副本发送 MOSDRepOp,副本应用事务后回复,Primary 收齐策略要求的副本应答后提交并回复客户端。
Ceph 客户端重试机制中,reqid 的作用是什么?
reqid 是请求唯一 ID,由客户端地址、client_inc 和 tid 组成。它用于幂等去重,同一 client_inc 内重发可直接返回旧结果;client_inc 递增后旧 reqid 失效,避免网络分区后盲目重放未确认写。
Ceph 中读租约的时长如何配置?
读租约时长由 pool 的 read_lease_interval 决定,默认约为 osd_pool_default_read_lease_ratio(默认0.8)乘以 osd_heartbeat_grace。调大 osd_heartbeat_grace 会拉长误判 down 的耐心和旧 Primary 可读的窗口。
Ceph 中客户端 map 落后时如何处理?
当客户端 map 落后或 acting 变化时,Objecter 会合并新 map,重新计算目标并重发请求;如果 OSD 尚未追上 map,客户端会等待或订阅后重发。重试中 Op 由 Objecter 持有,不丢请求。