4.2 视频入 COS:存储与转码

录制文件有了,下一个问题是放哪。我们最初用网盘,每周人工整理,文件一多就乱:找不到、误删除、分享链接过期。直到有一次家长投诉说回放看不了,我一查,网盘链接被运营随手撤了。那次之后我下定决心把回放迁到对象存储 COS 上。

对象存储和网盘的本质区别在于:它不是用来给人点点看的,而是给程序调用的——有稳定的 API、有目录规范、能配生命周期、能对接 CDN。关于 COS 的基础概念和开通步骤,官方的对象存储快速入门写得很清楚,这里只讲我在课堂项目里的实际设计。

顺带说一句为什么不用自己服务器存:一台云服务器的硬盘容量固定,扩容要停机或换盘,视频文件的增速却是不均匀的——开学季一周几百 GB,暑假又基本不动。对象存储按量计费、容量无限,省去了容量规划这件最烦人的事。服务器我们只留很小一块系统盘,存大文件一律进 COS,这个分工后来再没变过。

存储目录怎么设计

这是整个回放系统最重要的设计,没有之一。目录结构一旦定下来并跑出了数据,再改就是伤筋动骨。我的原则是:按业务实体分层,用存储桶做环境隔离。

层级 含义 命名规则
课程 一门课(如 Python 入门) course 目录加课程编号
期数 第几期开课 term 目录加期数编号
课节 具体一节课 lesson 目录加课节编号
日期 防同课节重录互相覆盖 8 位日期加时间戳

最终路径长这样:record/course/1001/term/3/lesson/42/20260301/origin.mp4。每一层都有意义:课程和期数方便按学期统计归档,课节对应回放列表,日期层是血泪经验——曾经同一节课补录了一次,新文件把旧文件直接覆盖,学生看到的是半截视频。加上日期层后,同一节课可以并存多个版本,回放默认指向最新版,旧版保留 7 天兜底。

有人会问日期层放在课节下面,层级是不是太深了?深一点的代价是路径长、排障时多敲几个目录,但换来的是版本管理和统计的确定性,这一层省不得。另外所有目录名只用小写字母、数字和连字符,不用中文和空格——中文路径在某些播放器和转码工具里出过莫名其妙的编码问题,排查一次就够你记住的。

迁移老数据的教训

迁到 COS 之前,网盘里已经攒了两个学期上百节课的录像。迁移时图快,直接按课程名建了几个大目录塞进去,结果回放列表入库时才发现问题:同一门课有两个名称(Python 提高班、Python 进阶),同一节课有重复视频,命名风格五花八门。最后花了一个多星期人工清洗,一边洗一边后悔没早定规范。正确做法应该是先定规范、写脚本批量重命名校验、再分批迁入,每迁一批就抽查一次,发现脏数据立刻停下处理,而不是一口气全倒进去再慢慢补。这段经历在《腾讯云实战手记:从云服务器到短信推送的全栈上线》里也有类似记录:数据迁移永远比写新代码慢,因为脏数据的问题只在迁移时才暴露,而且是成批地暴露。

存储桶规划

除了目录分层,存储桶本身也要规划。我们一共用三个桶:正式回放桶、录制中转桶、待处理桶。中转桶接云端录制落盘,服务端校验通过后转存到正式桶,转码期间文件放待处理桶,失败可重试。三个桶权限不同:正式桶给 CDN 回源专用,中转桶只开放给录制服务写入。这样即便某个环节的密钥泄露,影响面也被限制在一个桶里。测试环境另有独立的一套桶,和线上完全隔离,跑新流程先在测试桶里验证,我不止一次在测试桶里发现转码脚本的低级 bug,没让它流进线上。

两种上传方式

录制文件进 COS 有两条路:服务端转存和客户端直写。

方式 流程 适合场景
服务端转存 云端录制先把文件落到中转桶,服务端再复制到正式目录 需要二次校验和转码调度的正式环境
客户端直写 本地录制后由上传程序直接写 COS 公开课等允许人工后处理的场景

我们用的是服务端转存。转存时服务端做两道校验:文件大小是否为 0、实际时长和排课时长是否匹配,校验通过才登记到数据库。这样即便录制异常,数据库里也不会出现指向坏文件的记录,学生点进去永远是可播的文件。

客户端直写只在公开课场景用,用的是临时密钥加范围限定的策略——上传凭证只能写指定目录,有效期 15 分钟,不暴露长期密钥。为什么不统一用直写?因为服务端转存那两道校验是质量门,公开课对质量要求低、人工会把关,所以直写图快;正式课容不得坏文件混进数据库。两种方式各自服务最合适的场景,没必要一刀切。

大文件上传还有个工程细节值得说。本地录制的一个小时视频动辄一两个 GB,网络一抖就整段重传,体验极差。我们的上传程序做了分片上传:文件切成固定大小的块逐个传,某片失败只重试那一片,全部完成后服务端再合并。上传进度也能实时反馈给用户。这套逻辑在公开课场景尤其重要,老师家里网络不稳,整段重传能让人崩溃。

转码策略

学生看回放的环境五花八门:家里宽带、地铁上 4G、老旧 iPad。一份原始录制应付不了所有场景,必须转码出多个档位。

清晰度档位 分辨率 播放场景
原画 与录制分辨率一致 WiFi 下学习、电脑端
高清 720P 主流移动网络
标清 480P 弱网环境、流量敏感用户

转码按分钟数计费,所以不要无脑全转。我们的做法是原画必出,高清和标清只对正课转,公开课只转标清。播放器默认从高清起播,根据用户网络自适应降档。转码用的是通用的音视频处理能力,参数没什么神秘,重点是档位规划要和课型、用户网络分布匹配。一期课如果大部分学生都在手机上看,高清档就是主消费档,标清是保底;反过来如果都在电脑上看,原画的使用率会很高。这个分布每隔一阵子可以回头看一次播放日志,按实际比例调整转码策略。

转码完成还不算完:转码服务会回调通知服务端,服务端拿到新文件地址后才更新回放列表里的可用清晰度,学生看到的档位切换永远是真实的,而不是一个点了没反应的空选项。早期我们把转码和上传串成同步流程,高峰期一排课连着录完,转码队列堵住,学生课后两小时还看不到回放。后来改成异步:文件落盘即入队,转码状态实时同步到前端,列表里显示转码中、完成即更新,投诉降到零。夜里还会跑批补转标清档,白天只保证正课高清优先。

生命周期管理

存文件最怕两件事:一是旧文件越堆越多,存储费悄悄涨上去;二是该留的备份被误清。COS 的生命周期规则可以自动处理这两件事,核心是按文件前缀配置规则,让不同价值的文件走不同生命周期。

文件类型 热存期 归档 删除
正课原画 90 天 90 天后转归档存储 3 年后删除
正课转码档 180 天 不归档 180 天后删除
答疑课回放 30 天 不归档 30 天后删除
公开课回放 长期热存 1 年后归档 不删除

设计原则是按回放价值排期:收费课的回放是资产,长期保留但归档降费;引流课的回放是耗材,用完就清。归档存储的价格只有热存的零头,但取回要等几分钟到几小时,所以归档前要确认真的没人会临时点播——旧期回放归档前我们会在学生群公告一次,只说转为长期保存、不影响已购学生学习,避免引起家长恐慌。

上线第一年我只盯存储总量这一条曲线,月底环比超过 20% 就回头查规则。有一年暑假前总量突然连涨三周,排查发现是一期结课的课没配清理规则,视频全留在热存层,调整后曲线立刻回落。这条曲线后来成了我们月度经营会的固定看板,因为它直接反映内容资产的真实增长速度。关于存储类成本的长期观察,可以看我记录在《腾讯云实战手记:从云服务器到短信推送的全栈上线》里的月度复盘章节。

另外一条经验:异地备份只对核心资产做。正课原画我会再冷备一份到家里的 NAS,不用云上的多区复制——一个小机构的核心资产就那么几 TB,没必要为低频风险付长期溢价。NAS 相关的选型和搭建,可以参考《NAS 家庭服务器指南:群晖、软路由与内网穿透》。冷备的操作是季度一次,写进运营日历,用脚本对比 COS 和 NAS 的文件清单,少一个都会报警。有家长质疑过我们回放会不会哪天突然没了,我把备份和生命周期规则截图发给他们看,质疑立刻消失。对收费课来说,回放承诺本身就是交付的一部分,备份不止是技术问题,也是信任问题。

给小团队的三条建议

整节写到最后,如果只记三条经验,我会留这三条。第一,目录规范先于一切动手,网上盘还是上 COS 都一样,烂目录结构迁到云上只会变成云上的烂目录。第二,校验和质量门不能省,文件系统不保证写着写着一定成功,转存校验是最便宜的质量保险。第三,生命周期规则上线第一天就配上,不要等存储费账单变难看了才补,那时多半已经浪费了几个月。这三条都不难,难的是在兵荒马乱的项目早期愿意停下来先把规矩立好。

延伸阅读