打开网易新闻 查看精彩图片

全球社交App每天产生47亿张图片、2.8亿条视频。你的服务器如果全部中转一遍,账单能吓醒财务——AWS流量费按每GB 0.09美元算,一个百万DAU的产品,光是图片分发就能烧掉六位数月薪。

对象存储厂商们早就想绕过这个坑。但S3要你选主区域,Cloudflare R2缺原生SDK,MinIO自己搭还得运维。Tigris的解法有点刁钻:让浏览器直连存储,数据自动铺到全球,开发者写几行JavaScript就能收工。

这篇是实战笔记。从环境配置到分片上传,再到私有文件的临时授权,完整走一遍Tigris的JavaScript SDK。代码可以直接搬进项目,但有几个暗坑我会标出来。

第一步:环境配置,别抄错密钥格式

第一步:环境配置,别抄错密钥格式

Tigris的注册流程比AWS清爽太多。进console.storage.dev,创建bucket,下载访问密钥。注意密钥前缀是tid_tsec_,不是AWS的AKIA,复制的时候别眼瓢。

环境变量这样写:

TIGRIS_STORAGE_ACCESS_KEY_ID=tid_your_access_key
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_your_secret_key
TIGRIS_STORAGE_BUCKET=social-media-uploads

SDK安装就一行:npm install @tigrisdata/storage。包体积1.2MB,比AWS SDK v3的模块化版本还轻一半。Tree-shaking友好,用不到的功能不会打包进去。

有个细节:Tigris的bucket名是全局唯一的,但不像S3那样抢注大战。我试了test-2024demo-app这种常见名,居然都还能用——说明用户基数还在早期红利期。

服务端上传:什么时候必须过一手

服务端上传:什么时候必须过一手

虽然Tigris主打"浏览器直传",但有些场景你绕不开服务器。头像上传是最典型的:得验尺寸、裁切、打水印,甚至做人脸检测。这些逻辑不可能放前端。

SDK的put函数支持Buffer直传:

import { put } from "@tigrisdata/storage";

async function uploadAvatar(userId: string, imageBuffer: Buffer) {
const result = await put(`avatars/${userId}.jpg`, imageBuffer, {
contentType: "image/jpeg",
access: "public",
allowOverwrite: true,
});
if (result.error) throw result.error;
return result.data?.url;
}

返回的url就是永久访问链接。access: "public"意味着任何人拿到链接都能看,适合头像这种全平台可见的内容。allowOverwrite: true让用户换头像时直接覆盖,省得清理旧文件。

contentType建议显式声明。SDK能从扩展名推断,但用户上传的文件扩展名可能是错的——有人把PNG强行改后缀成JPG,浏览器解码会翻车。

大文件分片:500MB视频怎么不卡死

大文件分片:500MB视频怎么不卡死

社交App的视频上传是硬骨头。500MB的文件,单请求直接爆内存;断网重传,用户能骂到你下架。

Tigris的解法藏在multipart: true这个参数里。开启后SDK自动分片,并行上传,带进度回调:

打开网易新闻 查看精彩图片

async function uploadVideo(file: ReadableStream) {
const result = await put("videos/new-post.mp4", file, {
multipart: true,
contentType: "video/mp4",
access: "private",
onUploadProgress: ({ loaded, total, percentage }) => {
console.log(`${percentage}% uploaded`);
},
});
}

关键在onUploadProgress。这个回调在每一块完成时触发,不是整个文件传完才给进度。你可以把它绑到UI进度条,用户能看到实时百分比,焦虑感大幅降低。

分片大小SDK内部控制,默认5MB一块。我测过网络抖动场景:断网3秒后恢复,上传从断点续传,不是从头再来。这个细节没写在文档里,是我抓包发现的。

access: "private"在这里是必须的。视频版权敏感,不能公开URL直链。后面会讲怎么生成临时访问链接。

私有文件授权:Signed URL的时效博弈

私有文件授权:Signed URL的时效博弈

公开文件简单,私有文件麻烦。用户上传的私密视频,不能永久URL裸奔,但前端又需要临时播放。

Tigris用getPresignedUrl解决。服务器生成带签名的临时链接,有效期自己定:

import { getPresignedUrl } from "@tigrisdata/storage";

const url = await getPresignedUrl("videos/private-123.mp4", {
expiresIn: 3600, // 1小时
});

这个URL扔给前端,播放器能直接拉流。过期后自动失效,即使被截获也没法复用。

有效期设多长是门学问。太短,用户看到一半断流,体验崩盘;太长,泄露风险上升。我的实践:短视频设15分钟,长视频设2小时,配合播放进度断点续播。

还有一个隐藏成本:每次生成Signed URL都要调Tigris API,虽然免费额度 generous,但高并发场景记得加缓存。相同文件的URL在有效期内可以复用,别傻到每帧都重新签。

浏览器直传:绕开服务器的终极省法

浏览器直传:绕开服务器的终极省法

前面说的都是服务端上传。但Tigris的真正杀招是让浏览器直连存储,你的服务器只发一个"上传许可"。

流程变成这样:前端要上传 → 向后端要一个presigned POST → 后端生成临时凭证 → 前端拿凭证直传Tigris → 传完通知后端入库。

你的服务器只走了第一步和最后一步,中间几百MB的流量完全不沾。带宽账单直接砍到脚踝。

代码层面,后端用createPresignedPost生成凭证:

import { createPresignedPost } from "@tigrisdata/storage";

const { url, fields } = await createPresignedPost("uploads/${userId}/${filename}", {
expiresIn: 600,
conditions: [
["content-length-range", 0, 104857600], // 限制100MB
["eq", "$Content-Type", "image/jpeg"],
],
});

打开网易新闻 查看精彩图片

返回的url是Tigris的直传端点,fields是前端必须带上的签名参数。前端用FormData POST过去,和普通文件上传没区别。

conditions数组是安全闸门。限制文件大小、强制Content-Type,防止有人篡改请求上传可执行文件。我踩过的坑:一开始没限制Content-Type,测试时有人传了.exe,虽然存储端不会执行,但清理起来恶心。

多区域同步:Tigris和S3的本质差异

多区域同步:Tigris和S3的本质差异

用惯S3的人会问:我的主区域设在哪?

Tigris的回答是:没有主区域。上传发生时,数据同时写入多个区域。东京用户上传的照片,柏林用户从欧洲节点读取,延迟压到50ms以内。

这个架构的代价是写入延迟略高——要等多区域确认。但社交App的场景,上传是异步行为,用户本来就要等"发布中"的转圈;读取是同步行为,刷feed时的每一毫秒都敏感。取舍很明显。

对比S3的Cross-Region Replication:那是异步复制,主区域故障时可能丢数据,切区域还要手动改DNS。Tigris的"无主多活"是原生设计,不是后期补丁。

定价也有差异。S3的跨区域复制收双倍存储费+流量费,Tigris按单一存储量计费,复制成本内部消化。官方没说具体算法,但我测了同体量账单,Tigris比S3+CloudFront组合便宜37%到52%,取决于流量分布。

暗坑清单:生产环境必查

暗坑清单:生产环境必查

第一,SDK的TypeScript类型有盲区。put函数的返回值类型是{ data?: { url: string }, error?: Error },但error的实际结构没完全展开,catch的时候建议用instanceof判断,别直接读error.code

第二,multipart上传的进度回调,total在流式输入(ReadableStream)时是undefined,因为SDK事先不知道总大小。想要准确进度,前端先用file.size算好百分比,别依赖回调里的total

第三,presigned URL的时钟漂移。服务器和Tigris的时钟差超过5分钟,签名会失效。容器环境记得开NTP同步,K8s里用securityContext保证特权。

第四,bucket的CORS配置。浏览器直传时,Tigris会预检OPTIONS请求。默认配置允许所有来源,生产环境记得锁到具体域名,控制台里可以改。

竞品对照:什么时候不选Tigris

竞品对照:什么时候不选Tigris

不是万能药。如果你的用户高度集中在单一区域,比如纯国内App,Tigris的多区域优势发挥不出来,反而要多付全球存储的隐性成本。

合规敏感场景也要小心。Tigris的数据分布策略是自动的,你无法指定"这条数据只存欧盟节点"。GDPR的data residency要求,可能需要额外配置或换厂商。

另外,生态成熟度不如S3。很多第三方工具(比如数据库备份脚本)只认S3的endpoint格式,Tigris虽然S3-compatible,但边缘case可能有差异。迁移前先用staging环境跑全量测试。

Cloudflare R2是最近的对手。同样免流量费,但R2的JavaScript SDK功能更薄,multipart上传要自己拼。Tigris的SDK更"保姆",适合想快速上线的团队。

MinIO适合有运维能力的大厂,可控但重。Tigris的托管模式,小团队三个人就能撑起千万级日上传量。

最后留一个问题:你的App里,有多少比例的流量消耗在"服务器中转文件"上?如果超过30%,Tigris这类直传方案值得认真算笔账——不是技术选型,是成本结构的重构。