Devlog#01|上传文件的最佳实践 (自认)

发布于:01/04/2025 15:31:47

Video here https://youtu.be/5yWN7gUaGsQ

你好,你在读这一篇文章的当下你肯定接触过了我们的附件服务, 它掌管了我们上至帖子、消息的图片、视频,下至头像、表情等细枝末节的媒体文件。 这就是 HyperNet.Paperclip —— 回形针,我们的附件服务。

虽说我们本文介绍的是上传文件的最佳实践(自认), 但是上传完文件的配套设施我们还是会一并介绍的,不然上传了文件访问不了跟把文件丢到垃圾桶里面本质上没什么区别。

上传

Solar Network 上的文件可以有两个不同 API,一个是 multipart-form 直接上传,另一个是切片上传。

直接上传

直接上传没什么好讲的,各个 HTTP 框架都有对应获取客户端上传文件的实现, 让我们把篇幅留给重头戏,也是客户端主流使用的 API,切片上传。

切片上传

首先,让我们了解一下切片上传的原理。 客户端首先读取到文件的元数据(大小,文件类型等)发送给服务器, 在服务器创建一个标识符来代表这个文件。之后再按照配置把整个文件切成 n 块, 一块一块的发送给服务器。服务器成功收到所有的数据块之后,把所有的文件块按照 一定的顺序拼接在一起,这样就还原了整个文件。

切片上传的优势有很多,其中一个是在网络不稳定的时候可以进行断点续传, 另一个是因为 Access Token 有效期短,有时候等一个请求整体上传跑完 Access Token 过期了, 服务器直接不留情面的丢回来一个 Unauthorized 401 就好玩了。

上传之后

其实,关于上传文件还有一个更简单粗暴的方法,因为我们使用 S3 当后端存储, 直接可以生成一个预签名的上传 URL 给客户端,让客户端直接往 Bucket 里面丢东西就好了。

但是对于 SN 的场景,我会想要有更多的文件信息,例如图片的 EXIF,长宽高,视频的长度等等。 如果这些东西都要去找 Bucket 读取文件来获得,那请求数早就爆炸了,随之而来的就是钱包爆炸。

所以我们实现了一个 Analyzer 分析仪的功能。 文件上传完一个,文件就会丢给 Analyzer 做分析的功能,对于不同的文件类型,Analyzer 也会做不同的操作。

  • 对于图片,Analzyer 会直接读取 EXIF 数据
  • 对于视频,Analyzer 会找 ffmpeg 读取视频的元数据
  • 对于音频,Analyzer 会找 ffmpeg 读取音频长度(虽然其他的数据也会存起来,但是我不知道它们有什么用)

分析完之后,Paperclip 就会把文件丢给 S3 了,支持,文件上传算是全部完成。

访问

你或许会知道,SN 的附件有个叫 Boost 的功能,开了 Boost 之后,文件会被丢到 更快的 CDN 或者离观众更近的服务器来分发,可以用来提升访问文件的速度。

关于 Boost 怎么匹配的我就不在这里赘述了,想要知道的可以自行阅读源码。 或者未来找机会单独写一篇文章来讲。

无论是否使用 Boost,最终都会匹配到文件数据存储的配置,根据配置,就可以获取到访问 URL。 在这里就是用的预签名 URL 直接跳转到 S3 访问了,因为这样会快一点(如果 S3 速度好的话)

但是因为有的时候文件分析、上传会花比较长的时间,这时候,服务器会直接从本地发送文件 来弥补这一个空窗期。


关于为什么我会认为这个是上传文件的最佳实践呢? 那是因为这是我自己想出来的方法啊

啊,不对,主要是为了降本增效。 当然你可以用一些 S3 提供商给的图像服务来压缩图片、获取元数据,但说白了还是要钱。

俗话说得好,万事加一层,加一层解决不了就再加一层。 这一个额外的抽象层还给我们提供了一定的灵活度, 要是有一天,我们要换到一个更便宜的对象存储提供商,也可以迁移、新增同步进行。

btw 我们目前使用的对象存储提供商是 Cloudflare R2,作为互联网大善人他们也是非常佛心的只收存储和请求费, 流量费是不管的,而且存储费用也是相比 S3 低很多。还有 10G 的免费额度。但是速率就见仁见智了,毕竟 CF 的 CDN 嘛~


HyperNet.Paperclip 跟其他 HyperNet / Solar Network 项目一样 遵循 APGL v3 协议开源,你可以在我们的 GitHub 上看到该项目的源代码。

https://github.com/Solsynth/HyperNet.Paperclip

虽然但是,我觉得你不会想自己来跑一个 Paperclip 来用, 因为我们的「中服务架构」让你需要额外跑一个 Nexus, 一个 Passport, 一个 etcd, 一个 nats, 一个 postgresql 才能让它真正起作用。