bsdiff-rust
基于 Rust + napi-rs 重构的高性能二进制差分工具,消除 Python/node-gyp 依赖痛点
目录
问题背景
在使用 bsdiff-node 进行二进制差分时,团队遇到以下痛点:
| 问题类型 | 具体表现 |
|---|---|
| 编译依赖 | 依赖 Python + node-gyp,安装时频繁报错 |
| 环境一致性 | 要求特定 Python 版本,团队成员环境难以统一 |
| 维护成本 | C/C++ 代码维护成本高,缺乏内存安全保障 |
| CI/CD 适配 | 构建流水线需要额外配置编译工具链 |
解决方案
我重构了一个版本是基于 rust + napi-rs,项目名称是 bsdiff-rust,对比下 bsdiff-node 将编译工作在发布阶段,用户下载只会按他的系统平台的版本按需下载,不会出现二次编译的问题,并且将原有仓库里的 C 和 C++ 代码重构成了 rust,维护起来稳定性和性能会更好。
┌─────────────────────────────────────────────────────────┐
│ 发布阶段(CI) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Windows │ │ macOS │ │ Linux │ │
│ │ x64/arm │ │ x64/arm │ │ x64/arm │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ └──────────────┼──────────────┘ │
│ ▼ │
│ 预编译二进制产物 │
└─────────────────────┬───────────────────────────────────┘
│
▼ npm install
┌─────────────────────────────────────────────────────────┐
│ 用户端 │
│ 按平台自动下载对应二进制,无需编译 │
└─────────────────────────────────────────────────────────┘
核心改进
- 预编译发布:编译工作前置到发布阶段,用户零编译
- 按需下载:根据
os+arch自动匹配二进制文件 - Rust 重写:内存安全、高性能、可维护性强
- 格式兼容:完全兼容 BSDIFF40 标准格式
快速开始
安装
# 推荐:直接下载预编译二进制,无需任何额外依赖
npm install bsdiff-rust
# 对比原方案(可能失败)
# npm install bsdiff-node # 需要系统自带 Python + node-gyp
基础用法
import { diff, patch } from 'bsdiff-rust';
// 生成补丁
await diff('old.zip', 'new.zip', 'patch.bin');
// 应用补丁
await patch('old.zip', 'patch.bin', 'restored.zip');
完整示例(含错误处理)
import { diff, patch } from 'bsdiff-rust';
// 生成补丁
(async () => {
try {
await diff('old.zip', 'new.zip', 'patch.bin');
console.log('Patch generated successfully!');
} catch (err) {
console.error('Failed to generate patch:', err);
}
})();
// 应用补丁
(async () => {
try {
await patch('old.zip', 'patch.bin', 'new_restored.zip');
console.log('Patch applied successfully!');
} catch (err) {
console.error('Failed to apply patch:', err);
}
})();
API 参考
| 方法 | 签名 | 描述 |
|---|---|---|
diff | (oldPath: string, newPath: string, patchPath: string) => Promise<void> | 生成差分补丁 |
patch | (oldPath: string, patchPath: string, newPath: string) => Promise<void> | 应用补丁还原 |
技术实现
1. 构建系统迁移
| 维度 | bsdiff-node | bsdiff-rust |
|---|---|---|
| 绑定方案 | node-gyp (C++) | napi-rs (Rust) |
| 编译时机 | 用户安装时 | 发布时预编译 |
| 依赖项 | Python, C++ 编译器 | 无 |
| 安装耗时 | 30s+ (含编译) | < 5s |
2. 核心算法与 BSDIFF40 格式
bsdiff 的补丁格式遵循严格标准:BSDIFF40。名称来源于补丁文件前 8 字节的魔数 "BSDIFF40"。所有符合该格式的补丁文件在不同实现间可互通——无论是原版 bsdiff/bspatch、bsdiff-node 还是 bsdiff-rust,都能正确解析和应用。
┌───────────────────────────────────────────────────────┐
│ BSDIFF 算法流程 │
├───────────────────────────────────────────────────────┤
│ 旧文件 ──┬──▶ 后缀数组构建 ──▶ 最长匹配搜索 │
│ │ │ │
│ 新文件 ──┴──────────────────────────┴──▶ 差分编码 │
│ │ │
│ ▼ │
│ BSDIFF40 Patch │
└───────────────────────────────────────────────────────┘
Rust 重构要点:
- 后缀数组使用
suffixcrate 优化 - bzip2 压缩使用
bzip2crate - 严格的内存安全,无
unsafe代码块(除 FFI 边界) - 完全兼容 BSDIFF40 格式头(
BSDIFF40magic + 3×8 字节长度字段)
3. napi-rs 集成
#[napi]
pub async fn diff(old_path: String, new_path: String, patch_path: String) -> Result<()> {
// 异步文件 I/O + 线程池计算
tokio::task::spawn_blocking(move || {
bsdiff_core::diff(&old_path, &new_path, &patch_path)
})
.await
.map_err(|e| Error::from_reason(e.to_string()))?
}
4. 为什么选择 Rust
Rust 在系统级性能上不输 C,同时提供内存安全保障,更适合现代工程场景。结合 napi-rs 的优势:
- 不依赖特定 Node-API 版本,跨 Node 版本兼容性好
- 预编译跨平台二进制,避免用户安装时的编译痛点
- 类型安全的 FFI 边界,减少运行时错误
平台支持
| 平台 | 架构 | 状态 |
|---|---|---|
| Windows | x64, x86, arm64 | ✅ |
| macOS | x64 (Intel), arm64 (Apple Silicon) | ✅ |
| Linux | x64, arm64, armv7 | ✅ |
| Linux (musl) | x64, arm64 | ✅ |
性能基准
测试环境:MacBook Pro M1, Node.js v20, 100MB 测试文件
| 指标 | bsdiff-node | bsdiff-rust | 提升 |
|---|---|---|---|
| 安装耗时 | ~35s | ~3s | -91% |
| diff 执行 | 12.3s | 10.1s | -18% |
| patch 执行 | 2.8s | 2.2s | -21% |
| 峰值内存 | 420MB | 315MB | -25% |
最佳实践
优化 Patch 体积
直接对文件夹差分效果不佳,推荐以下流程:
旧版本目录 新版本目录
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ tar 打包 │ (顺序稳定) │ tar 打包 │
└──────┬──────┘ └──────┬──────┘
▼ ▼
┌─────────────┐ ┌─────────────┐
│ zip/gzip 压缩│ (消除元数据噪音)│ zip/gzip 压缩│
└──────┬──────┘ └──────┬──────┘
│ │
└───────────┬───────────────────┘
▼
┌─────────────┐
│ bsdiff diff │
└──────┬──────┘
▼
小体积 patch
原理:
tar保证文件顺序一致,消除目录遍历顺序差异- 压缩消除时间戳、权限等元数据噪音
- 让 diff 算法看到更连续的字节流,提高匹配效率
示例代码
import { execSync } from 'child_process';
import { diff, patch } from 'bsdiff-rust';
// 打包 + 压缩
execSync('tar -cf old.tar ./old_dir && gzip old.tar');
execSync('tar -cf new.tar ./new_dir && gzip new.tar');
// 差分
await diff('old.tar.gz', 'new.tar.gz', 'update.patch');
// 应用补丁
await patch('old.tar.gz', 'update.patch', 'restored.tar.gz');
总结
bsdiff-rust 的目标不是重新发明 bsdiff 算法,而是将这个经典算法工程化,让它真正能在现代 Node.js 生态中稳定使用。如果你在项目里做 客户端增量更新 / 大文件增量发布 / Node.js 跨平台 diff,bsdiff-rust 是一个值得尝试的解决方案。
- ✅ 标准兼容 BSDIFF40 补丁格式
- ✅ 无需用户本地编译,提升安装体验
- ✅ Rust 高性能实现,稳定快速
- ✅ 适合客户端热更新、大文件增量发布等场景
项目地址:bsdiff-rust