这主意是怎么蹦出来的?
去年冬天,我们几个朋友在群里瞎聊,有人发了张十年前的照片,大家瞬间炸了——谁还记得那会儿在干嘛?有人翻出旧手机,有人翻云盘,结果发现:一年到头,真正有记录的瞬间少得可怜。
我们这群人,有做Go后端开发的,有搞iOS的,有当老师的,有开咖啡馆的,那天晚上,一个大胆的想法冒出来:能不能用Go写个东西,让咱们这群人,每天录一段视频,坚持一整年?
第一个问题:谁来存这些视频?
我第一反应是扔云上,但算了一笔账:一个人每天30秒视频,按1080p算大概10MB,10个人就是100MB一天,一年下来 365天 × 100MB ≈ 36.5GB,听起来不大?注意,这是经过压缩的,如果大家随手拍不加处理,轻松翻三倍。

更麻烦的是读写频率——每天都有新视频进来,高峰期在晚上10点到12点,大家集中上传,如果用传统文件服务器,磁盘I/O立刻成瓶颈,我们试过NFS方案,结果某天晚上6个人同时上传,直接把办公室NAS卡死了。
用Go重新设计存储层
Go的并发模型在这时候派上了用场,我们用goroutine处理每一个视频上传任务,核心代码大概长这样:
func handleUpload(w http.ResponseWriter, r *http.Request) {
// 每个上传请求启动一个goroutine
go func() {
// 解析multipart表单
// 按“日期-用户ID”生成文件名
// 写入磁盘或对象存储
// 更新数据库记录
}()
// 立即返回202 Accepted
w.WriteHeader(http.StatusAccepted)
}
有意思的是,Go这里有个“坑”——如果你不给goroutine加超时控制,遇到慢速上传会大量累积,我们后来加了个context.WithTimeout,每个上传限时60秒,超时直接杀掉goroutine,释放资源。
存储方案我们选了 MinIO,一个兼容S3的开源对象存储,纯Go写的,为什么用它?三个原因:
| 原因 | 说明 |
|---|---|
| Go原生支持 | 官方SDK就是Go写的,集成起来几乎零成本 |
| 单机可跑 | 初期就5个人用,犯不着上AWS |
| 桶策略灵活 | 按用户分桶,权限好控制 |
视频质量该压到什么程度?
这是我们吵得最凶的话题,有人坚持要4K无损,有人觉得720p就行,最后妥协出一个方案——不设统一标准,让用户自己选。
但这就带来一个问题:Go怎么处理视频转码?我们调研了几个方案:
- FFmpeg命令行调用:最稳妥,但每次转码要fork子进程,开销大
- Go原生库:比如
gocv,绑定OpenCV,性能好但坑多 - 微服务拆分:把转码拆成独立服务,用消息队列驱动
最后选了第一种,但做了个优化——转码不阻塞上传,用户上传原始视频后,直接存到MinIO的一个“待处理”桶,转码服务异步拉取处理,处理完再挪到“已完成”桶,Go的os/exec包调FFmpeg时,记得限制CPU核数,不然6个任务一起跑能把服务器搞崩。
时间线的设计:比想象中复杂
最让我头疼的不是存储,是时间线的呈现,我们希望用户打开页面,能看到过去365天每天的视频缩略图,按日期排列,空的日期留灰块,你想想,10个人×365天,就是3650个视频格子,如果每次请求都查数据库,数据库会叫苦。
我们用了 Redis做缓存预热,每天凌晨3点,写个定时任务,用Go的time.NewTicker跑:
ticker := time.NewTicker(24 * time.Hour)
for range ticker.C {
// 凌晨3点执行
if time.Now().Hour() == 3 {
go refreshTimeLineCache()
}
}
refreshTimeLineCache里做的事情是:查询所有用户过去365天的上传记录,生成一个 map[string]map[string]bool 的结构(外层Key是日期字符串,内层Key是用户ID),然后序列化成JSON存Redis,这样前端请求时间线时,直接取缓存,O(1)复杂度,加载速度从原来的3秒降到200毫秒。
当然这有个问题——如果有人凌晨3点到4点上传视频,缓存就是脏的,我们的解决方案很朴实:用户上传视频后,主动删除一次对应日期的缓存Key,下次请求时重新生成,这算是个“最终一致性”的偷懒版本。
视频的隐私边界在哪里?
写到这里,你可能要问:一群人每天录视频,隐私怎么办?
确实,我们内部吵过一架,有人录了和老婆吵架的片段想上传,被劝阻了,最后定的规则很简单,但很有效:
- 每段视频上传前必须勾选一个“可查看范围”:仅自己、群组可见、所有人
- Go后端在读取时检查权限,用
jwt中间件加一个中间层
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
// 解析JWT
// 提取用户ID和角色
// 检查是否有权限查看该视频
// 如果通过,调用next.ServeHTTP
})
}
这中间出了个bug——有个用户把视频权限设成“仅自己”,结果时间线页面上他的格子是灰的,其他人看到的是:“张三今天没拍视频”,但实际上他拍了,只是别人看不到,这在群里引发了误解,有人以为他不坚持了,后来我们改成了:不管有没有权限,都显示缩略图,但点进去无权限的人会看到“该视频暂时不可查看”,这个改动虽然小,但让体验好了很多。
365天后,我们得到了什么?
我特意没说最后结果,因为文章还没结束,但可以透露一个细节:第300天的时候,我们中有个人录了一段他父亲生日的视频,他父亲在那之后两个月去世了,那段视频后来变得极其珍贵。
技术层面,Go在这个项目里表现超出预期。内存占用稳定在300MB以内(10个用户同时上传),CPU峰值在转码时冲到80%,但因为用了协程池,平时只有5%左右,唯一的问题是:Go的GC在大量文件操作时会有短暂停顿(STW),大约10-20ms,对视频上传这种场景其实无感,但如果你写的是实时直播系统,这点要留意。
如果你也想做类似的事,我的建议是:别一开始就想着完美,我们开始的代码极其简陋,连错误处理都没做全,但跑着跑着就迭代优化了,费曼说过,“如果你不能简单地解释它,说明你还没真正理解它。”我们的视频项目也一样,真正理解“一群人一年365天视频”这件事,是在第200天以后——那时候我们不再纠结技术细节,而是开始享受每天那30秒的记录。
至于Go语言是不是最好的选择?如果你需要并发处理、文件I/O、轻量级部署,它绝对合格,但如果你更看重快速原型,Python可能更快,选择权在你手里——毕竟,工具是服务于人的,人才是目的。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/qiche/1376.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《一群人一年365天视频,我们用Go语言造了个时间胶囊》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:这主意是怎么蹦出来的?去年冬天,我们几个朋友在群里瞎聊,有人发了张十年前的照片,大家瞬间炸了——谁还记得那会儿在干嘛?有人翻出旧手机...