【冬瓜哥实操】字节级CDP,什么鬼?!

我剑何去何从,爱与恨情难独钟。我刀割破长空,是与非懂也不懂。我醉一片朦胧,恩和怨是幻是空,我醒一场春梦,生与死一切成空!来也匆匆去也匆匆恨不能相逢,爱也匆匆恨也匆匆,一切都隋风。狂笑一声长叹一声,快活一生悲哀一生,谁与我生死与共,谁与我生死与共!


最近,有个名为扫地僧的枪手写了个文章,狠喷了一把一个叫做英方的容灾方案提供商。起喷点就是英方宣称的“字节级”CDP,喷技术也就喷吧,顺带连英方CEO和整个产品也一起喷了,够狠。看来扫地僧扫了太长时间的地,一爆发就是个大招啊。第二天,又出来一个叫做“小僧”的枪手则利用在某个微信群里的聊天记录把大家的评论攒了一下,调侃了一下扫地僧。

在此,冬瓜哥不想掺和喷人和喷公司的事情,因为有些背景冬瓜哥也搞不清楚,孰是孰非难辨。但是冬瓜哥的确想就这次事件,分析一下CDP的技术层面的东西,让大家清晰的了解一下CDP,以及英方这个所谓“字节级”的CDP到底是不是被喷的有理有据。

【主线1】什么叫CDP

如果说快照是数据照相机,那么CDP(Continuous Data Protection)就是数据录像机。录像当然要为了回放,要回放,就当然可以任意快进快退,看到并提取每一个“帧”,甚至直接把当前系统数据回滚到之前的某个历史时间点。大家接触最多的一个实例就是数据库的redo和undo日志了。

【支线】数据库系统的CDP方式

细粒度数据回滚和前滚这个思想到底是谁发明或者提出来的我们已经无从考证了,但是最成熟的高帧率数据录像机,还得是数据库系统莫属。数据库系统对每一笔操作都记录的非常详细,包括这笔操作所做的更改类型(插入、删除、更新等),以及该笔操作所对应的SCN序号、时间戳等。记录了所有变更操作和被覆盖的数据块,就可以任意回滚和前滚,当然,需要将一段时间内所有发生的变化都记录下来,才可以在这段时间内的历史数据中任意回滚/前滚。

数据库虽然可以任意回滚,但是它不能并行生成多个历史时刻的可访问的数据库影像。比如,一份十分钟之前的影像,一份一小时之前的影像,如果用户想同时访问这两个历史时刻的影像,此时数据库系统是无能为力的。

【支线】存储系统对CDP的特殊要求

作为一个针对存储系统的CDP系统来讲,则必须实现上述功能,才算专业。但是如何在保证细粒度回溯点的情况下,依然还能提供上述功能,那就是个比较大的挑战了。低帧率模式下,每个采样点会用一份表来记录映射关系,当挂起这个采样点成一个虚拟的卷影像之后,针对该影像的IO访问,通过查询该表便可以迅速得到执行。

但是高帧率模式下,采样点模型已经不适用了,由于回溯粒度非常高,甚至达到了每个IO级别,那么此时不可能为每个IO点都记录一张表。所以此时必须使用数据库日志形式的回滚方式,但又要同时实现并行影像访问,这似乎不可能。

【主线2】块级CDP

试想一下,在这种IO级别细粒度回溯的场景下,就完全没有必要为每个IO记录一个单独的表了,也就是说,日志本身,已经是个内嵌的表了。我们先来做个简易模型,有100个LBA组成一个卷,然后针对这个卷的10、20….100这10个LB做了多次乱序、重叠的写IO操作,现在要求生成第二个IO(LBA70)也就是T1时刻和第9个IO(LBA10)也就是T2时刻历史时刻的两份影像,而且要求可并行访问。如图所示。

【冬瓜哥实操】字节级CDP,什么鬼?!

假设这两个历史时刻虚拟卷已经生成,主机端已经挂载这两个卷,然后主机针对T1时刻的虚拟卷的LBA50进行了读操作,此时CDP系统应该去哪里找LBA50?读者应该很清楚了,那就是要从源卷的基准镜像中读出LBA50然后发送给主机。为什么?因为T1这个历史时刻点之前,对LBA50的更新还没有发生(虽然在当前日志中T1时刻后面确实有一笔LBA50的写IO被记录),也就是在日志中T1时刻之前未出现过LBA50。同理,主机针对比如LBA1024(不在日志中)的访问,同样也要去源卷基准镜像中寻找。但是针对LBA10的访问,CDP引擎就必须从日志中读出LBA10返回给主机,因为在T1时刻之前,日志中恰好有一笔针对LBA10的写IO被记录。再来看针对T2时刻的虚拟卷的访问,其道理是一样的,同样是访问LBA50,但是此时CDP引擎就必须明确的知道,LBA50是存储在日志中的并且是在T2时刻之前发生,所以必须从日志中来找到T2时刻之前最近的一笔(可能会有多笔)LBA50的记录从而返回给主机。

这个模型非常简单,任何一个人通过看图都能很快判断出任何一个IO访问,到底应该从哪里找数据返回给主机。仔细体会一下,寻找其中规律,这个算法模型其实说白了就是:针对任何历史时刻点的虚拟卷影像的任何LBA的访问,CDP引擎首先要判断出该IO地址对应的数据是否存在于日志中,如果有,再去日志中该历史时刻点之前的记录中去搜索,如果搜不到,就从基准镜像中读出并返回;如果初始判断该访问地址根本就不在日志中,则直接从源卷基准镜像中对应的LBA中读出该数据即可,不需要搜索,因为该LBA与访问的LBA是一一对应的。

我们看到,上述算法模型,对针对某地址的访问,做了两级搜索,为何不用一级搜索呢,比如任何一个访问,先整体搜一遍日志,搜不到再去基准镜像中读取?如果这样的话,性能会非常差。上图中的日志只记录了十几笔操作,但是现实中,会有几十万上百万甚至千万比操作,搜一遍谈何容易。那么如何快速的判断某地址是否在日志中或者不在日志中?这个就很简单了,利用一个Bitmap,源卷每个块或者LBA,对应Bitmap中的一个bit,如果该bit被置1,证明该块/LBA目前在日志中存在,但是并不能判断存在于该历史时刻点之前还是之后;如果该bit被置0,则证明该块/LBA在日志中不存在,也就是说从录像开始之后,这个块/LBA根本没有被更新过,此时皆大欢喜,直接去基准镜像中访问该块,性能最高。

第一级搜索可以利用位图来缓解性能问题。但是如果必须去日志中查找,此时性能一定会有损失的。但是我们还是可以进行优化。首先,为了加速查找,必须为整个日志生成一份元数据表,记录每个IO的序号和这个IO的起始目标地址和长度 ,以LBA扇区为粒度的话,使用32bit地址的话可以描述2TB的源卷容量,所以,实际产品可以选择一个支持原卷容量的上限,比如2TB。然后再使用32bit来表示IO的序号,也就是整个日志空间可保存约42亿次IO,每秒5K IOPS的话可以连续跑10天的,一般保存10天的细粒度回放日志基本够用了。IO的长度使用SCSI协议中的上限也就16bit足够。

这样的话一共需要80bit来描述日志空间中的一笔IO,加上其他考虑比如保留一些将来用的字段等,按照96bit也就是12字节来算。每笔IO记录将耗费12字节描述,每笔IO平均大小按照4KB来计算的话,元数据比例大概在,3‰左右,完全在可接受的范围内,如果日志空间大小为1TB,那么会耗费3GB内存空间来完全缓存元数据,这也是可行和可接受的。另外,这些元数据在没有生成虚拟影像的时候,是不需要常驻内存的,仅当在生成了虚拟影像之后,为了加速主机IO访问所以才有必要常驻内存。

元数据表只按照IO序号排序是不够的,因为IO序号不能被用作用户回滚时候的依据,因为没有任何人会对IO序号有概念,所以还必须在日志元数据表中加入时间戳,这里可以变通一下,可以对每笔IO都加上一个时间戳,也就是用时间戳来取代IO序号,如果不需要IO级别细粒度恢复的话,时间戳粒度可以粗一些,比如日志中每10个IO就记录一个时间戳,这些都是可以灵活设计的。

元数据表是按照时间戳/序号来排序的,当用户生成了某个时间戳的虚拟卷影像之后,根据前文分析,每一笔针对虚拟影像的IO会经历两级搜索,如果落入了日志空间中,那么CDP引擎就要将该时间戳之前的所有日志搜索一遍以查找日志中所有与该IO请求的地址范围有交集的那些IO记录,然后将数据拼接起来,返回给主机。由于日志中的IO记录是按照时间/序号排序的,所以为了提升搜索速度,必须另生成一份按照所记录IO的LBA起始地址排序的索引,也就是说,用户每生成一个某时间戳处的虚拟卷影像,CDP引擎就需要将该时间戳之前的日志部分的元数据做一个索引,这样就可以加速查找。

有了上述的模型和优化算法,基于IO粒度回滚的高帧率数据回放,就变得可行了。如图所示,还可以继续优化设计,比如将元数据空间单独拿出来,放到SSD中去加速访问。用户可以任意生成多分多个历史时刻的虚拟卷影像,每个影像所占用的开销只是速查索引而已,上层用户体验就会非常给力。还可以将CDP日志先缓存在RAM中,然后定期Flush到磁盘上,相当于分成在线日志和归档日志两大部分,另外,归档日志中较早的数据如果不再需要回放则可以被Merge到源卷基准镜像中去,相当于一个合成备份,这样就不必维护越来越多的日志空间和元数据了。

【冬瓜哥实操】字节级CDP,什么鬼?!
另外,在设计上还可以做更深层次的优化。长时间录像不是做不到,而是代价太高,比如需要大量的元数据被记录,这样重放时候的性能会较差。有没有办法来降低元数据记录量从而进一步提升重放性能呢?业界普遍做法,是定期生成一份基准数据,或者叫做baseline,然后在这份基准的基础上,记录增量数据。有技术感觉的读者一定会猜测到,其实就是以照片为基础,然后记录在每份照片基准之上的数据变化,这样所保存的元数据就非常小。这个原理,与实际的视频压缩编码非常类似。实际视频压缩编码技术中也是将视频分成P帧和I帧,P帧是基准,第一个I帧则只记录相对于P帧的增量,第二个I帧则记录相对于前一个I帧的增量,利用P+I1+I2+I3+……In的方式,对整个视频进行微分,那些变化量不大的场景,比如某个镜头,几秒之内视角和内容基本相同,这样,这个镜头就可以被编码成P+nI的形式,如果镜头切换,内容几乎全变,那么就在使用新的P+nI来记录,多个P+nI积分就可以还原出整个视频。

所以,可以使用快照+增量的方式,来降低每次搜索的数据量,每个快照其实就相当于一个P基准帧,也就是一份快照,一份照片。系统维护快照所耗费的元数据相比于CDP那是要少得多的,因为快照不会记录IO的顺序和时间,只会记录位置映射关系,所以代价比CDP小得多。基于每个快照,系统会在快照的基准上,记录增量日志及其元数据,这样,对T1时刻的查找就是基于T1时刻之前距离T1时刻最近的Snapsho进行的,基于它,与后续记录的CDP元数据共同组合,抽取出T1时刻的卷虚拟影像给主机使用。如果T2时刻与T1时刻之间没有Snapshot触发,那么T2时刻与T1时刻的虚拟影像提取就得使用同一个Snapshot。

【主线3】字节级CDP到底是 ”什么鬼“?

套用这位“扫地僧”的说法,所谓“字节级CDP”,鬼一般的蹊跷。冬瓜哥上面烧脑的分析了块级CDP的底层机制。倒腾上面这套东西,需要在一个单独设备中,先将源卷镜像一份出来。如下图所示,

【冬瓜哥实操】字节级CDP,什么鬼?!
主机访问底层的卷,一定是以512Byte为对齐单位,也就是一个扇区,或者说一个块,对卷做CDP保护,其最小粒度也只能是一个块。而如果对文件来做CDP的话,难度着实就不小了,因为文件的最小更改粒度,是1Byte,而不是512Byte。而且针对文件的操作,远比针对块的操作要复杂,后者只有Write操作,而前者,则还有比如delete,truncate,setattr,setendoffile,write,mkdir,link,unlink等这些文件系统通用操作,这些页都是某种变更,如果要做到每一笔变更都不丢失,那么CDP系统就要将所有这些变更都记录并追踪管理下来,也像上图那样存储在日志中。

这便是“字节级”CDP,其记录的真的字节级别的更新。

【支线】英方云的字节级CDP体验

为了求证此事,冬瓜哥向英方软件申请了个试用DEMO环境,亲自操作了一把,用实际证明了字节级CDP,真的不是个鬼,而是个仙。如图所示。进入云灾备的控制页面,首先看到的是一张全局Dashboard。

【冬瓜哥实操】字节级CDP,什么鬼?!

这些不重要,重要的是,冬瓜哥在某台远端机器上做了如下操作:

【冬瓜哥实操】字节级CDP,什么鬼?!
然后到控制台中尝试回放之前的历史时刻点,点击图中的“查看相关CDP日志”按钮之后,便跳出一个列出了针对目标文件所有细粒度操作的列表。

【冬瓜哥实操】字节级CDP,什么鬼?!
如下图所示,可以发现,其没有落下任何一个对该文件的变更操作,连link/unlink都抓取了下来,并复制到远端的日志*后续回放。

【冬瓜哥实操】字节级CDP,什么鬼?!

然而,由于粒度过细,该文件并不能从日志中即刻虚拟出一个即刻可以访问的虚拟文件,而必须基于最近baseline,将增量读出,重新replay一下增量到baseline,然后将replay好的文件副本拷贝到某个地方,才能供用户访问。不能直接像块级CDP回放时那样指哪打哪,但是优点就是能够在文件粒度、字节粒度,提供更加贴近用户的细粒度恢复。而基于块的CDP,需要先将整个虚拟卷挂起,然后才能看到文件,进入对应目录做恢复。

可以从图片中看到,该CDP服务端是运行在腾讯云的某台主机上的,跨网络传输,需要精打细算,在这方面,块级CDP的确有劣势,因为上层文件更新了哪怕只有一个Byte的元数据,底层其实也要更新至少4KB(之前读上来缓存住,更改后,刷盘),因为文件系统一般格式化的时候也是最小4KB的块大小,元数据也按照块来分配。所以,传这么多数据块的话,时延会比较高。其还可以从腾讯云复制到阿里云跨云复制和CDP, 目前未在其他产品上看到支持跨云实现CDP的

【主线4】字节级CDP底层机制

对于字节级CDP,源端的IO抓取器,其实本质上是一个filter driver,其插入到系统的IO路径上,将自己的回调函数注册或者说hook挂接到系统内现存的其他驱动下方,比如挂接到VFS下方,那么它就可以抓取每一笔针对文件系统的操作请求。而块级CDP的IO抓取器实际上是挂接到了SCSI协议栈顶层的Device Driver下方,从而抓取到的是针对某个磁盘设备的IO操作。在Windows下,这些IO请求有个名称叫做“IRP”,IO Request Packet,Linux下针对块设备的IO则被称为bio,block IO。

字节级CDP的IO抓取Filter Driver嵌入到IO路径之后,抓取每一笔IO操作,判断其是否是变更操作,比如write,link,setattr等,如果是,则将这个请求下发到下游驱动的同时,复制一份,封装到网络包中,推送给CDP服务端从而保存在增量日志*后续回放。

【支线】网络协议栈中的Filter Driver

Wireshark等抓包软件,大家也都用过,其底层就是用了一个FIlter Driver,插入到了网络协议栈里网卡驱动的上方,这样才能抓取到所有网卡上报的以太网帧,从而做分析。Filter Driver越处于下层,就越能够抓取更加底层的信息,但是缺点就是粒度不够细,因为上层的调用到了底层会大变样,比如,文件系统的调用,底层都会被转为块的传输,除非使用了NFS协议,因为NFS Client程序会将上层调用几乎透传到NFS Server端,但是此时抓取的是NFS封包,多了很多冗余信息,而如果在上层抓取,则直接抓取的是原始细粒度的IRP或者Linux下的file IO描述结构体。

【支线】多路径软件本身就是个FIlter Driver

多路径软件,就是插入到IO路径中,截获上方的IO,做判断,然后从对应路径转发出去;当路径故障时,向上层屏蔽其影响,自动切换路径。

多路径软件有多种不同实现,有些直接插入到Host Driver上方,有些则插入到Device Driver上方。越往上,兼容性越强,所以内核自带的多路径软件,都位于通用块层,这样可以屏蔽底层的差异,傲视群雄。


至于网络传输开销,这个冬瓜哥感觉并不是字节级CDP本身的问题,要实现如此细粒度,必然网络开销比例会增大。但是,对于块级CDP来讲,主机端的IO抓取器其会复制不少冗余的数据到CDP服务端,因为上层发出的写操作,到了下层可能会大变样,比如,如果经过了page cache,则上层读取或者写入1Byte的数据,到了底层可能就是4K数据,如果这样算,同一个网络报文,如果承载了1Byte的payload,和承载了4K的payload,后者单看比例的确效率高,但是又有何用呢,真正变化的数据,其实还是只有1Byte,而不是4KB,后者相当于产生了读写惩罚,也就是多余的那些数据本没有必要传到对端。

扫地僧同学可以来反驳一下。小僧、扫地僧,都是和尚,那么冬瓜哥是不是该自称本道了,哈哈哈!

沧海笑,滔滔两岸潮,浮沉随浪记今朝!苍天笑,纷纷世上潮,谁负谁胜出天知晓!*笑,烟雨遥,涛浪淘尽红尘俗事知多少!清风笑,竟惹寂寥,豪情还剩了一襟晚照!苍生笑,不再寂寥,豪情仍在痴痴笑笑!

【冬瓜哥实操】字节级CDP,什么鬼?! 【冬瓜哥实操】字节级CDP,什么鬼?!

【冬瓜哥实操】字节级CDP,什么鬼?!

本文转载须全文转载,包括二维码和所有图片文字,并注明出自 大话存储 公众号。长按识别二维码关注 大话存储 获取业界最高逼格的存储知识。 看了好的请点赞/转发/红包,平时群里发红包装逼,不如把红包猛烈的砸向冬瓜哥吧!冬瓜哥后续会有更多高逼格的东西出炉。大话存储,只出精品。

长按图片发红包:

【冬瓜哥实操】字节级CDP,什么鬼?!

支付宝扫码发红包:

【冬瓜哥实操】字节级CDP,什么鬼?!

长按扫码关注“大话存储”

【冬瓜哥实操】字节级CDP,什么鬼?!

强赠冬瓜哥真容:

【冬瓜哥实操】字节级CDP,什么鬼?!

(请注意,冬瓜哥不是西瓜哥,这是两个人,很多人给混淆了,冬瓜哥很早就叫冬瓜了,某菊花司的某人2年前参考鄙人昵称在业界混淆视听,现在以至于很多人认为大话存储为那人所著,后来有内部人士透露给冬瓜哥才知道,其目的一开始就是在冬瓜哥离开某菊之后消除冬瓜哥在某菊和业界的影响力,菊花黑寡妇作风典型代表。)