ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ceph RBD镜像锁机制详解:排他锁原理与运维排查实战

Ceph RBD镜像锁机制详解:排他锁原理与运维排查实战 1. 先弄明白rbd镜像的锁到底是什么1.1 一个被多挂盘“教做人”的运维现场先说个真实场景。前几年我维护一套Ceph集群某天业务方反馈说数据库虚机突然IO卡死rbd status看了半天镜像没有异常OSD也全部activeclean但那一块块rbd盘就是写不进去。排查到最后发现是另一个测试环境的主机把这块镜像也挂上了两边同时有IO进来librbd客户端和OSD之间因为锁冲突直接僵在那了。这就是rbd镜像锁在干活——它不让两个客户端同时写同一块块设备。Ceph的rbd镜像在默认开启exclusive-lock特性时任何客户端想要对镜像执行写操作必须先拿到这把锁。拿不到锁客户端连写IO都不会发出去。很多人用Ceph做云平台后端OpenStack Nova或Cinder在挂载块设备时底层实际都在跟这把锁打交道。说白了它就像卫生间门口那个指示灯灯亮着你只能在门外等强行推门进去里面的人和你都得遭殃。1.2 排他锁分布式存储的“单写者”协议rbd镜像的锁准确叫法是exclusive lock排他锁。它的职责是在分布式环境下保证“同一时刻只有一个客户端能对镜像发起写操作”。为什么需要它因为底层rbd块设备对应的是分布在不同OSD上的多个对象如果两个客户端同时往同一块地址写对象内的数据版本会互相覆盖而OSD层做不到像本地文件系统那样的细粒度锁。排他锁的粒度是整个镜像不是某个对象或某个偏移范围。也就是说谁拿到锁谁就拥有对整个镜像的写权限。读操作不需要锁任何客户端都可以直接读这也符合块设备最常见的“单写多读”使用模型。锁还有一个关键性质它不是永久持有。客户端持有锁后会通过watch机制持续向镜像头对象续约一旦客户端异常退出或网络隔离锁会在一段时间后自动释放或被其他客户端抢占。这个“能抢锁”的设计是高可用集群切换时必须依赖的否则一台宿主机宕机虚拟机的盘永远没人能接管了。1.3 锁是镜像特性不是OSD层自动默认新手最容易误解的一点是以为Ceph的OSD天然就有锁。实际上rbd镜像的锁是rbd镜像层面的特性在镜像创建时通过features控制的。你可以用rbd info查看一个镜像的features如果列表里有exclusive-lock那说明这块镜像启用了排他锁机制。镜像特性可以在创建时指定也可以后期动态修改。比如早期创建的镜像可能只有layering特性后来业务需要迁移场景才把exclusive-lock加上。需要注意关闭这个特性和开启这个特性都是有代价的后面我会专门展开。2. 排他锁的实现机制头对象、watch与锁记录2.1 锁信息存哪儿镜像头对象rbd镜像在Ceph里不是单个对象而是一个对象集合其中最特殊的是rbd_header头对象。它在镜像创建时生成像一块“门牌”区域专门记录元数据镜像名称、大小、特性、快照列表以及锁记录。old format和v2 format在头对象命名上不一样但锁相关的逻辑是基本共通的。锁记录写在头对象的locker字段里内容包括持锁客户端的唯一标识通常是client.进程号或主机名、Pod名字锁的cookie随机生成的一串标识符锁类型exclusive就是排他锁持锁时间戳等相关信息用rados -p rbd get rbd_header.xxx /tmp/header --snapshot这类命令能dump出头对象的原始内容锁就明明白白躺在里面。平时我们不用这么底层但理解这一点后续排障时会很清楚自己改的是什么。2.2 watch/notify把锁的状态变化广播出去锁的管理依赖Ceph底层的watch/notify机制。简单说获取锁成功的客户端会在头对象上注册一个watch其他客户端也可以注册watch用于感知锁的变化。场景是这样的客户端A拿到锁客户端B也想写于是B尝试获取同一把锁但锁已被A持有。此时B有两个选择一个是立刻失败另一个是“等待锁释放”。实际实现中B可以设置一个锁超时时间也就是说B会阻塞在那里等直到A释放锁或者watch收到通知说A已经失去锁B再去抢。watch/notify还承担了“心跳”的功能。A持有锁期间会定期向头对象发出通知如果A宕机watch连接就会断开。OSD管辖头对象时会判定A的watch超时随后将锁标记为可抢占。这个机制的响应速度直接决定了故障切换的快慢。2.3 一次典型加锁的完整动作我把一次加锁的完整动作拆开讲你感受下这个协议的精妙之处。第一步客户端Open镜像。librbd读取头对象发现镜像启用了exclusive-lock特性于是进入“需要获取锁”的状态。第二步客户端在头对象上注册watch然后调用锁申请接口。OSD收到请求检查头对象里的locker记录当前是否有人持有锁。没人持有就写入客户端的锁记录申请成功。第三步客户端继续执行后续IO。此后的每次写操作它都会直接向对象发送写请求对象在写入前还会再检查一次该客户端确实持有锁。这就是为什么锁还能防止“客户端自己忘了拿锁就写”的野路子行为。第四步客户端正常关闭镜像时会主动释放锁清掉头对象上的locker记录。如果异常退出watch超时后锁被标记为过期等待其他客户端抢占。第五步其他客户端如果配置了锁等待会在watch上收到锁被释放或抢占的通知随即尝试重新获取锁。这套机制说起来简单但它解决的是分布式系统里最头疼的“谁有权写”的问题。rbd没有引入额外的锁服务而是完全复用Ceph底层的对象操作和watch/notify能力这也是它能在大规模集群里稳定跑的原因之一。3. 锁的运维实操查看、释放、监控一条龙3.1 加锁、查锁和解锁的常用命令平时运维中锁相关操作主要靠rbd命令完成。我直接把常用的列出来附带解释。# 查看某块镜像当前是否有锁以及锁被谁持有 rbd lock ls --pool rbd --image disk-001 # 手动获取一把独占锁不常用但测试时有用 rbd lock add --pool rbd --image disk-001 testlock # 手动释放指定锁id参数填锁名cookie参数可以省略或填具体值 rbd lock remove --pool rbd --image disk-001 testlock # 查看镜像的watcher信息可以看到谁在“看着”这个镜像 rbd status --pool rbd --image disk-001rbd lock ls的输出一般是这样的Client Address Cookie client.82 10.0.0.11:0/123 auto这里client.82是librbd客户端的名字Address是客户端所在主机和tcp地址Cookie是锁的唯一标识。有了这些信息你就能判断是谁占着锁。rbd status会输出类似“Watcher: client.9110.0.0.22:0/456;”的信息它告诉你当前有哪些客户端watch了这个镜像。注意watch和lock不是一回事看的人可以有很多但拿锁的人只能有一个。排查时两者要互相印证。3.2 锁残留的常见场景与强制解锁锁本身不是bug真正让人头疼的是“锁残留”。最常见的一个残留场景某个客户端所在的宿主机宕机或网络隔离后Ceph侧没有及时把锁清掉导致新的客户端长时间拿不到锁镜像打不开业务起不来。出现这种情况官方本来就给你提供了强制解锁手段# 强制清掉某个锁cookie写锁名即可 rbd lock remove --pool rbd --image disk-001 client.82 auto执行后头对象里的locker记录会被清掉。但这只是头对象层面的清理。那个还活着的客户端其实并不知道自己的锁已经被强制移除了它依然会继续发写IO。OSD在收到它的写请求时会检查locker记录发现它已经没有锁了于是直接拒绝写入并把这个客户端加入blacklist。所以强制解锁是一把双刃剑它让新的客户端有机会获取锁但也会让旧的客户端陷入“IO失败”状态。如果是宿主机确实已经宕机这个操作是合理的但如果你搞错了被清的锁其实是一个健康客户端正在使用的那业务IO会立刻异常虚机里会出现文件系统只读、数据库报错等连锁反应。3.3 三个必须记住的参数实际集群里锁相关的行为是可以通过配置调优的。我重点提三个参数大家根据自己的业务场景调整。第一个是rbd_blacklist_on_break_lock默认值是true。它控制的是当有人通过rbd lock remove强制破锁时是否把原先的持锁客户端加入OSD黑名单。如果设为false则破锁后原客户端还有可能继续IO但数据一致性风险极高。我的建议是保持默认true安全大于一切。第二个是rbd_lock_timeout默认0表示客户端获取锁失败后立即报错。设成大于0的秒数比如10客户端就会在拿不到锁时等待这么久期间不断重试。对经常做故障切换的集群设个合理值能避免虚机在切换期间直接报IO错误而是等锁释放后自行恢复。第三个是rbd_request_timed_out_seconds它决定客户端等待一个请求的全局超时时间。实际排查中我发现很多“锁等待卡死”都跟它有关——耐心等锁的客户端如果这个值设得太小反而会提前放弃。这三个参数集群重启时可以通过ceph daemon osd.0 config set在线调整但最好是统一写进ceph.conf里防止重启后丢失。4. 常见问题与排查技巧实录4.1 镜像卡在opening/等待锁超时有段时间我频繁遇到云平台创建虚机时镜像一直处于opening状态最后超时。用rbd status一看镜像上挂着一个来自“疑似已宕机”主机的watch。这台机器能在rbd status里看到但ping不通ens级问题。排查思路很清晰先确认原主机确实不可用然后直接清理watch和锁。清理watch需要用到一些底层的rados操作但多数情况下直接执行rbd lock remove就够了因为锁清掉后watch也会随之失效。值得提醒的是如果你发现镜像卡在打开阶段但镜像本身并没有锁记录那问题可能不是锁而是客户端和OSD之间的网络连通性。别一看到卡住就往锁上猜先分清是“拿不到锁”还是“根本联系不上OSD”。4.2 强制破锁后客户端IO直接挂掉这是个经典的“我本将心向明月”案例。我以为某块镜像是一个废弃虚拟机在占用直接rbd lock remove把锁清了。结果那台“废弃”虚机其实还在跑业务侧立刻报IO错误虚机内出现EXT4-fs error、数据库事务写不进去之类的问题。原因是rbd_blacklist_on_break_lock默认开启破锁后原客户端被加入了黑名单它的原watch连接也失效了。客户端收到-EBLACKLISTED错误所有IO都被打回意识不到竞争关系明确以前任何强制操作都存在风险。从那次以后我给自己定了一条规矩执行强制破锁前必须能看到原客户端所在宿主机已经关机或网络隔离的明确证据。拿不准就先联系对应业务的负责人确认哪怕多花十分钟也比误伤业务强。4.3 性能优化与锁相关设计的副作用有些团队为了追求性能会关闭exclusive-lock特性理由是锁竞争会给每次写IO带来额外开销。确实每次写操作OSD都要检查锁会让写入路径多一次对象属性的读取。在低配机械盘集群上这个开销可能很明显。但关闭排他锁的后果是你无法再保证“单写者”协议。块设备一旦被多个客户端同时挂载写那就是自找麻烦——轻则数据错乱重则整个镜像损坏连rbd restore都救不回来。我的建议是不要把排他锁和性能对立起来。性能瓶颈通常出在磁盘IOPS、网络带宽或EC校验上锁检查的消耗占比微乎其微。真正想优化应该从缓存策略、副本池和EC池的选型入手。排他锁是保命的机制不值得用数据安全去换那一点点性能。4.4 关闭exclusive-lock的后果与替代方案如果某个场景真的需要多客户端同时挂载写同一块rbd镜像比如Oracle RAC这类共享存储集群标准的做法不是粗暴关闭exclusive-lock而是需要考虑更合适的产品形态。Ceph官方对RBD多写场景的支持力度一直有限即使有journaling特性加rbd mirror也不是为了多写设计的。正确姿势是如果真的要多写建议把数据库分成多个rbd镜像每个实例挂自己的盘或者改用CephFS共享文件系统走文件锁机制再或者用支持多写的存储方案比如Lustre或GFS等分布式文件系统。有一种临时做法是在镜像上关闭exclusive-lock让多个客户端同时“裸写”但这属于饮鸩止渴数据安全性完全没有保障只适合测试环境临时验证生产环境千万别碰。5. 我踩过的坑和经验总结5.1 别在业务高峰随手破锁我吃过一次很大的亏。某个上午业务高峰一个rbd镜像被某台主机异常占用新主机无法获取锁。图省事我直接在业务侧没有确认的情况下执行了rbd lock remove结果新主机拿到锁后旧主机还在尝试IO两边在同一块盘上交叉写虚机文件系统直接出现大量错误。事后复盘正确做法应该分三步先确认旧主机状态再决定是否要把旧主机联系从平台上摘除然后在业务低峰执行破锁最后等新主机拿到锁做一次文件系统检查确认数据没有异常再恢复对外服务。很多问题的根源不在锁本身而是操作者太着急。5.2 先看watchers再看锁锁和watch是两回事但排障时两者必须一起看。rbd status能告诉你谁在watch镜像rbd lock ls能告诉你谁在持有锁。如果watch里有多个客户端但只有一个持有锁那是正常状态如果lock ls里显示的客户端在status里根本不在watcher列表里这更像是客户端异常退出后锁没清干净的残留。反过来如果lock ls显示没有锁但镜像仍然打不开那问题基本不在锁而在别处比如网络策略、osd状态或者客户端配置。把思维从“锁”上抽离出来顺着IO路径去查往往更容易找到真凶。5.3 让“锁”成为自动化运维的常规巡检项最后提一个建议把这套锁机制做成巡检脚本而不是只等出故障再手工查。#!/bin/bash # 巡检所有rbd镜像找出持有锁但超过N小时未变化的记录 rbd ls --pool rbd | while read img; do echo $img rbd lock ls --pool rbd --image $img 2/dev/null done锁记录长时间不变化一般意味着要么客户端正常持有但低频写要么就是残留锁。加一个时间维度去判断比我前面说的“看是不是宕机”更实用。单靠人工一个个看集群规模大了根本忙不过来。rbd镜像的锁就像分布式存储世界的红绿灯。平时它安安静静工作你不觉得它有多重要一旦它出问题整个业务都会卡在原地。我这些年做集群运维凡是数据一致性问题最后追到根因十有八九都能跟“锁”扯上关系。理解它、尊重它、监控它这是每个和Ceph打交道的工程师都值得花功夫的事。
返回列表