Skip to content

AWS S3

欢迎来到 AWS S3 知识库!

☁️ S3 简介

Amazon S3 (Simple Storage Service) 是 AWS 提供的对象存储服务,是云存储的事实标准。自 2006 年推出以来,S3 已成为对象存储领域最成熟、功能最全面的服务。

🎯 S3 核心特性

✅ 主要优势

  • 99.999999999% 持久性 - 11 个 9 的数据持久性
  • 99.99% 可用性 - 高可用性保证
  • 无限扩展 - 存储容量无限制
  • 全球部署 - 多个区域可选
  • 丰富功能 - 版本控制、生命周期、事件通知等
  • 生态完善 - 与 AWS 其他服务无缝集成

📦 S3 核心概念

1. Bucket(存储桶)

特点:
- 全球唯一的名称(如 my-app-images)
- 属于特定区域(如 us-east-1)
- 根级别容器,不能嵌套
- 每个账户最多 100 个 bucket(可申请提额)

命名规则:
- 3-63 个字符
- 只能包含小写字母、数字、连字符和点号
- 不能以连字符或点号开头/结尾
- 不能使用 IP 地址格式

2. Object(对象)

对象组成:
├── Key: 对象的唯一标识符
│   例: images/2024/product-123.jpg
├── Value: 对象数据(0 字节到 5TB)
├── Metadata: 元数据
│   ├── 系统元数据
│   │   - Content-Type
│   │   - Content-Length
│   │   - Last-Modified
│   │   - ETag
│   └── 用户定义元数据
│       - x-amz-meta-*
├── Version ID: 版本标识(开启版本控制后)
├── Access Control: 访问控制
└── Storage Class: 存储类别

3. Key(对象键)

S3 是扁平化存储,没有真正的目录:

key = "images/products/2024/01/photo.jpg"

看起来像目录结构,实际上:
- 整个字符串是一个 key
- 使用 / 分隔只是命名约定
- S3 控制台会模拟目录显示

最佳实践:
- 使用有意义的前缀组织对象
- 考虑性能优化(避免连续前缀)
- 避免特殊字符

🚀 快速开始

1. 安装 AWS SDK

bash
# Node.js
npm install aws-sdk

# Python
pip install boto3

# Java
# 添加到 pom.xml
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.12.x</version>
</dependency>

# Go
go get github.com/aws/aws-sdk-go/service/s3

2. 配置凭证

bash
# 方式一: 环境变量
export AWS_ACCESS_KEY_ID=your_access_key
export AWS_SECRET_ACCESS_KEY=your_secret_key
export AWS_DEFAULT_REGION=us-east-1

# 方式二: AWS CLI 配置
aws configure

# 方式三: 配置文件 (~/.aws/credentials)
[default]
aws_access_key_id = your_access_key
aws_secret_access_key = your_secret_key

3. Node.js 示例

javascript
const AWS = require('aws-sdk');

// 配置 S3
const s3 = new AWS.S3({
  region: 'us-east-1',
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});

// 创建 Bucket
const createBucket = async (bucketName) => {
  try {
    await s3.createBucket({ Bucket: bucketName }).promise();
    console.log(`Bucket ${bucketName} created`);
  } catch (err) {
    console.error(err);
  }
};

// 上传文件
const uploadFile = async (bucketName, key, fileContent) => {
  const params = {
    Bucket: bucketName,
    Key: key,
    Body: fileContent,
    ContentType: 'image/jpeg',
    // ACL: 'public-read',  // 公开读取
    Metadata: {
      'author': 'John Doe',
      'uploaded-by': 'my-app'
    }
  };
  
  try {
    const result = await s3.upload(params).promise();
    console.log('File uploaded:', result.Location);
    return result;
  } catch (err) {
    console.error(err);
  }
};

// 下载文件
const getFile = async (bucketName, key) => {
  const params = {
    Bucket: bucketName,
    Key: key
  };
  
  try {
    const data = await s3.getObject(params).promise();
    return data.Body;
  } catch (err) {
    console.error(err);
  }
};

// 列出对象
const listObjects = async (bucketName, prefix) => {
  const params = {
    Bucket: bucketName,
    Prefix: prefix,
    MaxKeys: 100
  };
  
  try {
    const data = await s3.listObjectsV2(params).promise();
    return data.Contents;
  } catch (err) {
    console.error(err);
  }
};

// 删除对象
const deleteFile = async (bucketName, key) => {
  const params = {
    Bucket: bucketName,
    Key: key
  };
  
  try {
    await s3.deleteObject(params).promise();
    console.log(`File ${key} deleted`);
  } catch (err) {
    console.error(err);
  }
};

// 批量删除
const deleteMultipleFiles = async (bucketName, keys) => {
  const params = {
    Bucket: bucketName,
    Delete: {
      Objects: keys.map(key => ({ Key: key }))
    }
  };
  
  try {
    const result = await s3.deleteObjects(params).promise();
    console.log('Deleted:', result.Deleted.length);
    return result;
  } catch (err) {
    console.error(err);
  }
};

4. Python (Boto3) 示例

python
import boto3
from botocore.exceptions import ClientError

# 创建 S3 客户端
s3 = boto3.client('s3',
    region_name='us-east-1',
    aws_access_key_id='your_access_key',
    aws_secret_access_key='your_secret_key'
)

# 上传文件
def upload_file(bucket_name, file_path, object_key):
    try:
        s3.upload_file(file_path, bucket_name, object_key)
        print(f"File uploaded to {object_key}")
    except ClientError as e:
        print(f"Error: {e}")

# 下载文件
def download_file(bucket_name, object_key, file_path):
    try:
        s3.download_file(bucket_name, object_key, file_path)
        print(f"File downloaded to {file_path}")
    except ClientError as e:
        print(f"Error: {e}")

# 生成预签名 URL
def generate_presigned_url(bucket_name, object_key, expiration=3600):
    try:
        url = s3.generate_presigned_url(
            'get_object',
            Params={'Bucket': bucket_name, 'Key': object_key},
            ExpiresIn=expiration
        )
        return url
    except ClientError as e:
        print(f"Error: {e}")
        return None

🔐 预签名 URL

预签名 URL 允许临时授权访问私有对象,无需公开对象权限。

Node.js 实现

javascript
// GET 预签名 URL (下载)
const getPresignedUrl = (bucketName, key, expiresIn = 3600) => {
  const params = {
    Bucket: bucketName,
    Key: key,
    Expires: expiresIn  // 秒
  };
  
  return s3.getSignedUrl('getObject', params);
};

// PUT 预签名 URL (上传)
const getUploadPresignedUrl = (bucketName, key, contentType, expiresIn = 3600) => {
  const params = {
    Bucket: bucketName,
    Key: key,
    ContentType: contentType,
    Expires: expiresIn
  };
  
  return s3.getSignedUrl('putObject', params);
};

// 使用示例
const url = getPresignedUrl('my-bucket', 'images/photo.jpg', 3600);
console.log('Download URL:', url);

// 前端可以直接使用这个 URL 下载文件
// <a href="${url}" download>Download</a>

前端直传实现

javascript
// 后端生成上传 URL
app.post('/api/get-upload-url', async (req, res) => {
  const { fileName, fileType } = req.body;
  const key = `uploads/${Date.now()}-${fileName}`;
  
  const url = s3.getSignedUrl('putObject', {
    Bucket: 'my-bucket',
    Key: key,
    ContentType: fileType,
    Expires: 300  // 5分钟
  });
  
  res.json({ url, key });
});

// 前端使用 URL 直传
async function uploadFile(file) {
  // 1. 获取预签名 URL
  const response = await fetch('/api/get-upload-url', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      fileName: file.name,
      fileType: file.type
    })
  });
  
  const { url, key } = await response.json();
  
  // 2. 直接上传到 S3
  await fetch(url, {
    method: 'PUT',
    body: file,
    headers: {
      'Content-Type': file.type
    }
  });
  
  console.log('File uploaded:', key);
}

📊 存储类别

存储类别对比

存储类别使用场景可用性检索时间最短存储时间相对成本
S3 Standard频繁访问99.99%毫秒1.0x
S3 Intelligent-Tiering访问模式未知99.9%毫秒30天0.8-1.0x
S3 Standard-IA低频访问99.9%毫秒30天0.5x
S3 One Zone-IA低频访问(单区)99.5%毫秒30天0.4x
S3 Glacier Instant归档即时访问99.9%毫秒90天0.2x
S3 Glacier Flexible归档99.99%分钟-小时90天0.1x
S3 Glacier Deep Archive长期归档99.99%12小时180天0.04x

设置存储类别

javascript
// 上传时指定
const uploadWithStorageClass = async (bucketName, key, body) => {
  const params = {
    Bucket: bucketName,
    Key: key,
    Body: body,
    StorageClass: 'STANDARD_IA'  // 低频访问
  };
  
  await s3.upload(params).promise();
};

// 修改现有对象的存储类别
const changeStorageClass = async (bucketName, key) => {
  const params = {
    Bucket: bucketName,
    CopySource: `${bucketName}/${key}`,
    Key: key,
    StorageClass: 'GLACIER',
    MetadataDirective: 'COPY'
  };
  
  await s3.copyObject(params).promise();
};

🔄 生命周期管理

自动转换存储类别或删除对象。

javascript
const setLifecyclePolicy = async (bucketName) => {
  const params = {
    Bucket: bucketName,
    LifecycleConfiguration: {
      Rules: [
        {
          Id: 'Move to IA after 30 days',
          Status: 'Enabled',
          Prefix: 'logs/',  // 仅应用于 logs/ 前缀
          Transitions: [
            {
              Days: 30,
              StorageClass: 'STANDARD_IA'
            },
            {
              Days: 90,
              StorageClass: 'GLACIER'
            }
          ],
          Expiration: {
            Days: 365  // 1年后删除
          }
        },
        {
          Id: 'Delete incomplete multipart uploads',
          Status: 'Enabled',
          AbortIncompleteMultipartUpload: {
            DaysAfterInitiation: 7
          }
        }
      ]
    }
  };
  
  await s3.putBucketLifecycleConfiguration(params).promise();
};

📝 版本控制

保留对象的多个版本,防止意外删除或覆盖。

javascript
// 启用版本控制
const enableVersioning = async (bucketName) => {
  const params = {
    Bucket: bucketName,
    VersioningConfiguration: {
      Status: 'Enabled'
    }
  };
  
  await s3.putBucketVersioning(params).promise();
};

// 列出对象的所有版本
const listVersions = async (bucketName, key) => {
  const params = {
    Bucket: bucketName,
    Prefix: key
  };
  
  const data = await s3.listObjectVersions(params).promise();
  return data.Versions;
};

// 下载特定版本
const getObjectVersion = async (bucketName, key, versionId) => {
  const params = {
    Bucket: bucketName,
    Key: key,
    VersionId: versionId
  };
  
  const data = await s3.getObject(params).promise();
  return data.Body;
};

// 删除特定版本
const deleteObjectVersion = async (bucketName, key, versionId) => {
  const params = {
    Bucket: bucketName,
    Key: key,
    VersionId: versionId
  };
  
  await s3.deleteObject(params).promise();
};

🔔 事件通知

S3 可以在对象创建、删除时触发事件。

javascript
// 配置事件通知到 Lambda
const setEventNotification = async (bucketName, lambdaArn) => {
  const params = {
    Bucket: bucketName,
    NotificationConfiguration: {
      LambdaFunctionConfigurations: [
        {
          Events: ['s3:ObjectCreated:*'],
          LambdaFunctionArn: lambdaArn,
          Filter: {
            Key: {
              FilterRules: [
                {
                  Name: 'prefix',
                  Value: 'uploads/'
                },
                {
                  Name: 'suffix',
                  Value: '.jpg'
                }
              ]
            }
          }
        }
      ]
    }
  };
  
  await s3.putBucketNotificationConfiguration(params).promise();
};

💡 最佳实践

1. 命名规范

javascript
// ✅ 好的命名
const key = `users/${userId}/avatars/${timestamp}-${uuid}.jpg`;
const key = `invoices/${year}/${month}/${invoiceId}.pdf`;

// ❌ 避免
const key = `用户头像.jpg`;  // 避免中文
const key = `file(1).jpg`;   // 避免特殊字符

2. 性能优化

javascript
// 避免连续的字母数字前缀(会导致分区热点)
// ❌ 不好
user-001/...
user-002/...
user-003/...

// ✅ 好 - 添加随机前缀
const hash = md5(userId).substring(0, 4);
const key = `${hash}/users/${userId}/avatar.jpg`;

3. 成本优化

javascript
// 使用生命周期策略自动归档
// 定期清理未完成的分片上传
// 使用智能分层存储

📖 学习资源

官方文档

推荐阅读


准备好了吗?开始使用 AWS S3!