Skip to content

Commit bd64d15

Browse files
authored
feat: enhance S3 object storage documentation with detailed upload mechanisms and benefits (#303)
(cherry picked from commit 5b2c3b0)
1 parent 6c7f802 commit bd64d15

2 files changed

Lines changed: 319 additions & 98 deletions

File tree

  • docs/blog/golang/architectural
  • i18n/en/docusaurus-plugin-content-docs-blog/current/golang/architectural

docs/blog/golang/architectural/3.md

Lines changed: 140 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,121 +4,212 @@ hide_title: true
44
sidebar_position: 3
55
---
66

7+
## 1. 背景
78

8-
# 基于 Go 与 S3 的高效文件上传方案 —— 支持断点续传与秒传
9+
在即时通信系统里,图片、语音、视频、文件、视频封面等内容都属于对象数据。它们和普通文本消息不同,通常体积更大、上传时间更长、受网络波动影响更明显。如果所有文件都经由业务 API 服务中转,服务端会同时承担业务处理和大流量文件搬运,容易影响消息、会话、群组等核心链路的稳定性。
910

10-
在现代 App 与 Web 应用中,文件上传是常见且关键的功能。为应对传输中可能出现的中断、重复上传等问题,我们设计并实现了一套基于 **Go 服务端 + S3 存储** 的文件上传机制。该方案支持**断点续传****秒传** ,并具备良好的**可扩展性****极简客户端 SDK** ,实现客户端与 S3 直连,**数据流不经 API 服务中转**
11+
OpenIM 的 S3 对象存储能力,就是为了解决这个问题:让业务服务只负责鉴权、签名、元数据和访问控制,让文件数据直接进入对象存储。这样既能提高上传效率,也能降低 API 服务压力
1112

13+
## 2. 核心定位
1214

13-
## ✨ 设计目标
15+
OpenIM 的 S3 方案不是简单地把文件上传到 MinIO,而是一套面向 IM 场景的大对象管理能力。它覆盖上传、下载、访问、续传、去重、清理、迁移等环节。
1416

17+
整体上可以理解为两层:
1518

16-
- ✅ 极简 SDK:客户端无需存储系统适配逻辑
19+
- 服务端负责控制面:鉴权、生成临时凭证、登记对象信息、生成统一访问地址、执行过期清理。
20+
- SDK 负责客户端上传体验:读取文件、计算哈希、分片上传、断点续传、并发控制、进度回调、取消上传。
1721

18-
- ✅ 降低服务器负载:文件流量不经由 API 服务
22+
这种拆分让“业务控制”和“文件流量”分离。服务端不再搬运大文件,客户端拿到授权后直接上传到对象存储。
1923

20-
- ✅ 支持断点续传与秒传,提升用户体验
24+
## 3. 整体上传体验
2125

22-
- ✅ 兼容任意 S3 接口,易于接入多种对象存储
26+
用户发送图片、视频或文件时,SDK 会先在本地分析文件,判断文件大小和类型,并计算文件内容特征。然后 SDK 向服务端申请一次上传授权。服务端校验用户身份和对象归属后,返回临时上传凭证。之后文件内容由客户端直接传到对象存储,不再经过 OpenIM API 服务中转。
2327

24-
- ✅ 可拓展支持非 S3 的 HTTP PUT 分片上传
28+
上传完成后,服务端会登记这份对象和业务对象名之间的关系,并返回一个 OpenIM 层面的访问地址。业务消息里保存的是这个稳定地址,而不是某个存储厂商暴露的原始地址。后续访问时,OpenIM 再根据对象元数据生成临时访问链接并重定向到真实对象。
2529

30+
这个链路的好处是:
2631

32+
- 上传大文件时不会挤占业务 API 带宽。
33+
- 下载地址可以统一由 OpenIM 管理。
34+
- 底层存储后端变化时,业务层地址可以保持稳定。
35+
- 私有桶也可以通过临时授权安全访问。
2736

28-
## 📦 上传流程
37+
## 4. 多存储后端兼容
2938

39+
OpenIM 支持多种 S3 或 S3 兼容对象存储,包括 MinIO、腾讯云 COS、阿里云 OSS、七牛 Kodo、AWS S3 等。业务层不需要感知具体厂商差异,只需要使用 OpenIM 提供的统一对象能力。
3040

31-
上传流程共分为五步
41+
这带来的优势是部署选择更灵活
3242

43+
- 私有化部署可以使用 MinIO。
44+
- 公有云部署可以接入云厂商对象存储。
45+
- 企业后续更换存储厂商时,业务逻辑不需要整体重写。
46+
- 存储访问域名、内外网地址、权限策略可以按部署环境调整。
3347

34-
### 1. 获取分片大小
48+
## 5. 客户端直传
3549

36-
接口:`GET /object/part_limit`
50+
传统文件上传常见做法是客户端先把文件传给业务服务,业务服务再转存到对象存储。这种方式实现简单,但在大文件和高并发场景下成本很高。
3751

38-
客户端首次上传前,请求服务端获取推荐的分片大小(如 5MB),并进行缓存
52+
OpenIM 采用客户端直传:服务端只签发短期授权,文件本体由客户端直接上传到对象存储。这样 API 服务只处理轻量请求,不承担大文件传输
3953

54+
客户端直传的优势包括:
4055

41-
### 2. 计算文件哈希
56+
- 降低 API 服务带宽和连接压力。
57+
- 大文件上传更稳定,不影响消息收发等核心接口。
58+
- 对象存储天然适合承载大对象和高并发下载。
59+
- 文件上传链路更短,整体吞吐能力更好。
4260

43-
客户端将文件按分片大小切分,并计算每个分片的哈希值。然后将所有分片哈希拼接,最终计算出文件整体哈希(`file_hash`),用于秒传判断。
61+
## 6. 分片上传
4462

63+
对于较大的图片、视频和文件,SDK 会使用分片上传。文件被拆成多个小块,每个分片独立上传,最后由对象存储合并成完整对象。
4564

46-
### 3. 初始化上传
65+
分片上传带来的体验提升很明显:
4766

48-
接口:`POST /object/initiate_multipart_upload`
67+
- 大文件不需要一次性完整上传。
68+
- 某个分片失败时,只需要重传失败部分。
69+
- 可以通过受控并发提升上传速度。
70+
- 上传过程更容易展示细粒度进度。
4971

50-
客户端提交文件整体信息(如 hash、文件名、大小):
72+
SDK 会根据文件大小和服务端限制自动选择合适的分片大小,上层业务不需要手动处理这些细节。
5173

74+
## 7. 秒传
5275

53-
- 若服务器发现该文件已存在(通过 `file_hash` 判断),则返回秒传成功的下载地址;
76+
OpenIM 支持基于文件内容的秒传。SDK 会计算文件内容特征,服务端可以判断相同内容是否已经存在。如果对象存储里已经有这份文件,就不再重复上传文件内容,而是直接登记一次新的业务引用并返回访问地址。
5477

55-
- 若不存在,则返回每个分片的上传签名信息,包括
78+
秒传的价值主要体现在重复发送场景
5679

80+
- 同一用户重复发送相同文件时,可以瞬间完成。
81+
- 不同消息引用同一份真实对象,减少重复存储。
82+
- 降低网络传输成本和对象存储写入成本。
83+
- 弱网环境下减少不必要的重传。
5784

58-
- 分片上传 URL
85+
秒传不是按文件名判断,而是按内容特征判断,因此更适合 IM 文件场景。
5986

60-
- 必需的 Header(如 `Content-Type`, `Authorization`
87+
## 8. 断点续传
6188

62-
- `PartNumber` 等标识
89+
SDK 会在本地记录上传进度。上传过程中,如果网络中断、应用退出、进程重启,已经成功上传的分片不会丢失。用户再次上传同一个文件时,SDK 可以从本地恢复状态,只补传尚未完成的分片。
6390

91+
断点续传能显著改善弱网和移动端体验:
6492

65-
### 4. 上传分片(直传 S3)
93+
- 大文件失败后不用从头开始。
94+
- 切后台、断网、重启后仍能继续。
95+
- 每个已成功的分片都能沉淀为有效进度。
96+
- 用户重试成本更低,失败体验更可控。
6697

98+
这也是 OpenIM S3 方案相比普通表单上传的重要优势之一。
6799

68-
客户端使用返回的签名信息,将每个分片通过 HTTP PUT 直接上传至 S3,对应:
100+
## 9. 并发与内存控制
69101

102+
SDK 支持分片并发上传,但不会无节制地并发。上传时会根据分片大小和内存预算动态调整并发数量,避免为了追求速度导致客户端内存过高。
70103

71-
- 上传过程中记录每一分片的 ETag 与 PartNumber
104+
当分片较小时,SDK 可以并发上传多个分片;当分片较大时,SDK 会降低并发;如果分片过大,还会退回更稳妥的流式上传方式。
72105

73-
- SDK 持久化这些信息,实现断点续传
106+
这种策略在移动端、桌面端、Web/WASM 等环境中都更稳。它兼顾了上传速度、内存占用和设备稳定性。
74107

108+
## 10. 进度反馈与取消上传
75109

110+
SDK 会把上传过程拆成多个可感知阶段,例如打开文件、计算哈希、获得上传会话、上传分片、完成合并等。上层 UI 可以据此展示更准确的进度,而不是只有一个模糊的百分比。
76111

77-
> ⚠️ 文件内容始终不经由服务端,仅签名和元数据走 API
112+
对于大文件来说,这一点很重要。用户可以清楚知道当前是在本地计算、网络上传,还是等待服务端完成合并
78113

114+
SDK 也支持取消上传。业务侧可以为上传任务绑定取消标识,用户点击取消后,SDK 会中止对应上传任务,避免继续消耗网络和设备资源。
79115

80-
### 5. 完成上传
116+
## 11. 统一访问地址
81117

82-
接口:`POST /object/complete_multipart_upload`
118+
OpenIM 上传完成后返回的是 OpenIM 统一访问地址,而不是直接暴露对象存储原始地址。访问文件时,OpenIM 会根据对象元数据生成临时访问链接,再跳转到真实存储地址。
83119

84-
客户端上传所有分片成功后,调用此接口提交分片信息列表。
120+
这样设计有几个好处:
85121

122+
- 消息里保存的文件地址更稳定。
123+
- 底层存储迁移不会直接影响历史消息。
124+
- 私有对象也可以通过临时授权安全访问。
125+
- 可以统一处理文件名、内容类型、图片缩略图等访问策略。
86126

87-
- 服务端校验每个分片哈希及整体 hash
127+
对于图片类对象,访问层还可以支持缩略图、格式转换、宽高裁剪等派生能力,从而提升聊天图片加载效率。
88128

89-
- 校验通过后,发起 S3 的合并操作
129+
## 12. 权限与隔离
90130

91-
- 最终返回文件的访问地址(可跳转到签名 S3 URL)
131+
普通用户上传对象时,对象会归入当前用户自己的命名空间。这样可以避免用户伪造或覆盖其他用户的文件对象。服务端在生成上传授权前会进行身份校验和对象归属校验,确保上传行为受控。
92132

133+
对象存储的长期密钥不会下发给客户端。客户端拿到的只是短期上传或访问凭证,即使链接泄露,风险也会被限制在较短时间和特定对象范围内。
93134

94-
## ⬇️ 下载流程
135+
这种方式兼顾了直传效率和安全边界。
95136

137+
## 13. 元数据治理
96138

97-
客户端下载时,从服务端获取临时签名下载地址(S3 Pre-signed URL),可设置权限与有效期控制。该地址可通过 API 统一跳转实现权限校验
139+
对象存储只负责保存文件内容,但 IM 系统还需要知道这份文件属于哪个用户、哪个业务类型、使用哪个存储后端、什么时候创建、是否可以清理等信息
98140

141+
OpenIM 会为每个业务对象登记元数据。元数据把“业务对象名”和“真实存储对象”分离开来。多个业务对象可以引用同一个真实对象,这也是秒传和去重能够成立的基础。
99142

143+
元数据治理带来的能力包括:
100144

101-
## ✅ 特性一览
145+
- 统一生成访问地址。
146+
- 支持内容去重和秒传。
147+
- 支持按业务类型做生命周期清理。
148+
- 支持统计真实对象是否仍被引用。
149+
- 支持未来迁移底层存储。
102150

103-
| 特性 | 说明 |
104-
| --- | --- |
105-
| 秒传支持 | 基于文件 hash 实现上传秒跳过 |
106-
| 断点续传 | 支持断线、异常退出后继续上传 |
107-
| 极简 SDK | 客户端无需签名逻辑,仅执行 PUT |
108-
| 存储兼容性强 | 支持任意兼容 HTTP PUT 的对象存储 |
109-
| 高可拓展性 | 可快速接入其他上传协议和存储系统 |
151+
## 14. 生命周期清理
110152

153+
IM 系统里的文件对象并不都需要永久保存。比如图片、语音、视频、视频封面等消息对象,可以根据业务策略设置保留时间。
111154

155+
OpenIM 支持按业务分类清理过期对象。清理时不会简单粗暴地删除真实文件,而是先删除过期业务引用,再判断真实对象是否仍被其他业务对象引用。只有没有任何引用时,才会删除对象存储里的真实文件。
112156

113-
## ⚙️ 可扩展性设计
157+
这种引用计数式清理可以避免误删。它和秒传能力天然配套:同一份真实文件可能被多个消息引用,只有所有引用都过期后,真实对象才应该被删除。
114158

159+
## 15. FormData 小程序兼容通道
115160

116-
该方案以“服务端统一生成签名 + 客户端通用 PUT 上传”为核心架构,只要目标存储系统支持分片上传并接受带签名的 PUT 请求,即可无缝接入:
161+
除了完整的 SDK 分片上传链路,OpenIM 还保留了 FormData 表单直传能力。这条链路主要用于小程序 JSSDK,用来适配小程序平台的文件上传能力和调用限制。
117162

163+
需要特别说明的是,Go 语言提供的 SDK 不会调用 FormData。Go SDK 使用的是完整的分片上传链路。
118164

119-
- 支持 Amazon S3、MinIO、阿里云 OSS、腾讯云 COS 等
165+
FormData 是兼容通道,不具备完整上传链路的高级能力。它不支持:
120166

121-
- 也可扩展支持其它上传服务
167+
- 分片上传。
168+
- 秒传。
169+
- 本地断点续传。
170+
- 分片并发。
171+
- 分片级校验和分片级进度回调。
122172

173+
因此,普通 App、桌面端、Go SDK、Web/WASM 端应优先使用完整的 SDK 上传能力;小程序 JSSDK 在平台限制下可以使用 FormData 作为简化直传方案。
123174

124-
客户端代码不变,服务端扩展签名生成逻辑即可。
175+
## 16. 存储迁移能力
176+
177+
由于 OpenIM 在元数据中记录了对象所属的存储后端,系统具备从一个对象存储迁移到另一个对象存储的基础能力。例如从自建 MinIO 迁移到云厂商对象存储,或者在不同云厂商之间切换。
178+
179+
迁移时,系统可以读取旧存储中的真实对象,写入新存储,并更新元数据中的存储后端信息。业务消息里保存的是 OpenIM 统一访问地址,因此迁移后历史消息仍然可以通过原有方式访问。
180+
181+
这降低了长期运维风险,也减少了被单一存储厂商或部署形态绑定的成本。
182+
183+
## 17. 相比传统上传方式的优势
184+
185+
OpenIM S3 方案的优势可以归纳为五类。
186+
187+
性能方面,文件流量直达对象存储,API 服务只处理控制请求,大文件上传不会挤占核心业务接口。
188+
189+
稳定性方面,分片上传、断点续传、分片校验、取消上传和受控并发共同提升了弱网环境下的成功率。
190+
191+
成本方面,秒传和去重减少重复上传与重复存储,生命周期清理减少无效对象长期占用空间。
192+
193+
安全方面,客户端只拿短期授权,不接触对象存储长期密钥;文件访问也可以通过临时链接进行控制。
194+
195+
演进方面,统一对象地址和元数据治理让底层存储可以迁移或替换,业务层不需要感知具体厂商变化。
196+
197+
## 18. 适用场景
198+
199+
OpenIM S3 对象存储适合以下场景:
200+
201+
- 聊天图片、原图、缩略图。
202+
- 语音消息。
203+
- 视频消息和视频封面。
204+
- 普通文件消息。
205+
- ASR 等需要临时对象上传的能力。
206+
- 私有化部署中的文件存储。
207+
- 大文件、弱网、移动端上传场景。
208+
209+
如果业务只需要小程序端简单表单上传,可以使用 FormData 兼容通道;如果需要秒传、分片、断点续传和更完整的上传体验,应使用 SDK 的完整上传能力。
210+
211+
## 19. 总结
212+
213+
OpenIM 的 S3 对象存储能力,是一套面向 IM 大对象场景的文件基础设施。它通过客户端直传降低服务端压力,通过分片和断点续传提升弱网体验,通过秒传和生命周期清理降低成本,通过统一访问地址和元数据治理提升长期可维护性。
214+
215+
它的核心价值,是把文件上传从“业务 API 的沉重负担”变成“可授权、可续传、可去重、可治理、可迁移的对象服务能力”。对于企业级 IM,这种设计能同时提升性能、稳定性、安全性、成本控制和后续演进能力。

0 commit comments

Comments
 (0)