Bun技术评估 - 07 S3

概述

本文是笔者的系列博文 《Bun技术评估》 中的第七篇。

本文主要探讨的内容,是探讨在bun的体系中,如何集成和处理网络化的对象存储系统,具体而言就是对Amazon S3或者其兼容系统的操作的方式和内容。

这部分内容,其实对于现代的Web应用是非常关键和重要的,因为现在的Web应用,很多情况下都需要和各种各样的文件处理打交道。原有的实现方式,基本上是以文件系统+HTTP服务的模式来提供的。这个模式存在很多问题,比如上传文件,需要使用MultiPart文件模式操作实现服务,无法提供多文件选择和上传进度的监控,体验不佳。在技术上,可能还需要对临时文件系统进行复制和迁移,或者需要使用NFS这种系统在服务器后端来共享文件,还可能需要Web文件服务,访问控制等等,缺点就是系统和环节众多,实现流程复杂,性能损耗较大等等。

所以,现在的解决方式,都比较倾向于选择Amazon提出的网络化的文件存储系统和解决方案,即S3系统。

S3和MINIO

S3的全称是Simple Storage Service(简单存储服务),它早期是一个由Amazon Web Services(AWS)提供的对象存储和访问服务,这里的对象是指任意形式的数据文件,包括但不限于网站静态资源、图片、音视频、日志文件、备份数据等等。

这里借用一张图来总结一下S3的特点和优势(当然它主要是从云计算系统的角度出发的描述):

即:

  • 按需付费:费用并非一个固定的软硬件投资,而是按照所需要的容量、流量和时间付费
  • 可扩展性:用户可以方便扩展容量
  • 可用性和持续性:Amazon保证服务的可用性和可持续性
  • 安全性:Amazon设计的安全和访问控制体系,保证比网络文件系统更高的安全性
  • 易用性:提供各种客户端和API,方便使用和集成
  • 内置的优化:Amazon会优化数据的存储

笔者理解,和传统的基于文件系统的文件存储服务不同,S3就是一个兼容HTTP协议的互联网化的文件存储、访问和管理系统S3,并且提供了很多特别适合于网络应用的功能、特性和规范。虽然不是SMB、NFS这种标准化的技术系统,但由于其设计实现完善合理,使用成熟而广泛,已经成为互联网对象存储服务的事实标准。而且最重要的是,虽然从官方角度而言S3是Amazon提供的一种网络服务,但这一技术体系基本上是开放的,市场上已经有很多兼容的系统可用,甚至用户可用自己架构起私有的S3系统,比如笔者在本文中评估和使用的minio,就是这样一个系统。

MINIO是一个兼容S3的开源对象存储系统。这里兼容的意思是其操作方式,命令、客户端和API都是和S3一样的,对于开发者而言,基本上不需要修改原有的代码,仅修改配置信息,就可以使当前的支持S3的应用系统,支持MINIO系统,而这个系统可以是自己架构的,这一就为很多企业和政府的应用,提供了S3范式的文件存储和服务能力。

现在MINIO已经更名为AISTOR,但它的MINIO软件,还是以社区开源的方式,提供给用户进行自行部署和管理。MINIO的安装和部署非常简单,它是那种典型的"互联网"应用系统,可以提供单一可执行文件+参数启动的部署方式,如下面的命令,可以简单的启动一个单主机多磁盘模式的MINIO服务:

shell 复制代码
// minio主文件夹
mkdir /opt/minio -p

// 数据文件夹,多磁盘挂载
mkdir /opt/minio/data1 && mount /dev/sdc /opt/minio/data1
mkdir /opt/minio/data2 && mount /dev/sdd /opt/minio/data2
mkdir /opt/minio/data3 && mount /dev/sde /opt/minio/data3
mkdir /opt/minio/data4 && mount /dev/sdf /opt/minio/data4

// 下载 minio
cd /opt/minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio

// 启动服务
./minio --version
/opt/minio/minio server --address :9000 --console-address :9001  /opt/minio/data{1...4}

除了S3和Minio之外,其他Bun S3支持的兼容系统,还包括Google Cloud Storeage,Cloudflare R2,Deigital Ocean Space,Supabase等系统,配置和使用方式也是大同小异。

在了解的S3系统的基本情况之后,下面进入本文的主题,将S3系统和Bun应用进行集成。

基本形式

Bun对S3提供原生和内置的支持,可以直接使用。在bun s3模块中,进行数据和文件操作的基本模式可以参考下面的代码:

js 复制代码
// import module
import { s3, write, S3Client } from "bun";

// 配置和客户端实例
const mioConfig = {
    endpoint: "http://192.168.9.192:9000",    // Make sure to use the correct endpoint URL
    accessKeyId: "public",
    secretAccessKey: "Public2025",
    bucket: "public", // woring bucket
}

const minio = new S3Client(mioConfig);

// 写入内容
await minio.write("data.json", JSON.stringify({ hello: "world" }), {
// ...credentials,
    type: "application/json",
});

// 下载内容
const s3file = minio.file("data.json");
const data = await s3file.json();
console.log("Data:", data);

简单说明如下:

  • 使用前需要import模块相关方法,此处的核心是S3Client这个类,当然这些类都是内置的,无需外部安装
  • 使用连接参数对S3Client进行初始化
  • 基本参数包括accessKeyId(用户名)、secretAccessKey(密码)、bucket存储桶、endpoint端点地址等
  • 如果要上传文件或者写入内容,可以使用client的write方法
  • 访问或者下载文件,需要先实例化一个文件对象
  • 然后执行对象的内容转换方法,就可以获取对象内容(有点像http模块的fetch方法)

文件对象模式

在基本形式中,是直接使用客户端实例来对数据进行操作的。但bun s3也提高了基于文件对象进行操作的模式,更加直观方便:

js 复制代码
// file() returns a lazy reference to a file on S3
const metadata = minio.file("user/tom.json");

// Upload to S3
await write(metadata, JSON.stringify({ name: "Tom", age: 0 | Math.random()*50 }));

这个机制工作的原理是,先使用文件名,和file()方法,创建一个文件对象实例,然后就可以使用这个实例进行相关的操作了,比如写入信息,读取信息或者流等等。这个时候,其实文件可以是不先已经存在的,就是一个逻辑对象,minio服务器会自动处理。

内容读取

读取S3文件的内容,示例中使用的是json(),因为我们确定存储的是一个JSON对象(虽然是文件方式)。其他可用方法包括:

  • json(), json对象
  • text(),文本文件内容
  • arrayBuffer(),文件buffer,支持任意实际内容和格式
  • slice(),文件片段
  • stream(),读取流

除了读取整个文件之外,还配合使用slice方法,读取部分内容,操作方式如下:

js 复制代码
const partial = s3file.slice(0, 1024);

// Read the partial range as a Uint8Array
const bytes = await partial.bytes();

// Read the partial range as a string
const text = await partial.text();

最后,还有一种常用重要的信息读取方式,就是文件流,我们在后续章节专门讨论。

文件流

S3设计的一个主要目标,就是大量的文件处理,当然也就包括大规模并发的大型文件的上传和下载的应用,在这种情况下,将整个文件作为一个整体来处理是不现实的,必须要引入数据流的技术。官方提供的示例代码如下:

js 复制代码
// 下载文件流
const stream = s3file.stream();
for await (const chunk of stream) {
  console.log(chunk);
}

// 上传大型文件
const writer = s3file.writer({
  // Automatically retry on network errors up to 3 times
  retry: 3,

  // Queue up to 10 requests at a time
  queueSize: 10,

  // Upload in 5 MB chunks
  partSize: 5 * 1024 * 1024,
});

const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
for (let i = 0; i < 10; i++) {
  await writer.write(bigFile);
}
await writer.end();

在这个示例中,对于文件的读取和写入,都可以使用文件流的操作方式。文件流的基本原理和概念,就是不一次性处理整个文件,而是将文件分割成为方便处理的小型片段(slice),然后一次处理一个片段,就不会遇到很大的性能和资源占用的问题了,当然,使用流处理大型文件,除了分片之外,还需要考虑分片处理的效率、有序性、完整性等问题,这也是任何一个流处理机制需要解决的问题。

示例代码中可以看到,Bun S3处理下载和上传方式略有不同。下载时,s3file对象本身就提供了stream方法,它本身就可以输出数据流,只需要在接收端处理数据片段即可。

上传文件稍微复杂一点。需要先创建一个写流对象,并且有一些选项可以配置重试次数,写流分片的大小,队列大小等。然后调用其写流的方法,来传输实际的文件内容(当然也可以对接一个读取流)。

Presign URL 预签名地址

数据的访问控制,是所有对象存储系统都需要考虑的问题,尤其是在互联网应用这种非常开放的应用场景当中。传统的基于文件系统的用户认证和文件访问控制机制,显然是不能适应这些应用场景的。而普通的HTTP资源访问控制方式(用户认证-响应)也无法很好的适应这种程序化的数据访问和传输场景。为此S3设计和提供了一种Presign(预签名)的访问控制方式,有效的简化和解决这个问题。

我们先来看一下这个操作是如何实现的:

js 复制代码
// file() returns a lazy reference to a file on S3
const metadata = minio.file("user/tom.json");

// // Presign a URL (synchronous - no network request needed)
const url = metadata.presign({
    acl: "public-read",
    expiresIn: 60 * 60 * 24, // 1 day
});    
console.log("URL:", url);

// 预签名URL内容
URL: http://192.168.9.192:9000/public/user/tom.json
?X-Amz-Acl=public-read
&X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=public%2F20250630%2Fus-east-1%2Fs3%2Faws4_request
&X-Amz-Date=20250630T065109Z&X-Amz-Expires=86400
&X-Amz-SignedHeaders=host
&X-Amz-Signature=8427050878db389957a2f7b44d54d9f4c94111a870ba5dec87ff8696271ba275

S3处理的基本机制是,对于所有的文件对象,都可以抽象成为一个URL地址。然后对这个地址,基于服务器的配置进行一个签名,并且生成一个带有签名信息的新的URL地址。这些签名信息中具有访问控制信息、访问策略、签名时间和内容等等。S3客户端需要使用这个签名后的URL来进行操作,这时服务器可以对这个操作URL进行验证,由于这些信息是动态的,就保证了这一过程的安全性。这样就实现了一种服务器可验证的临时的对象访问的控制。

开发者可以通过控制签名的过程,来实现不同的访问控制策略。比如示例中可以配置签名过期的时间,和预置的访问策略(公共可读,或者任何可配置的ACL),简单而灵活。

系统内置的常见可用ACL包括:

  • "public-read": 可开放读取
  • "public-read-write": 可开放读写
  • "private": 只有桶所有者可读
  • "authenticated-read": 认证用户可读
  • "bucket-owner-read": 桶所有者可读
  • "bucket-owner-full-control": 桶所有者完全控制
  • "aws-exec-read": aws账号可读
  • "log-delivery-write": AWS服务用于日志记录

除了ACL之外,预签名还可以配置访问方式,包括PUT/DELETE/GET/HEAD/POST等等,达成更精细的控制。

文件管理

除了最常用的文件上传下载之外,Bun S3还提供了很多兼容的S3文件管理功能,包括:

  • S3Client.list: 文件列表,可以使用prefix提供简单的查询功能
  • S3Client.exists: 检查文件是否存在
  • S3Client.size: 检查文件大小
  • S3Client.stat: 获取文件信息
  • S3Client.delete: 删除文件

错误处理

bun s3 API操作时,可能会抛出错误,这个错误会带有一个错误编码,常见的错误和代码包括:

  • ERR_S3_MISSING_CREDENTIALS,凭证错误
  • ERR_S3_INVALID_METHOD, 方法错误
  • ERR_S3_INVALID_PATH,路径错误
  • ERR_S3_INVALID_ENDPOINT,端点错误
  • ERR_S3_INVALID_SIGNATURE,签名错误
  • ERR_S3_INVALID_SESSION_TOKEN,会话错误

开发者应该熟悉这些错误信息,理解其发生的原因,并且能够快速的调整代码并且排除这些错误。

S3Client静态类

bun提供了一个静态类S3Client,和相关的静态方法,在一些情况下可以简化代码和操作:

js 复制代码
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// Write string
await S3Client.write("my-file.txt", "Hello World");

// Write JSON with type
await S3Client.write("data.json", JSON.stringify({ hello: "world" }), {
  ...credentials,
  type: "application/json",
});

// Write from fetch
const res = await fetch("https://example.com/data");
await S3Client.write("data.bin", res, credentials);

// Write with ACL
await S3Client.write("public.html", html, {
  ...credentials,
  acl: "public-read",
  type: "text/html",
});

静态方法虽然操作方便,但笔者怀疑,由于无法复用客户端示例,每次都需要进行配置和验证,在大量操作情况下,性能应当不如客户端实例那么好,所以,可能更适合于一些临时性的操作。

S3协议访问

在系列文章前面的内容中,我们应该已经了解到,bun支持标准的Web客户端方法fetch来发起http请求。实际上, bun中也可以支持fetch方式来直接访问S3对象。方法是在URL中,使用"S3://"协议,如下面代码所示:

js 复制代码
// fetch方法+s3配置
const response = await fetch("s3://my-bucket/my-file.txt", {
  s3: {
    accessKeyId: "your-access-key",
    secretAccessKey: "your-secret-key",
    endpoint: "https://s3.us-east-1.amazonaws.com",
  },
  headers: {
    "range": "bytes=0-1023",
  },
});


const response = await fetch("s3://my-bucket/my-file.txt");

// bun.file 也支持s3协议
const file = Bun.file("s3://my-bucket/my-file.txt");

这样的协议支持和使用方式,显然让开发工作更加简单灵活。

性能

按照bun技术文档的说法,相比nodejs的s3 npm实现,bun s3可以提供高达5倍左右的性能。

bun.sh/bun-s3-node...

小结

本文探讨了bun内置的S3对象存储和其兼容系统的操作支持,包括一般形式和流程,文件对象模式,数据上传下载,文件管理,预签名地址,错误处理等方面的内容。

笔者认为,对S3和其兼容系统的内置支持,模式和性能都比较成熟完善,是bun的一个非常重要和有特点的功能特性,大大的简化了Web应用程序对于文件的处理和操作。

相关推荐
一只叫煤球的猫14 分钟前
手撕@Transactional!别再问事务为什么失效了!Spring-tx源码全面解析!
后端·spring·面试
旷世奇才李先生28 分钟前
Ruby 安装使用教程
开发语言·后端·ruby
失落的多巴胺1 小时前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear1 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息1 小时前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
样子20181 小时前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿1 小时前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘1 小时前
vue文本插值
javascript·vue.js·ecmascript
海的诗篇_3 小时前
前端开发面试题总结-原生小程序部分
前端·javascript·面试·小程序·vue·html
沃夫上校3 小时前
Feign调Post接口异常:Incomplete output stream
java·后端·微服务