版本控制
S3 版本控制功能可以保留、检索和恢复对象的每个版本,防止意外删除或覆盖。
🔖 什么是版本控制
版本控制为 Bucket 中的每个对象保留多个变体。当启用版本控制后:
- 每次上传同名对象会创建新版本
- 删除对象会创建删除标记,而不是真正删除
- 可以恢复到任意历史版本
🎯 版本控制状态
Bucket 有三种版本控制状态:
- 未版本控制(默认) - 不保留版本
- 启用版本控制 - 保留所有版本
- 暂停版本控制 - 停止创建新版本,保留现有版本
🚀 启用版本控制
Node.js
javascript
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
// 启用版本控制
const enableVersioning = async (bucketName) => {
const params = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled'
}
};
try {
await s3.putBucketVersioning(params).promise();
console.log('Versioning enabled');
} catch (err) {
console.error(err);
}
};
// 暂停版本控制
const suspendVersioning = async (bucketName) => {
const params = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Suspended'
}
};
await s3.putBucketVersioning(params).promise();
console.log('Versioning suspended');
};
// 获取版本控制状态
const getVersioningStatus = async (bucketName) => {
const result = await s3.getBucketVersioning({
Bucket: bucketName
}).promise();
console.log('Versioning status:', result.Status || 'Unversioned');
console.log('MFA Delete:', result.MFADelete || 'Disabled');
return result;
};AWS CLI
bash
# 启用版本控制
aws s3api put-bucket-versioning \
--bucket my-bucket \
--versioning-configuration Status=Enabled
# 查看状态
aws s3api get-bucket-versioning --bucket my-bucket📋 列出对象版本
列出所有版本
javascript
const listObjectVersions = async (bucketName, prefix = '') => {
const params = {
Bucket: bucketName,
Prefix: prefix
};
try {
const result = await s3.listObjectVersions(params).promise();
console.log('Versions:');
result.Versions?.forEach(version => {
console.log(` ${version.Key}`);
console.log(` Version ID: ${version.VersionId}`);
console.log(` Last Modified: ${version.LastModified}`);
console.log(` Size: ${version.Size} bytes`);
console.log(` Is Latest: ${version.IsLatest}`);
console.log('---');
});
console.log('Delete Markers:');
result.DeleteMarkers?.forEach(marker => {
console.log(` ${marker.Key}`);
console.log(` Version ID: ${marker.VersionId}`);
console.log(` Last Modified: ${marker.LastModified}`);
console.log('---');
});
return result;
} catch (err) {
console.error(err);
}
};列出特定对象的所有版本
javascript
const listVersionsOfObject = async (bucketName, key) => {
const params = {
Bucket: bucketName,
Prefix: key
};
const result = await s3.listObjectVersions(params).promise();
// 过滤出精确匹配的对象
const versions = result.Versions?.filter(v => v.Key === key) || [];
console.log(`Versions of ${key}:`);
versions.forEach((v, index) => {
console.log(`${index + 1}. Version ${v.VersionId}`);
console.log(` Date: ${v.LastModified}`);
console.log(` Size: ${v.Size} bytes`);
console.log(` Latest: ${v.IsLatest}`);
});
return versions;
};⬇️ 下载特定版本
javascript
// 下载最新版本(不指定版本ID)
const getLatestVersion = async (bucketName, key) => {
const result = await s3.getObject({
Bucket: bucketName,
Key: key
}).promise();
console.log('Version ID:', result.VersionId);
return result.Body;
};
// 下载特定版本
const getSpecificVersion = async (bucketName, key, versionId) => {
const result = await s3.getObject({
Bucket: bucketName,
Key: key,
VersionId: versionId
}).promise();
return result.Body;
};
// 下载所有版本
const downloadAllVersions = async (bucketName, key) => {
const versions = await listVersionsOfObject(bucketName, key);
for (const version of versions) {
const content = await getSpecificVersion(bucketName, key, version.VersionId);
const filename = `${key}.${version.VersionId}`;
// 保存到本地
require('fs').writeFileSync(filename, content);
console.log(`Downloaded: ${filename}`);
}
};🗑️ 删除操作
软删除(创建删除标记)
javascript
// 不指定版本ID的删除会创建删除标记
const softDelete = async (bucketName, key) => {
const result = await s3.deleteObject({
Bucket: bucketName,
Key: key
// 不指定 VersionId
}).promise();
console.log('Delete Marker ID:', result.VersionId);
// 对象看起来被删除了,但所有版本仍然存在
};硬删除(永久删除特定版本)
javascript
// 指定版本ID的删除会永久删除该版本
const hardDelete = async (bucketName, key, versionId) => {
await s3.deleteObject({
Bucket: bucketName,
Key: key,
VersionId: versionId
}).promise();
console.log(`Permanently deleted version ${versionId}`);
};删除所有版本
javascript
const deleteAllVersions = async (bucketName, key) => {
// 1. 列出所有版本
const params = {
Bucket: bucketName,
Prefix: key
};
const result = await s3.listObjectVersions(params).promise();
// 2. 删除所有版本和删除标记
const objectsToDelete = [
...(result.Versions || [])
.filter(v => v.Key === key)
.map(v => ({
Key: v.Key,
VersionId: v.VersionId
})),
...(result.DeleteMarkers || [])
.filter(d => d.Key === key)
.map(d => ({
Key: d.Key,
VersionId: d.VersionId
}))
];
if (objectsToDelete.length === 0) {
console.log('No versions found');
return;
}
// 3. 批量删除
await s3.deleteObjects({
Bucket: bucketName,
Delete: {
Objects: objectsToDelete
}
}).promise();
console.log(`Deleted ${objectsToDelete.length} versions`);
};♻️ 恢复已删除的对象
javascript
// 删除删除标记以恢复对象
const restoreDeletedObject = async (bucketName, key) => {
// 1. 找到删除标记
const result = await s3.listObjectVersions({
Bucket: bucketName,
Prefix: key
}).promise();
const deleteMarker = result.DeleteMarkers?.find(
d => d.Key === key && d.IsLatest
);
if (!deleteMarker) {
console.log('Object is not deleted');
return;
}
// 2. 删除删除标记
await s3.deleteObject({
Bucket: bucketName,
Key: key,
VersionId: deleteMarker.VersionId
}).promise();
console.log('Object restored');
};🔄 恢复到特定版本
javascript
// 通过复制旧版本来恢复
const restoreToVersion = async (bucketName, key, versionId) => {
// 复制旧版本作为新的当前版本
await s3.copyObject({
Bucket: bucketName,
CopySource: `${bucketName}/${key}?versionId=${versionId}`,
Key: key
}).promise();
console.log(`Restored to version ${versionId}`);
};🔒 MFA 删除
MFA (多因素认证) 删除提供额外的安全层,防止意外删除。
javascript
// 启用 MFA 删除(需要通过 AWS CLI 或控制台)
// 只有 Bucket 所有者的根账户可以启用
// AWS CLI 示例
// aws s3api put-bucket-versioning \
// --bucket my-bucket \
// --versioning-configuration Status=Enabled,MFADelete=Enabled \
// --mfa "arn:aws:iam::123456789012:mfa/root-account-mfa-device 123456"
// 启用后,删除版本需要 MFA 码
const deleteWithMFA = async (bucketName, key, versionId, mfaCode) => {
await s3.deleteObject({
Bucket: bucketName,
Key: key,
VersionId: versionId,
MFA: `arn:aws:iam::123456789012:mfa/device ${mfaCode}`
}).promise();
};💰 成本管理
查看版本占用的存储
javascript
const calculateVersionStorage = async (bucketName) => {
const result = await s3.listObjectVersions({
Bucket: bucketName
}).promise();
let totalSize = 0;
let versionCount = 0;
result.Versions?.forEach(v => {
totalSize += v.Size;
versionCount++;
});
console.log(`Total versions: ${versionCount}`);
console.log(`Total size: ${(totalSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
return { versionCount, totalSize };
};使用生命周期管理旧版本
javascript
const setVersionLifecycle = async (bucketName) => {
const params = {
Bucket: bucketName,
LifecycleConfiguration: {
Rules: [
{
Id: 'Delete old versions',
Status: 'Enabled',
NoncurrentVersionExpiration: {
NoncurrentDays: 30 // 非当前版本保留30天
},
AbortIncompleteMultipartUpload: {
DaysAfterInitiation: 7
}
}
]
}
};
await s3.putBucketLifecycleConfiguration(params).promise();
console.log('Version lifecycle configured');
};💡 最佳实践
1. 选择性启用版本控制
javascript
// ✅ 适合启用版本控制的场景
- 重要文档
- 配置文件
- 数据库备份
- 需要审计跟踪的数据
// ❌ 不适合版本控制的场景
- 日志文件(太多版本)
- 临时文件
- 大型媒体文件(成本高)2. 设置版本过期策略
javascript
// 自动清理旧版本以控制成本
const smartVersionLifecycle = {
Rules: [
{
Id: 'Manage versions',
Status: 'Enabled',
NoncurrentVersionTransitions: [
{
NoncurrentDays: 30,
StorageClass: 'STANDARD_IA' // 旧版本转低频
},
{
NoncurrentDays: 90,
StorageClass: 'GLACIER' // 更旧的版本归档
}
],
NoncurrentVersionExpiration: {
NoncurrentDays: 365 // 1年后删除旧版本
}
}
]
};3. 监控版本数量
javascript
// 定期检查并清理不需要的版本
const cleanupOldVersions = async (bucketName, keepVersions = 5) => {
const result = await s3.listObjectVersions({
Bucket: bucketName
}).promise();
// 按对象分组
const objectVersions = {};
result.Versions?.forEach(v => {
if (!objectVersions[v.Key]) {
objectVersions[v.Key] = [];
}
objectVersions[v.Key].push(v);
});
// 删除超出保留数量的版本
for (const [key, versions] of Object.entries(objectVersions)) {
if (versions.length > keepVersions) {
// 按日期排序,保留最新的
versions.sort((a, b) =>
new Date(b.LastModified) - new Date(a.LastModified)
);
const toDelete = versions.slice(keepVersions);
for (const version of toDelete) {
await s3.deleteObject({
Bucket: bucketName,
Key: key,
VersionId: version.VersionId
}).promise();
console.log(`Deleted old version of ${key}`);
}
}
}
};4. 版本对比
javascript
// 对比两个版本的差异
const compareVersions = async (bucketName, key, version1, version2) => {
const [content1, content2] = await Promise.all([
getSpecificVersion(bucketName, key, version1),
getSpecificVersion(bucketName, key, version2)
]);
const str1 = content1.toString('utf-8');
const str2 = content2.toString('utf-8');
if (str1 === str2) {
console.log('Versions are identical');
} else {
console.log('Versions differ');
// 使用 diff 库进行详细对比
}
};⚠️ 注意事项
- 版本控制无法关闭 - 只能暂停,已有版本会保留
- 存储成本 - 每个版本都占用存储空间并计费
- 删除标记 - 软删除会创建删除标记,也占用空间
- 列举性能 - 大量版本会影响 listObjectVersions 性能
- 最终一致性 - 高并发下版本列表可能不是最新的
📖 相关资源
恭喜!你已经完成 AWS S3 的所有核心知识学习!🎉