跳到正文

目录

restic 拆解:35.9K Stars 的 Go 备份工具,如何把加密、内容定义去重、增量快照、多后端压成一条命令行

判断:restic 真正解决的问题不是「把文件复制一份」这件事,而是「当备份存储不被信任时,怎么让增量、去重、加密、多后端四件事能压成一条对运维足够简单的命令行」。仓库的五大设计原则——Easy / Fast / Verifiable / Secure / Efficient——是同一条判断在五个不同维度的展开:Easy 给运维、Fast 给数据量、Verifiable 给恢复链路、Secure 给不可信后端、Efficient 给存储成本。

目标读者:运维 / SRE / 平台工程师、自建备份系统的个人或小团队、对内容定义分块与可复现备份感兴趣的 Go 工程师 预计阅读时间:30 - 45 分钟 前置知识:文件系统基础、块级去重概念、AES 对称加密、HTTP/S3/SFTP 协议基本概念 数据来源restic/restic 仓库(v0.19.1,35.9K Stars,1.9K Forks,BSD-2-Clause)+ README 的五大设计原则 + 仓库的 doc/design.rstchunker/pack/backend/repository/ 包名边界

目录

读完能做什么

  1. 说出 restic 仓库在本地文件系统上的目录结构(data/ / snapshots/ / index/ / keys/ / locks/ / config),以及各目录在备份流程里承担什么角色。
  2. 解释内容定义分块(CDC, Content-Defined Chunking)如何让「同样的内容片段在两次备份中落进同一个 blob」,以及这和「按固定偏移分块」的根本区别。
  3. 区分 restic 仓库里的四类对象(data blob / tree blob / pack header / pack 文件自身)以及它们的加密 / 压缩 / 哈希边界。
  4. 描述一次 restic backup ~/work 从分块到上传后端的完整数据流,并把其中至少三个点对应到源码包名(chunker/pack/backend/)。
  5. 评估 restic 适合什么样的备份场景(个人 NAS、自建 S3、自托管 server),以及哪些场景它并不擅长(数据库热备、P2P 同步、几十 GB 级单文件流式处理)。

§1 系统地图:restic 的四层架构

restic 的整个系统可以分成四层,从下到上分别是后端层、数据层、索引层、快照层。理解这四层之间的边界,比记住某个具体命令更重要——大多数 bug、性能问题和误用都来自「把上一层的语义套到下一层」。

四层之间的依赖方向是严格自上而下的:快照层只引用「树」与「文件 blob」,不直接知道它们在哪个 pack 文件里;索引层负责把 blob-id 翻译成「在哪个 pack 文件的什么偏移」;数据层只关心「怎么把 blob 聚合、加密、落盘」;后端层只关心「怎么把字节搬到远端」。

§2 仓库结构:本地文件系统视角

对一个 restic 仓库执行 ls(无论后端是 local、SFTP 还是 S3),你会看到这套固定目录结构(README 提到「restic supports the following backends」,所有后端都把同一套结构映射到自己的存储原语上):

/tmp/backup/
├── config          # 仓库配置:id(32 字节随机数)、chunker 多项式、格式版本号
├── data/           # 加密后的 pack 文件
│   ├── 00/         #   按 SHA-256 前两位分目录
│   │   └── 00abc... # 实际的 pack 文件
│   ├── 01/
│   └── ff/
├── index/          # 仓库索引(加密,记录 blob → pack 映射)
│   └── <hash>
├── snapshots/      # 所有 snapshot 的 JSON 清单
│   └── <snapshot-id>
├── keys/           # 仓库主密钥的加密副本
│   └── <key-id>
├── locks/          # 并发锁,防止同时两个进程写仓库
│   └── <lock-id>
└── tmp/            # 临时文件,写 pack / 上传时暂存,用完即清

几个值得专门说一下的边界:

  • data/ 目录的 pack 文件按 blob 独立加密,其他所有文件也都加密。 官方设计文档(doc/design.rst)明确:除 keys/ 目录外,仓库里所有文件都加密——configindexsnapshotslocks 整体走 IV || 密文 || MACdata/ 里的 pack 文件则由各自独立加密的 blob 拼成。也就是说,快照里的文件名、目录结构、索引里的 blob 映射——这些明文都藏在加密载荷里,攻击者对着仓库本身读不出来。这一点会决定你能不能用 restic 做「端到端加密」(可以),但能不能做「内容 + 元数据都不可观测」的备份(仍有限制,见下一点)。
  • 可观测性残留是「文件层次」而非「内容层次」。 每份文件(pack、index、snapshot)的对象 id(即内容 SHA-256)和文件大小是明文可见的——后端 / 中间人能统计「有多少对象、各自多大」,从规模上推断你备份了什么、改了多少。但原始的文件名、目录结构、contents 都藏在加密里,无法直接读出。这一点直接决定了 restic 的威胁模型边界:它不信任存储本身,但也不承诺「连备份规模都藏起来」。
  • data/xx/xx... 的两级目录分片把单个目录里的文件数量摊薄,对本地文件系统的大量小文件场景尤其友好,对象存储的列举和同步工具同样受益。这不是装饰——它直接决定了仓库能不能「忘了清理也能继续写」。
  • index/ 是一个可重建的派生数据。 它由 snapshot + data 推算出来,因此丢了一个 index 文件不会丢数据,只会让下一次 backup 多花时间重建。
  • keys/ 才是真核心。 仓库主密钥(对称)只在这里以「用用户口令派生出来的密钥」再加密一次存放;丢了口令,主密钥解不开,仓库等于归零。

§3 内容定义分块与去重

restic 最容易被误读的一点是「它做不做去重」。答案是「做,但只在 pack 层面、用 SHA-256 当 blob id」。这里的去重和 Borg 的去重机制在精神上接近,但在分块策略上是两套思路。

先看固定偏移分块的问题:假设你有一个 100 MiB 的虚拟机磁盘镜像,里面只是开头 4 KiB 的引导扇区变了。如果按「每 1 MiB 切一块」的固定偏移分块,后面的 99 块在两次备份中也会被算成不同的内容——因为下游 blob 整体没变,但每个 blob 的内部字节变化会让它们的 SHA-256 哈希全变。结果就是「只改了 4 KiB 也要传 100 MiB」。

restic 的解法是 CDC(Content-Defined Chunking)。仓库里有一个 chunker/ 包,用 64 字节的滑动窗口在数据流上扫描「切点」:当窗口内容的 Rabin 指纹(滚动哈希)命中预设条件时,就切一刀。doc/design.rst 给了明确参数:小于 512 KiB 的文件不切分,blob 大小在 512 KiB 到 8 MiB 之间、平均目标 1 MiB;切分用的不可约多项式在 restic init 时随机生成、写进仓库的 config 文件,这让基于分块尺寸的水印攻击难以实施。

CDC 的好处是「改 4 KiB 引导扇区只会让平均 1 MiB 的那一块发生变化,其余 99 块在数据流上仍然落在同样的偏移附近、生成同样的 SHA-256」。这正是 incremental backup 想要的语义。

两个常被忽略的副作用

  1. CDC 让 blob 数量不可预测。在固定偏移模型里,N 字节的数据会产生 ⌈N / block_size⌉ 个块;在 CDC 模型里,块数取决于内容分布。一个仓库里 blob 数量级是「数据量 / 平均 chunk size」的近似,但实际块数可能在 ±30% 区间内浮动。
  2. CDC 让「按块单独寻址」变得困难。因为下游 pack 文件中的 blob 偏移是确定的,restic 用「blob id = SHA-256(plaintext) + 长度 + 类型」做内容寻址,绕过「字节级寻址」的需要。这套设计直接决定了它能选择 AES-256 这种对称加密(不需要保留原始偏移),而牺牲的是「不能像 ZFS 那样在任意字节范围做 sub-block dedup」。

§4 Pack 文件:聚合 + 加密 + 认证

单个 blob 太小(平均 1 MiB 的 chunk),如果直接对 blob 做一次 S3 PUT,几 GB 的备份会产生几十万个对象——每个 PUT 都是一条计费请求,海量小对象还会拖慢列举和生命周期管理,这正是对象存储的反模式。restic 的解法是把一批 blob 聚合到一个 pack 文件,整体上传。

一个 pack 文件的内部结构(按 doc/design.rst 的 Pack Format 一节)是:

+------------+------------+------------+
| blob 1     | blob 2     | blob 3     |  ...   (各 blob 顺序排列)
+------------+------------+------------+
|        加密后的 header(blob 元信息)    |   (在文件末尾)
|        4 字节小端 Header_Length 写在最尾 |
+----------------------------------------+

每个 blob 独立加密 + 认证:  IV(16B) || 密文 || MAC(16B)
header 解密后的每条记录:    类型(1B) || 密文长度(4B) || [明文长度(4B)] || 明文 SHA-256

一个 pack 文件由一串各自独立加密的 blob 拼接而成,file 末尾附一份 header:解密后它记录当前 pack 里每个 blob 的类型、密文长度、明文长度(v2 压缩 blob 才有)和明文 SHA-256,最后 4 字节(小端)写加密 header 自身的长度。header 跟 payload 一样加密加认证,有两个工程上的好处:

  • 寻址元信息(哪个 blob 在哪、多大)不可被中间人篡改或读取;而且只读 header 就能索引整个 pack,不用把全部 blob 解一遍。
  • pack 文件名就是整个 pack 文件内容的 SHA-256repository/packer_manager.go 在上传前对临时文件做二次哈希),与 blob、snapshot 的命名共用同一套「文件名 = 内容哈希」的内容寻址规则。

加密默认是 AES-256-CTR 流式加密 + Poly1305-AES 认证(每份 blob / 文件独立一个随机 IV 和 MAC,总开销 32 字节);自 0.14 起又引入可选算法(AES-256-GCM、ChaCha20-Poly1305),可在创建仓库时指定,但从未改变「普通新建仓库默认 AES-256-CTR」这一事实。之所以用流式加密而不是 CBC 这类分组模式:CDC 切出来的 blob 长度任意、不对齐块边界,流式模式(CTR、chacha)+ MAC 正好能一次处理「任意字节长度的 blob 序列」。

§5 快照模型与 tree 树引用

snapshot 是 restic 对外暴露的「一份备份的最小单位」。它本质上是一份 JSON 清单,里头记录了「哪个时间、哪台机器、哪些路径、对应的根 tree 是什么」。

snapshot 里的内容不是「文件列表」而是「一棵树」。restic 的 tree 概念几乎就是 Unix 文件系统的镜像:

tree (一个目录)
├── entry: file, name="README.md", blob=abc123..., size=1024
├── entry: file, name="main.go", blob=def456..., size=2048
└── entry: dir,  name="docs/", subtree=789xyz...

每条 entry 记录「名字 + 类型 + 对应 blob / subtree + 元数据(mode、mtime、uid、gid)」。当一个文件被分块成 N 个 blob 时,tree 里那个文件 entry 会指向 N 个 blob 的 id 列表。这种「文件 → blob 列表」「目录 → 子 tree 列表」的递归结构就是 snapshot 的全部内容。

这个模型带来几个对运维非常重要的语义:

  • 增量备份 = 共享 blob 集合。在两次 backup 之间没有变化的文件,对应的 blob 列表、tree 都不变;只有发生变化的文件才被重新切块、生成新 blob。在大多数真实场景下,「文件级 100% 没变」的比例远高于「chunk 级 100% 没变」的比例,所以 snapshot 间的去重率通常远高于单文件内部去重率。
  • 删除文件是 0 成本。restic 不修改历史 snapshot,所以删除一个文件只影响新 snapshot 对应的 tree 链;老 snapshot 完全保留所有引用,可以随时回看。
  • 恢复任一历史时刻。tree 是不可变的,老 snapshot 永远指向它创建那一刻的完整树形。

§6 后端抽象:backend interface 与多实现

README 列出 restic 支持的后端:local 目录、SFTP(via SSH)、HTTP REST server、Amazon S3(含 Minio)、OpenStack Swift、Backblaze B2、Microsoft Azure Blob Storage、Google Cloud Storage,再加上「通过 rclone 接入的 30+ 服务」。这一长串列表对应的是仓库里同一个 backend.Backend interface 下的多份实现。

Backend interface 的能力面非常小,核心就是「把字节流送到远端」和「从远端拉字节流」两个动作。internal/backend/backend.go 里的主要方法:

  • Save(ctx, h, rd) / Load(ctx, h, length, offset, fn) —— 写 / 读对象
  • Stat(ctx, h) / List(ctx, t, fn) —— 查元数据 / 列对象
  • Remove(ctx, h) —— 删单个对象;Delete(ctx) 则清空整个后端
  • Close() —— 释放连接

再辅以少量描述性方法(Location 类信息、错误判定、冷存储 Warmup 预热)。这就够了。restic 本身负责分块、加密、压缩、聚合、索引、快照、保留策略;具体后端只负责搬运字节

这套边界的工程价值在于:如果你想加一个新后端(比如对象存储里新出的服务),通常只需要写一个几百行的 Backend 实现,不需要动 restic 核心的任何一行。这也是 rest-server 项目能独立存在的原因——它把 restic 的仓库协议单独跑成 HTTP 服务,让没有对象存储的环境(一台普通服务器、一块 NAS)也能当远程备份后端。

REST 协议本身是 restic 自定义的(不是 AWS S3 协议),文档写在仓库 doc/REST_backend.rst,主要定义了 GET /data/<id>POST /data/<id> 等几个端点、HTTP Basic 鉴权、流式上传。OpenAPI / Protobuf 都不在协议里——纯文本 + 二进制流。

§7 任务流案例:一次完整 backup → restore

这一节把前 6 节的内容串成一条真实任务。假设我们执行:

$ restic -r /tmp/backup backup ~/work

走过的链路如下:

  1. 打开仓库(repository.Open。读 config 文件;尝试打开一个 keys/ 下的密钥文件;向用户索要口令;用口令派生的密钥解开主密钥;把主密钥缓存在内存里。
  2. 加载索引。读 index/ 下所有索引文件,把 blob-id → (pack-id, offset, length) 全部塞进一个内存 map。这一步把「逻辑上引用一个 blob」变成「物理上知道它在哪个 pack 文件的什么位置」。仓库很大时(上百万 blob 量级),索引加载的时间和内存开销会成为启动的主要成本。
  3. 扫描 ~/work。archiver 走一遍目录树,得到「文件 + 元数据 + 内容流」的列表。restic 没有内置默认排除——.gitnode_modules 这类目录想跳过,必须显式写 --exclude 规则(或用 --exclude-if-present--exclude-caches 这类条件排除)。
  4. 对每个文件做 CDC 分块(chunker/。文件内容被切成一串 chunk(blob)。每个 blob 计算 SHA-256,先查本地索引:如果这个 blob 已经在仓库里,直接复用;如果不在,进入第 5 步。
  5. 聚合到 pack 文件(pack/。新 blob 进入一个内存中的「待写 pack 缓冲」。当缓冲达到默认目标大小(16 MiB)或 backup 结束时,把缓冲连同末尾 header 一起序列化、整体加密、上传到后端。
  6. 生成 tree 链(archiver/。在分块和上传的并行过程中,archiver 会构造当前目录的 tree——每写完一个文件就更新父目录 tree 的对应 entry;每写完一个目录就生成一个新 tree blob 并把它的 id 记到父目录的 entry 里。
  7. 生成 snapshot。所有文件写完后,构造一份 snapshot JSON,包含「time、hostname、username、paths、tags、root tree id」等元数据,把 snapshot 自身作为 blob 写入 snapshots/
  8. 刷新索引(repository.SaveIndex。把本次新增 / 访问过的 blob 映射写回 index/ 下的新文件。索引是「最后一致」的派生数据——这次 backup 没写完,下次 backup 启动时还能重建。
  9. 释放锁。备份完成后删除 locks/ 下的锁文件。

恢复的链路是上面 6 → 5 的反向版本:

$ restic -r /tmp/backup restore <snapshot-id> --target /tmp/restore
  1. snapshots/<id> 拿到 root tree id。
  2. 沿 tree 链递归,把每个 tree blob 从 data/<pack> 里取出来解密、解析、得到 entry 列表。
  3. 对每个 file entry,按 blob 列表从 pack 文件里把对应字节范围读出来、写回本地文件。
  4. 最后把所有文件的 mtime、uid、gid、mode 还原。

整个 backup 流程中没有「先把整个仓库下载到本地」这种操作——所有读写都按 blob / pack 粒度走 Backend interface。restore 也不会拉全量数据,只取真正需要的 blob。

§8 维护操作:forget / prune / check / repair

restic 把「保留策略」「回收存储」「校验一致性」拆成三个独立子命令,这条边界是 restic 区别于「无脑 rsync + cron」的核心理由之一。

forget 改的是 snapshot 层。它按 policy(比如 --keep-daily 7 --keep-weekly 4 --keep-monthly 6)删掉过期的 snapshot 引用——但它不动 data 层的 blob。也就是说,forget 之后「看历史」会少掉几份,但仓库大小不会立刻变小。

prune 改的是 data 层。官方文档给出的流程是三步:先扫全部 snapshot 算出仍在使用的数据;再把仓库里每个 pack 归为「完全占用 / 部分占用 / 完全未用」——完全未用的直接删,完全占用的原样保留,部分占用的按选项决定是否重打包;最后执行重打包、更新索引、删掉废弃文件。重打包需要从后端下载再重新上传受影响的 pack,对远端仓库很耗时,所以 prune 提供 --max-unused(默认允许 5% 的未用空间,用空间换流量)、--max-repack-size--dry-run 这些控制阀。snapshot 在 prune 前后完全不变;变的只是 data/ 目录里哪些文件留下来。

check 改的是校验语义。它对每个 pack 文件做:(a) 解密 + 验证 Poly1305 认证标签;(b) 把所有 blob 的 SHA-256 和 pack header 中的记录对一遍;(c) 把 index 里记录的 blob 集合和 pack 实际含有的 blob 集合做交集检查。任何一个 blob 哈希对不上,check 就会报告「数据损坏」。

repair 是 check 失败后的恢复手段。它能从一个完整 pack 重建丢失的 index 条目,或从一个完整 blob 重建丢失的 pack header——但无法恢复已经被损坏的 blob 自身。所以 check 应该定期跑,而不是出问题才跑。

这四者的依赖关系是:

forget → 删 snapshot
prune  → 真正回收存储(依赖 forget 之后)
check  → 校验(独立)
repair → 修 index / pack header(独立)

把它们错位用(比如「pruneforget 直接跑」「check 没跑过直接 repair」)是新手最容易踩的坑。

§9 性能与去重率:能推出什么、不能推出什么

restic 在 README 里没有给出统一 benchmark 数字——这是有意的,因为去重率和吞吐量高度依赖数据形态与后端选择。这里把常见的几类「能推出什么 / 不能推出什么」列清楚。

能推出的:

  • CDC 让「文件级修改」的去重率显著高于「整文件备份」。这是结构性结论:文件级修改只影响命中切点附近的那几个 chunk,其余 chunk 在两次备份中哈希不变。但具体省多少完全取决于数据形态——同样一次「改 5% 页面」的数据库目录,chunk 边界落在修改区域内外的比例不同,新块占比可以差出数倍。把 CDC 的收益当量级判断可以,当承诺不行。
  • 后端带宽往往是真正瓶颈。restic 在内部已经做了:(a) pack 级别的整体加密上传,(b) 索引里的 blob 去重(已经在仓库里的 blob 不重传),(c) 读取与上传并发——backup--read-concurrency(默认 2)控制并行读文件数,后端连接数用 -o <backend>.connections=... 调,全局 --pack-size 则调整目标 pack 大小(MiB,实际产物可能略大)。剩下的时间大部分花在「client 读源 + 后端 I/O 写远端」上。这意味着:在 1 Gbps 本地磁盘 + 100 Mbps S3 出口的机器上,backup 速率不会超过 100 Mbps。
  • v0.19 系列的性能改进集中在内存与索引。仓库 CHANGELOG.md 里 0.19.0 的对应条目:Enh #5713「Significantly speed up index loading」(大仓库索引加载显著提速,mount 改为启动时只加载一次索引)和 Enh #5610(降低 check / copy / diff / stats 的内存占用)。这类改进只在「仓库已经很大」时才可感知,小型仓库(万级 blob 以下)几乎测不出差别。

不能推出的:

  • 「restic 比 Borg 快 X%」 这类跨工具对比。它们的分块策略、加密模式、压缩选项、保留策略都不同,benchmark 的输入数据稍有差异,结论就会反转。仓库 README 也只说「restic should only be limited by your network or hard disk bandwidth」,没有给出绝对数字。
  • 「restic 在 X 后端上等价于原生客户端」。restic 的 S3 后端经 SDK 直接调 S3 API,一次 PUT 对应一个 pack 文件;aws s3 cp 传单个大文件时走 multipart 分片上传。两者的请求模式不同,吞吐数字不能互相套用,也随对象大小和并发度变化。仓库 README 也只说「restic should only be limited by your network or hard disk bandwidth」,没有给出绝对数字。
  • 「restic 的压缩率」。仓库格式 v2 起用 zstd 压缩:数据与 tree blob 按 --compression 档位压缩(现在有 auto(默认,只压明显可压缩的数据,省 CPU)、offfastestbettermax 五档),快照、索引这些元数据文件在 v2 中同样以 zstd 压缩存储。两点限制要记住:已写入仓库的数据压缩级别不能事后更改;从 v1 升级 v2 后,存量元数据要靠一次 prune(配合 --repack-uncompressed)才被压缩。压缩率本身没有单一数字可给——它是「输入数据 + 压缩参数 + chunk 分布」的函数。

§10 采用顺序与适用边界

把 restic 当成「一个能加密、能去重、能增量、支持本地/S3/SFTP 的备份工具」来用是它的舒适区。下面是按团队规模、备份规模给出的采用顺序建议。

先采用 restic 的场景:

  • 个人 / 小团队的数据备份(< 10 TiB),目标后端是本地 NAS、S3 兼容对象存储、自建 rest-server。典型组合:服务器 + S3-compatible + cron + forget/prune 月度清理。
  • 需要「备份不可信存储」。比如你要把数据备份到公有云 S3,但云厂商或管理员的访问是「不可信」的——restic 的端到端加密(密钥只在客户端解)正好覆盖这个需求。
  • 需要「快速验证备份可恢复性」restic check + restic mount(通过 FUSE 把历史快照挂载成只读文件系统)的组合,让恢复演练的门槛比 Borg、tar 这类工具低。

先用别的方案的场景:

  • 数据库热备(GB / 小时级别写入)。restic 不是为「持续、低延迟、流式增量」设计的;PostgreSQL / MySQL 应该先做物理 / 逻辑备份落盘,再把那份快照喂给 restic。
  • 几十 GB 级的单文件流式备份(如虚拟机磁盘镜像、容器镜像层)。restic 在设计上能处理,但 CDC 切块的代价 + 加密 + pack 聚合的链路会让单文件恢复粒度变粗。qemu-img + 对象存储 / ZFS send 通常更直接。
  • 需要 P2P / 多副本 / 跨地域同步。restic 是「单 client → 多 backend」模型,没有内置的多对多同步。需要跨机房复制要靠后端层自己解决(CRR、rclone bisync 等)。
  • 需要「连备份规模都不可观测」。restic 保证内容与文件名加密,但对象数量、pack 大小、备份节奏是明文可见的——有强隐蔽需求的用户(如 whistleblower)会嫌这一步不够。这类需求不是「换一种加密」能解决,而是要把仓库藏在加密隧道 / 匿名存储后面,或用流量混淆层掩盖访问模式。注意:restic 的文件名、目录结构本身就是加密的,不存在「文件名也要单独加密」这类额外要求。

采用顺序的实操建议:

  1. 第一周:本地 S3-compatible 仓库(MinIO 单机即可),跑通 init + backup + restore 三件套。
  2. 第二周:把后端换成「真实 S3 兼容存储」,加上 forget policy。
  3. 第一月:把 check 加入周度 cron,把 prune 加入季度 cron。
  4. 第三月起:把同一份数据再复制到第二个后端(不同 region / 不同云),用 restic copy 同步;这是社区常用的「多副本」做法——仓库本身靠锁文件协调并发,跨后端复制要在后端层解决。

§11 结尾判断

把 §1 - §10 收回来看,restic 的工程取舍是清晰的:

  • 它选端到端加密(AES-256-CTR + Poly1305)——这覆盖了内容、文件名、目录结构、索引;代价是「batch 规模(对象数、大小)仍然可观测」,收益是「后端完全不可信时的确定性安全」。
  • 它选 CDC 而不是固定偏移——代价是「blob 数量不可预测、index 大」,收益是「增量修改只重传受影响的小窗口」。
  • 它选 pack 聚合而不是 per-blob 上传——代价是「单 blob 读要把整个 pack 拉回来的一部分」,收益是「S3 友好、PUT 计费低」。
  • 它选 5 原则里的「Easy」——代价是「保留策略被拆成 forget / prune 两步,对新人不直观」,收益是「运维误删风险被显式化,不会一不小心把历史清空」。

这些取舍在 2026 年看仍然站得住——这是 restic 能维持十余年活跃开发 + 35.9K Stars + 几乎所有自托管备份方案都把 restic 当默认后端的根本原因。

如果你的备份需求落在「端到端加密 + 增量 + 多后端 + 命令行可脚本化」的交集里,restic 是当下最不坏的选择。如果你需要的特性在 §10 的反例集合里,请直接换工具——restic 不会因为新的需求而被「打补丁」成另一个工具,这是它最值得尊重的工程克制。

§12 事实核验与引用

  • 仓库全名与最新发布restic/restic,写作时最新 tag v0.19.1(2026-07-05 发布;35.9K Stars、1.9K Forks、BSD-2-Clause License、Go 作为主语言)。
  • 后端列表:来自 README 的 “Backends” 段,包含 local / SFTP / REST server / Amazon S3(含 Minio)/ OpenStack Swift / Backblaze B2 / Microsoft Azure Blob Storage / Google Cloud Storage / rclone。
  • 五大设计原则:来自 README 的 “Design Principles” 段(Easy / Fast / Verifiable / Secure / Efficient),逐条与原文一致。
  • 可复现构建:来自 README “Reproducible Builds” 段,「The binaries released with each restic version starting at 0.6.1 are reproducible」。
  • 包名边界chunker/(CDC 分块)、pack/(pack 文件生成)、backend/(后端接口)、repository/(仓库级状态机)均与仓库目录结构对应,但具体函数签名不在本文范围内——读者应直接读 internal/backend/backend.gointernal/repository/packer_manager.godoc/design.rst 确认。
  • 本文提及的设计细节出处:分块窗口 64 字节、blob 512 KiB–8 MiB、平均 1 MiB,来自 doc/design.rst(Backups and Deduplication 一节);pack 结构 EncryptedBlob… || EncryptedHeader || Header_Length(4B 小端)、header 条目的类型/长度/明文哈希编码,来自同文件 Pack Format 一节;加密格式 IV || Ciphertext || MAC、AES-256-CTR + Poly1305-AES、总开销 32 字节,来自同文件与官方 References;自 0.14 起可选 AES-256-GCM / ChaCha20-Poly1305,默认不变。pack 文件名为整个 pack 文件内容的 SHA-256,见 internal/repository/packer_manager.gosavePacker
  • 可调参数出处--pack-size(目标 pack 大小,MiB)是全局选项;backup --read-concurrency(默认 2)控制并行读取——均见 restic help 输出(doc/manual_rest.rst)。目标 pack 大小的工程背景见官方 Tuning Parameters 页面;源码常量为 DefaultPackSize = 16 MiBMinPackSize = 4 MiBMaxPackSize = 128 MiBinternal/repository/repository.go)。
  • 压缩语义出处--compression 五档(auto|off|fastest|better|max,默认 auto,仅限仓库格式 v2);v2 中快照 / 索引等元数据文件同样以 zstd 压缩(doc/design.rst 的 Unpacked Data Format 一节);存量 v1 数据的压缩迁移与 prune --repack-uncompressed,见官方文档 “Upgrading the repository format version” 一节。
  • prune 语义出处:三步流程与 --max-unused(默认 5%)等选项,来自 doc/060_forget.rst 的 “Customizing pruning” 一节。
  • 本文不覆盖:(a) restic 0.x 各版本之间的兼容性承诺;(b) rclone 30+ 后端的具体行为差异;(c) restic copyrestic rewrite 的内部实现;(d) FUSE restic mount 在不同操作系统上的可用性边界。这些主题需要单开一篇文章。

延伸阅读

参与讨论

使用 GitHub 登录。欢迎补充事实、异议与实践。