ublk故障恢复方案设计

简介: 简介在ublk简介一文中我们介绍了Linux社区新提出的ublk:基于io_uring的全新高性能用户态块设备。ublk已经合入Linux 6.0主线,并且操作系统团队已经在ublk上开展了一系列实践,并在2022年中国LInux内核开发者大会上分享了阿里云在ublk上的实践。为了适配阿里云的分布式云存储产品,我们对ublk添加了NEED_GET_DATA特性和故障恢复特性。前者对数据拷贝进行优化

简介

ublk简介一文中我们介绍了Linux社区新提出的ublk:基于io_uring的全新高性能用户态块设备。ublk已经合入Linux 6.0主线,并且操作系统团队已经在ublk上开展了一系列实践,并在2022年中国LInux内核开发者大会上分享了阿里云在ublk上的实践。为了适配阿里云的分布式云存储产品,我们对ublk添加了NEED_GET_DATA特性和故障恢复特性。前者对数据拷贝进行优化,属于小特性,不多介绍;后者则是ublk产品化必备的特性,非常重要。本文对ublk故障恢复方案进行介绍,我们前后一共设计了3版方案,在不断的优化与迭代过程中收获颇丰,最终顺利合入6.1主线,并被LWN发文,在此分享给大家。

ublk背景知识

ublk提供了用户态块设备的能力。简单地说,就是提供一个/dev/ublkb*这样的块设备,所有这个设备上的IO请求的读写逻辑都是您在用户态编写的代码,比如pread/pwrite到一个具体地文件(这样就是loop设备了)。使用用户态块设备,你可以方便地向上层业务提供您的存储系统(如ceph),业务只需要对/dev/ublkb*执行标准的读写操作即可。如果您有一个用户态的存储系统(可以是跨多个机器的网络分布式存储),想要向业务暴露/dev/ublkb* 来提供IO读写功能的,就可以考虑ublk。比如您的业务用了容器或者虚拟机,就可以暴露/dev/ublkb*作为他们的磁盘,读写/dev/ublkb*会导致实际IO passthrough(透传)给您的用户态存储系统(如ceph)。如果您没有存储系统,也可以使用ublk,因为ublksrv可以自己在本地运行daemon处理实际IO,如作为qcow target运行向容器或虚拟机提供qcow磁盘。ublk的系统架构如图所示:

                                   
                                   
                                target(backend)------------> null/loop/qcow/socket/ceph/rpc...  
                                      ^
                                      |
                                      |
                                   libublksrv
       fio                            ^
        |                             |        user
--------|-----------------------------|---------------
        |                             |        kernel
        v                             |
    /dev/ubdbX                        |        
        |                             |
        |                             |
        v                          io_uring      
      blk-mq                          ^   
        |                             |
         -----------> ublk_drv---------

如同FUSE要运行fuse daemon来把IO转发给用户态文件系统,ublk需要在用户态运行daemon,称为ublksrvd,进行数据转发。在现有的ublk实现中,daemon一旦退出,会导致内核驱动自动删掉所有资源,包括/dev/ublkb*块设备,避免资源泄露。这种方法使得内核驱动实现简单,但不可用于生产环境,因为daemon进程很容易退出:如内存oom被内核干掉;daemon代码写错了导致段错误;各种用户态的攻击手段或管理员退出等等。为了让ublk产品化,我们需要设计故障恢复机制。

故障恢复设计

先确定一下设计目标:为了让前端业务顺利运行,在daemon退出(称为故障)后,我们不能让/dev/ublkb*消失;且要允许用户通过某些手段,启动新的daemon并关联到该/dev/ublkb*设备,接管所有IO,整个系统继续运行

让前端业务顺利运行

为了满足第一个目标,我们需要一个检测daemon是否退出的机制,现有的ublk会周期性地运行一个monitor_work,检查daemon是否为PF_EXITING,该标记会在进程挂掉时被内核设置。现有的ublk的逻辑是直接abort掉所有的blk-mq请求(blk_mq_end_request),然后del_gendisk()删掉/dev/ublkb*块设备。现在我们需要在开启故障恢复特性时,保留该块设备。因此,我们设计了UBLK_F_USER_RECOVERY标记,用户在创建ublk块设备时指定feature标记,内核在monitor_work检查标记判断是否开启故障恢复特性,否则走原来的删除逻辑,是则保留该块设备(代码上就是什么都不做)

那么,该如何处理故障后发来的用户请求呢?因为ublk位于通用块设备层,它需要实现->queue_rq()接口,每来一个blk-mq请求,->queue_rq()都会被调用一次。现有的ublk实现的ublk_queue_rq()函数会通过io_uring passthrough机制的io_uring command通知用户态daemon有新的请求需要处理(详情看ublk简介)。在发生故障后,我们不能这么做,因为没有用户态daemon了,这里我们想了好几种方案:

  • V1:在ublk_queue_rq()中,请求被放到一个链表 pending_list 中,然后等到新的daemon起来后,把pending list中的请求都下发下去。这种思想是很直接的,但在代码实现时,遇到了很大的困难:由于ublk使用io_uring passthrough机制,每个blk-mq请求都对应一个io_uring command,而这个cmd必须是用户态daemon事先发给内核(io_uring sqe),然后在ublk_queue_rq(),发送io_uring cqe。这使得我们“刷”pending list的时机很难控制:我们要在新的daemon把所有的(队列深度个)io_uring command都发给内核ready后,才能“刷”pending list。然而,倘若只有一部分cmd ready新daemon又crash了该怎么办呢?我们还要考虑旧的cmd的释放问题(因为旧的daemon已经退出了,但是它发给内核的cmd还没被内核返回cqe,导致io_uring ctx在内核没被释放)避免内存泄漏……总之,这种pendling list的方案看上去直接,但很难写出代码,我们卡了一个月有余……在某次浏览其他块设备驱动代码时,笔者突然发现了新曙光。
  • V2:blk-mq提供了blk_mq_freeze_queue()和 blk_mq_quiesce_queue() 函数,顾名思义两者都能让请求下发“暂停”,其功能的差异本文不予赘述。总之,在旧daemon退出后,monitor_work可以调用blk_mq_quiesce_queue(),阻止->queue_rq()的调用,这样新请求就进不来了。这里可能有人要问: 那前端业务会不会不能提交IO,导致业务卡住啊? 经过代码分析,笔者认为不会卡住IO下发,因为blk-mq在接受到上层提交IO(如submit_bio)后,会先让它们排队,队列深度是用户自己设置的(比如128)。当队列quiesced后,请求都留在blk-mq的队列里,在调用 blk_mq_quiesce_unqueue() 后,再对每个请求都->queue_rq()一下。其实 blk_mq_quiesce_queue() 已经帮我们完美地做了一个pending list了,所以以后还是要先积累知识,多看代码,不可贸然下手啊……你能想到的问题,别人早就踩过坑了……

解决了上述“故障后到来的请求该如何处理”问题后,我们又突然想到一类请求没有处理:“故障到来前已经发给旧daemon,但daemon挂了所以我也不知道他做完了没”,为此,我们设计了UBLK_F_USER_RECOVERY_REISSUE标记,用户可选地设置该feature。新daemon关联到/dev/ublkb*后,这类请求会被重新下发一次,因此要求后端的分布式存储有处理重复写入的能力。在代码上,若开启该标记,monitor_work会blk_mq_requeue_request请求(此时是quiesced状态,不会发生->queue_rq),否则直接blk_mq_end_request。可以说,UBLK_F_USER_RECOVERY_REISSUE是从实际业务出发而设计的。

启动新的daemon

这部分的设计简单了许多,我们考虑把恢复分为2阶段:START_RECOVERY和END_RECOVERY。在START_RECOVERY我们必须重置一些状态:比如ublk会记忆旧daemon的mm struct,task struct,都要清理掉;ublk自己的IO结构体有一些状态位,也置位初始。在END_RECOVERY阶段我们要等待新的daemon把所有的io_uring command都下发完成后,unquiesce队列,然后就可以愉快地ublk_queue_rq()并把IO透传给后端分布式存储了!之所以设计成两阶段,一是为了用户方便用(用户自己发START_RECOVERY,启动新的daemon,再发END_RECOVERY,几个阶段很清晰);二是简化了开发(因为ublk记录很多用户态daemon的信息,START_RECOVERY释放并重置信息,END_RECOVERY确认新信息)。

总结

ublk故障恢复是笔者在ublk上开发的第一个大型特性,也是第一次接触block层的代码。由于不熟悉block的实现,我们走了很多弯路,幸好在社区的指导和自我努力下,我们完成了设计与编码,历时3月有余。我们积累了很多Linux知识(如linux work的原理,io_uring ctx的管理,gendisk的管理,block层的API),并为ublk在分布式存储的应用扫清了道路。

参考文献

【1】 https://lwn.net/Articles/906097/

【2】https://developer.aliyun.com/article/989552

【3】https://lore.kernel.org/all/20220523131039.17697-1-ankit.kumar@samsung.com/

【4】https://lore.kernel.org/all/8a52ed85-3ffa-44a4-3e28-e13cdc793732@linux.alibaba.com/

【5】https://github.com/ming1/ubdsrv

【6】https://lore.kernel.org/all/20220713140711.97356-1-ming.lei@redhat.com/

【7】https://lore.kernel.org/all/20220923153919.44078-1-ZiyangZhang@linux.alibaba.com/

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
负载均衡 关系型数据库 RDS
良好架构设计中的可靠性:高可用、容错、灾难恢复
良好架构设计支柱 云计算良好架构设计有五大支柱,分别是:安全性,可靠性,性能效率,成本优化和卓越操作。其中可靠性是指系统从基础设施或者服务故障当中实现恢复、以动态方式获取计算资源以满足需求,以及缓解配置错误或者暂时性网络问题等干扰因素的能力。
4806 0
|
运维 监控 数据库
线上服务故障处理原则
墨菲定律 任何事情都没有表面看起来那么简单 所有事情的发展都会比你预计的时间长 会出错的事情总会出错 如果担心某个事情发生,那么它更有可能发生 墨菲定律暗示我们,如果担心某种情况会发生,那么它更有可能发生,久而久之就一定会发生。
2273 0
|
4月前
|
运维 监控 负载均衡
什么是系统可用性?如何提升可用性?
本文探讨了系统可用性的概念、计算方法及其重要性。可用性指系统能在预定时间内正常运行的比例,计算公式为:(运行时间)/(运行时间+停机时间)。文章列举了不同级别的可用性对应的停机时间,并介绍了提升系统可用性的多种策略,包括冗余设计、故障检测与自动恢复、数据备份与恢复、负载均衡、容错设计、定期维护与更新及使用高可用性云服务和网络优化。这些措施有助于构建更加稳定可靠的系统。
542 0
|
网络架构
系统可用性理解
开发一个软件系统,对其要求越来越高,如果你了解一些「架构设计」的要求,就知道一个好的软件架构应该遵循以下 3 个原则: 1. 高性能 2. 高可用 3. 易扩展
338 1
|
运维 监控 测试技术
故障治理:如何进行故障复盘
故障复盘的重要性无需多说,每一次故障都是宝贵的学习机会,本人接手故障复盘工作已经半年有余,从一开始的手足无措,慢慢变得游刃有余。以下内容为本人从网上查阅学习多个专家经验,并结合工作经历总结而来,仅供参考。
|
算法 BI
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.2故障分体系
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.2故障分体系
398 0
|
运维 NoSQL 容器
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.3 故障快恢
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.3 故障快恢
252 0
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.4 故障复盘
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.4 故障复盘
317 0
|
运维 监控
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.2故障应急
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.2故障应急
369 0
|
运维 监控 中间件
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.1故障发现
《云上业务稳定性保障实践白皮书》——三.故障管理体系——3.故障管理全流程——3.3.1故障发现
210 0

热门文章

最新文章

相关实验场景

更多