上文说了怎么用LTO磁带归档,还说了我要自己写一个归档管理软件。这个软件写的差不多了,还在测试。趁着这个机会说说怎么用LTO磁带备份电脑上的数据。
环境
关于备份,我看了好多备份软件。他们主要分为两种:一种是单机的,在电脑上安装一个程序,插上磁带机就可以使用;另一种则是CS结构,需要把磁带机插在一台服务器上,然后需要备份的客户端向服务器提交数据。从我的需求来看,我自然是不需要一台专用的备份服务器。但单机软件普遍卖的很贵,最便宜的也要一百多欧元。因为沾上了磁带,自然免不了“企业级”。只要沾上了这三个字,那溢价可是大了去了。
本着不买立省100%的原则,我将目光转向了Linux。因为在上一篇谈归档的时候,我就注意到网上很多人在狗叫什么“tar就够了哪用得着什么花里胡哨的归档软件”。对于归档来说,tar肯定是不够用,但是作为备份,也许tar是够用的。但是很明显,tar是一个Linux程序,而我使用的是Windows,脑瘫Razer在Bios里偷工减料,使得我必须关闭安全启动才能启动Linux,但我又不想牺牲安全性,可是磁带机使用的是雷电接口,本质上是个PCIe设备,没办法像USB那样直接透传给虚拟机。面对这样一种天不时地不利人不和的状况,我发现了Cygwin。
这个东西我不是第一次用了。早在我不想让微软在我的硬盘上满地拉屎时,我就用这个组件来代替微软的编译器在Windows上编译代码。我在谷歌的海洋中先帆船后潜水时注意到,这个东西竟然能够支持磁带设备!它能够将NT内核的\\,\TAPE0
映射到Linux上的/dev/st0
和/dev/nst0
。那话不多说,就是它了。
要在Cygwin上使用磁带机,你可能需要额外安装一个mt
包,用于管理磁带机的倒带。此外,使用mt命令让磁带机倒带则需要管理员权限。
一切皆文件
在最开始拿到磁带机的时候,我注意到磁带机是一个SCSI设备,本想着写点代码用Java调用,但却发现好像只有C的兼容性最好,因为SCSI相当于是直接和设备交流。IBM给了一个很长的SCSI编程手册,我想想看,还是不要折磨自己的好。
幸运的是,Linux的设计哲学就是“一切皆文件”。得益于驱动程序的帮助,磁带机在Linux下被抽象为一个字符设备文件。也就是说,我们可以通过类似Java中的输入输出流那样和磁带上的数据交互。如果跳出编程的思维,从使用的角度来看,就是说我们可以将其视作一个普通文件进行操作。
例如我们要将一段文字写到磁带上:
mt rewind
echo "Hello world!" > /dev/st0
cat /dev/st0
最后的cat命令将会打印出我们写到磁带上的Hello world
。注意到这里我使用的是/dev/st0
,其中st
的含义是scsi tape,这个设备在每次操作完磁带后都会进行一个rewind操作,将磁带倒回最开始的地方。而/dev/nst0
多了一个n,表示操作完成后不要回转磁带。
因为/dev/st0
是一个字符设备文件,它不关心磁带的位置,如果你请求一个,他就开始读。所以在操作磁带前务必使用mt tell
和mt rewind
确认磁带位置。为什么要特意强调这一点呢?我们做个append操作试试看:
mt rewind
echo "Appending string" >> /dev/st0
cat /dev/st0
这个时候cat打印出来的却只有我们的Appending string
,前面的部分消失了。按照道理说,如果/dev/st0是一个普通文件的话,>>
操作符应该会将后来的字符串续写到原有数据之后才对。但问题在于,/dev/st0并不是一个普通文件,因为读写时磁带都是从前往后走,所以自然而然也就没有续写一说。可是怎么才能续写呢?我们用不会回转的nst0
试试看:
mt rewind
echo "Hello world!" > /dev/nst0
echo "Appending string" > /dev/nst0
mt rewind
cat /dev/st0
这个时候cat打印出来的却只有Hello world了,我们后加的数据反而不见了。如果这时候我们执行两次cat /dev/nst0
,就会产生这样的效果:
$ cat /dev/nst0
Hello world!
$ cat /dev/nst0
Appending string
看起来我们追加的部分好像并不和第一个字符串在一起。为什么cat在读出Hello world之后就结束了呢?为什么第二个cat就只打印出后来追加的字符串呢?
一开始我也很疑惑这个问题,后来搜索未果,跑去问了ChatGPT。简单总结一下就是,磁带机对应的设备文件并不只是单纯的设备文件。我们使用重定向、dd、tar之类的命令读写它时,实际上对应的系统调用是read和write,当然,使用前后还要open和close。但这种设备文件和普通文件不同,设备文件的背后是驱动程序。驱动程序在访问过程中充当一个重要的翻译,他将你的读写调用翻译成磁带机能够理解的scsi命令,并将scsi命令的响应翻译成读写调用的结果。至于mt
命令也通过这个设备文件管理倒带,这个是使用了ioctl这个特殊的调用。而作为设备文件读写时,驱动程序承担起了额外的作用,例如在写入数据前插入文件标记、在写入后插入EOF标记,这些标记对于应用程序和用户来说是透明的,但对于驱动程序和磁带机来说却是辅助其运行的重要部分。例如通过mt fsf
命令就可以在不同的数据块之前切换:
mt rewind
mt fsf 1
cat /dev/nst0
通过mt fsf 1
,我们从磁带开头向后跳跃一个文件,刚好来到我们后来插入的Appending String这里。果不其然,cat命令打印出来的也是我们后来追加的字符串。
可是问题来了,如果我确实要追加字符串,应该怎么办呢?我们看看mt命令的文档:
This manual page documents the tape control program mt. mt performs the given operation, which must be one of the tape operations listed below, on a tape drive. The commands can also be listed by running the program with the -h option. The version of mt is printed with the -v or --version option. The path of the tape device on which to operate can be given with the -f or -t option. If neither of those options is given, and the environment variable TAPE is set, it is used. Otherwise, a default device defined in the file /usr/include/sys/mtio.h is used.
Some operations optionally take an argument or repeat count, which can be given after the operation name and defaults to 1. The postfix k , M , or G can be used to give counts in units of 1024, 1024 1024, or 1024 1024 * 1024, respectively.
The available operations are listed below. Unique abbreviations are accepted. Not all operations are available on all systems, or work on all types of tape drives.
fsf
Forward space count files. The tape is positioned on the first block of the next file.
- ...
bsf
Backward space count files. The tape is positioned on the last block of the previous file.
- ...
使用bsf操作,刚好可以回到前一个文件的结尾:
mt rewind
mt fsf 1
mt bsf 1
echo "Now we're talking" > /dev/nst0
这时我们直接在上一段数据的结尾写入新的字符串。随后rewind之后cat:
$ cat /dev/st0
Hello world!
Now we're talking
这里可以看到,我们的数据成功地追加到了上一段数据的后面,即便我们用的是截断的>
操作符。如果这个时候我们再mt fsf 1
试试看呢?
$ mt fsf 1
mt: /dev/tape: Input/output error
由于磁带本身的性质,当我们在第一个数据块后面写入“Now we're talking”的时候,原本的Appending String就被覆盖掉了。没有了正确的文件标记,磁带机认为后面没有数据,于是当我们想往后找下一块数据的时候,驱动程序就告诉我们发生了IO错误。
当然了,除了文本数据,我们还可以使用其他数据:
$ mt rewind
$ dd if=【完整熟肉】舞台剧「孤独摇滚!」【8_20千秋楽夜公演】\[中字_汉化\].mp4 of=/dev/st0 bs=4M
记录了403+1 的读入
记录了403+1 的写出
1690491848字节(1.7 GB,1.6 GiB)已复制,31.8105 s,53.1 MB/s
$ dd if=/dev/st0 of=test.mp4 bs=4M
记录了403+1 的读入
记录了403+1 的写出
1690491848字节(1.7 GB,1.6 GiB)已复制,37.4315 s,45.2 MB/s
$ sha256sum *.mp4
ff461ca52619b076243e8bd5bb3da7d075d053e4687c12e85d51d871237b636a *【完整熟肉】舞台剧「孤独摇滚!」【8_20千秋楽夜公演】[ 中字_汉化].mp4
ff461ca52619b076243e8bd5bb3da7d075d053e4687c12e85d51d871237b636a *test.mp4
这里我用了一个MP4文件来做测试,和刚才的字符串类似,可以对/dev/nst0
连续使用多个dd来写入不同的文件。不过这有一个问题:写入的只是数据,文件名却没有了。这对于备份来说可是不太好——数据都在,你看着猜吧。
tarball
好在我们不需要重复造轮子:tar就能完美解决这个问题。或者说,几乎任何一种压缩包都可以满足我们这个需求,只不过tar是最简单的一个。
在挑选压缩包格式的时候,第一个问题就是速度。磁带机写入时磁带是以某一个速度旋转的,如果写入的速度不够快,就会导致磁带机走走停停,甚至要倒回来一点再开始写。这种行为对于磁带机的磨损非常大,甚至还有一个专有名词,叫shoe-shining,翻译过来就是擦皮鞋。磁带会像那皮鞋那样反复转来转去,只因为设备提供数据的速度跟不上写入速度,磁带转过去了,可是待写入的数据还没产生。而数据来了,磁带已经转过去了,这个时候磁带机就得先刹车、再倒车,倒回去之后再开始转。如此反复,磁带机可就命不久矣了。虽然现代LTO磁带机能够根据数据流的速度动态调整磁带的转速,但不稳定的速度也会带来磨损。
为了缓解这个问题,首先要从数据源入手。这里的数据源是我们的tar命令,它将文件和对应的信息打包输出成为二进制流,我们可以让tar直接写到/dev/nst0
上,也可以通过管道交给dd
来写。无论如何,如果我们的tar命令太慢,那磁带机也就只能干等。怎么才能让tar变快呢?最简单的办法就是不用压缩,然后是加大blocking factor。
这里简单说说tar是怎么把文件打包的。Tar格式非常简单,基本上就是一个元数据块(作为头),跟着一个数据块。而Tar的基本单位是一个Record,默认情况下是10240字节一个Record,但为了提高效率,例如我要备份的文件都是几百个GB的大文件,那我们可以通过增大Record来减少整体Record的数量,进而减少冗余数据,同时还能提高tar的读写速度。我们可以通过-b 2048
来将Record的大小调整为2048 * 512 = 1MB
。但Record作为固定长度的基本单位,如果一个文件的长度小于Record的大小,那么类似于磁盘的簇,这个Record就只能存储这一个文件,由此就引发了空间浪费。
为了消除这种空间浪费,我们可以使用压缩。Tar本身支持各种压缩,但测试发现这些压缩算法普遍比较慢,唯一快的要算是zstd了。但比起软件压缩,我们还有更好的选择:LTO磁带机的硬件压缩。我的LTO 6磁带机自带硬件压缩,所以可以直接让tar输出原始流,然后磁带机去进行压缩。这样一来我们便不必担心CPU的压缩速度跟不上磁带机的消耗速度了。
如果最后还是慢的话,我们就要求助于mbuffer了。这个小工具能够在管道中充当一个蓄水池,我们可以让它在缓存写到一定程度之后再开始向磁带机发送数据:
tar -b 2048 -cvf - path | mbuffer -m 8G -P 95 | dd of=/dev/nst0 bs=4M
这个命令就利用mbuffer在管道中提供了一个8GB的缓存,来自tar的数据会先暂存在缓存中,等到数据量达到缓存容量的95%,才开始向dd命令发送数据。
硬件加密
我之前在归档时说过,如果30年后忘记了磁带的密码,那真是莫大的悲哀。但是备份和归档不一样,备份是一种比较近的数据。按照我的计划,每一个备份做多存续3个月,而且它们原封不动地复制了我电脑上的数据,万一失窃泄露的还是挺多的,所以加密是很有必要的。可是和压缩一样,软件加密是下下策,我们有磁带机的硬件加密。
但问题来了,压缩我们可以用mt compression
命令来管理,可是加密好像就不行了。在Linux上有stenc,但cygwin上无法编译。好在有人将其移植到了Windows上,叫做LTOEnc,我们启动一个管理员权限的终端就可以运行它了。
至于密码,将密码存储在磁盘上是一种很不明智的选择,因此我选择在运行时生成。将文本密码过一遍sha256,用对应的32字节哈希值来充当磁带机的密码。
Bash脚本
为了便于操作,我将上述操作编写成了脚本,放在了GitHub上。同时我将管理加密的脚本和负责备份的脚本分开了。在备份之前,先使用apply-lto-key.sh
设置好磁带机的加密,然后使用backup-c-and-c.sh
备份。
备份的流程是利用tar通过管道输出到dd,因为数据量比较大,所以能看到一个进度条或者统计信息会比较安心。通过使用选项可以让dd命令打印出当前的进度,例如已写入的数据量、写入的速度等:
dd of=/dev/nst0 status=progress
写入完成后再运行clear-lto-key.sh
清空磁带机的加密密钥。
之后如果要恢复数据的话,按照同样的流程先设置磁带机的密码,然后使用tar -b 2048 -tvf /dev/nst0
即可。关于文件的顺序,可以参考前文。在磁带开头是C盘的备份,如果从磁带开头执行mt fsf 1
就能找到D盘的备份。
Cygwin的背刺
本来事情到这里应该就已经结束了。我也觉得我这写了一天bash脚本,也该结束了。没想到,这个时候没有意外的话就该出现意外了。
我注意到我的脚本备份速度总是在60MB/s上下波动。我以为是C盘小文件太多,速度起不来。我为了验证这个想法,在D盘上跑了一下,发现即便是几十GB的文件也是这个速度,遂开始排查。我分别尝试了如下组合:
- 使用dd命令,if从文件读,of直接写到磁带。能跑满160MB/s
- 使用dd命令,if从文件都,输出到管道;使用dd命令,从管道读,写到磁带机。只能跑到60MB/s
这样一来就一目了然了:竟然是管道的效率太低。至于tar,在处理单个文件的时候问题不大,可是处理多个文件的时候也会在Cygwin上出现性能问题。
经过查证,我发现最根本的问题在于Windows没法实现Linux的fork调用。这个调用在Linux下是吧一个进程一分为二,一个进程可以继续执行,而另一个进程可以获知自己是分出来的进程,进而可以执行一些别的任务。但是在Windows中,进程就是进程,如果想要一分为二就只能再从头创建一个。这就导致Cygwin在模拟POSIX的fork调用时进行了许多麻烦的模拟,导致所有使用fork的调用都会被拖慢。
但是磁带机是SCSI设备,它不能像USB那样透传到虚拟机里。我的雷蛇笔记本在bios上偷工减料,我5年前的HP都能无痛安全启动openSUSE,到了雷蛇这里却只认微软的证书。用不了Linux,现在Windows上的兼容层性能又不行,那能怎么办呢?
Powershell,启动!
那当然是转会纯Windows解决方案啦!不过这个方案只能算是下下策吧。原本我是想玩一下Linux下的磁带的,但是鉴于目前形势所迫,纯Windows的解决方案由于没有tar、dd、mt、字符设备文件这些东西,直接往上写数据肯定是不可能了。那就只能换一种思路。
LTFS一直是个好东西,既然直接写数据不可能了,那我就用LTFS好了。但是LTFS有个问题:它只增不删。即便你删掉了上面的文件,实际的数据也还是留在磁带上的。当你继续写入文件时,并非从开头开始写,而是接着旧数据往后写。所以用不了几次,一盘LTFS磁带就写满了,即便逻辑上什么也没有。
不过办法还是有的。HPE的StoreOpen里面自带了一个mkltfs的工具,我可以使用命令行格式化一盘磁带。并且这个操作是幂等的,也就是说我可以用它格式化一盘普通磁带,也可以直接再次格式化LTFS磁带,从而起到删掉数据的作用。不过挂载就没有那么幸运了:纯命令行的挂载是做不到的,需要使用GUI挂载。不过这倒也问题不大,因为GUI允许你单独挂载一个磁带机。类似于没有光碟的光驱那样,你可以现挂载驱动器,再用命令行处理磁带。
至于加密,经过测试,我可以通过LTOEnc启用加密(mixed模式),然后再挂载LTFS驱动器,这样之后无论是格式化还是读写就全都是加密数据了。至于压缩文件的格式,我是用命令行版的7z,也就是7za,通过-ttar
选项来创建tar归档。整体和Linux差不多,只是从EOF变成了更加复杂的LTFS。不过不一样的部分则是这次我们不会再遇到性能瓶颈了。
我把这部分脚本也放到了GitHub上,欢迎参考。
后记
这次是真的结束了。顺便说一个小插曲,最后在收尾的时候,我明明记得7za是有一个进度条的,怎么到了我的脚本里突然就没了?我把脚本里面的变量复制到控制台运行,有输出;进到脚本运行就没输出。百思不得其解。最后无意中发现,我用来计时的Measure-Command
竟然默认会吞掉stdout输出,开发人员认为这会影响被计时命令的性能。彳亍口巴。
总之这次是真的好了,在尽最大努力不自己写软件的情况下。
-全文完-
【代码札记】LTO磁带备份指北 由 天空 Blond 采用 知识共享 署名 - 非商业性使用 - 相同方式共享 4.0 国际 许可协议进行许可。
本许可协议授权之外的使用权限可以从 https://skyblond.info/about.html 处获得。
哈哈,你们猜怎么着?我又换回cygwin辣!因为Windows上的7zip用7z格式打包时速度很快,还带有crc,但是因为最后要更新header,就导致ltfs要把整个文件重新复制一遍再做修改。而使用7z打包tar的时候,因为改变不了record大小,所以只能跑到20MB/s。最后我左思右想,Cygwin上使用管道传给dd主要是想看一个速度的统计数据,如果我直接让tar写到磁带机,是没有任何性能问题的(除了小文件)。但是作为牺牲,我只能给tar加上`-v`选项让它打印出正在处理的文件名。这对于大文件来说问题不大,可是在处理众多小文件时,控制台的打印速度会拖慢tar的处理速度,但就这也比Windows上的7z打包tar要快!
垃圾Windows,呸!!!