EasyDarwin媒体文件管理:自动清理与存储策略
痛点直击:流媒体服务器的存储管理困境
你是否遇到过流媒体服务器因存储耗尽导致服务中断?是否还在手动清理过期视频文件?EasyDarwin作为高性能RTSP流媒体服务器,每天处理大量视频数据,如何高效管理媒体文件生命周期、避免存储溢出成为关键挑战。本文将系统解析EasyDarwin的媒体文件自动清理机制与存储优化策略,帮助你构建稳定可靠的流媒体存储系统。
读完本文你将获得:
- 掌握EasyDarwin双维度自动清理机制的工作原理
- 学会配置文件过期时间与磁盘使用率阈值
- 理解媒体文件存储架构与路径规划
- 优化存储策略的5个实用技巧
- 常见清理问题的排查与解决方案
存储管理核心架构
EasyDarwin采用分层存储架构,将媒体文件生命周期管理分为主动清理和被动清理两大维度,结合文件系统监控与数据库记录,实现完整的存储治理闭环。
核心存储目录结构
EasyDarwin采用双目录结构分离管理原始文件与转码文件:
| 目录类型 | 配置参数 | 功能描述 |
|---|---|---|
| 源文件目录 | VodConfig.SrcDir | 存储上传的原始视频文件,保留完整质量 |
| 转码文件目录 | VodConfig.Dir | 存储转码后的TS分片文件及索引,用于点播服务 |
通过finder.Engine组件统一管理这两个目录的文件生命周期,确保存储系统有序运行。
自动清理机制详解
1. 定时过期清理:基于时间维度的治理
EasyDarwin实现了基于文件修改时间的自动过期清理机制,通过finder.Engine组件的定时任务扫描文件系统,删除超过设定存活时间的媒体文件。
核心实现代码分析
// deadline 超时删除
func (e *Engine) deadline() {
ticker := time.NewTimer(e.deadlineDurationTimer)
defer ticker.Stop()
for {
select {
case <-e.ctx.Done():
return
case <-ticker.C:
if err := filepath.Walk(e.prefix, func(path string, info os.FileInfo, _ error) error {
if info == nil || info.IsDir() {
return nil
}
// 判断文件修改时间是否超过过期时间
if time.Since(info.ModTime()) > e.expire {
_ = os.RemoveAll(path)
}
return nil
}); err != nil {
slog.Error("deadline", "err", err)
}
ticker.Reset(e.deadlineDurationTimer)
}
}
}
关键参数配置
| 参数 | 默认值 | 说明 |
|---|---|---|
expire | 可配置 | 文件过期时间,超过此时间的文件将被删除 |
deadlineDurationTimer | 10分钟 | 定时扫描间隔,建议根据文件创建频率调整 |
最佳实践:对于安防场景建议设置7-30天过期时间,直播回放场景可缩短至24-48小时,具体根据业务需求和存储容量规划。
2. 磁盘阈值清理:基于空间维度的治理
当磁盘使用率达到设定阈值时,系统会触发紧急清理流程,按文件创建时间从旧到新删除文件,确保磁盘空间维持在安全水位。
核心实现代码分析
// overWrite 处理磁盘空间超过阈值的清理
func (e *Engine) overWrite() {
for {
select {
case <-e.ctx.Done():
return
case <-e.limit:
var files []system.FileInfo
for {
// 获取当前磁盘使用率
got, err := system.DiskUsagePercent(e.prefix)
if err != nil {
slog.Error("DiskUsagePercent", "err", err)
continue
}
// 检查是否低于安全阈值
if float32(got) < e.diskUsagePercentLimit {
// 根据使用率差距动态调整下次检查间隔
s := e.diskUsagePercentLimit - float32(got)
switch true {
case s > 30:
time.Sleep(5 * time.Minute)
case s > 10:
time.Sleep(time.Minute)
case s > 6:
time.Sleep(10 * time.Second)
}
break
}
// 首次运行时获取文件列表
if files == nil {
files, err = system.GlobFiles(e.prefix)
if err != nil {
slog.Error("GlobFiles", "err", err)
continue
}
}
// 清理旧文件
count, err := system.CleanOldFiles(files, e.deleteElements)
if err != nil {
slog.Error("CleanOldFiles", "err", err)
}
if count <= len(files) {
files = files[count:]
}
// 如果一次删除不足,退出循环等待下次触发
if count < e.deleteElements {
break
}
}
}
}
}
磁盘清理关键参数
| 参数 | 默认值 | 说明 |
|---|---|---|
diskUsagePercentLimit | 85% | 磁盘使用率阈值,超过此值触发清理 |
deleteElements | 5 | 每次清理的文件数量,避免一次性删除过多文件影响系统性能 |
工作流程:当磁盘使用率超过阈值时,系统会按文件创建时间从旧到新排序,每次删除deleteElements数量的文件,直到使用率低于阈值。清理过程采用渐进式策略,避免对系统负载造成冲击。
3. 手动清理API:按需触发的文件删除
除自动清理外,EasyDarwin提供RESTful API接口用于手动触发文件清理,支持单文件删除和批量删除两种模式。
单文件删除API
/**
* @api {post} /vod/remove 08 删除点播
* @apiGroup 02vod
* @apiParam {String} id
* @apiUse simpleSuccess
*/
func (r *VODRouter) remove(c *gin.Context) {
form := IDForm{}
if err := c.Bind(&form); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
db := data.GetDatabase()
var vod video.TVod
db.First(&vod, consts.SqlWhereID, form.ID)
if vod.ID != "" {
if vod.Status == consts.VodStatusTransing {
AbortWithString(c, http.StatusBadRequest, fmt.Sprintf("%s 正在转码", vod.Name))
return
}
conf := gCfg.VodConfig
// 必须添加 efile.GetRealPath 才能对比。要不然 video.Folder 为空,会导致整个最外层文件夹被删除
tsFolder := efile.GetRealPath(filepath.Join(conf.Dir, vod.Folder))
srcFile := efile.GetRealPath(filepath.Join(conf.SrcDir, vod.Path))
if tsFolder != conf.Dir && srcFile != conf.SrcDir {
slog.Info("remove tsFole : ", tsFolder)
slog.Info("remove srcFile : ", srcFile)
os.RemoveAll(tsFolder)
os.RemoveAll(srcFile)
}
db.Delete(vod)
}
Success(c)
}
批量删除API
/**
* @api {post} /vod/removeBatch 09 批量删除点播文件
* @apiGroup 02vod
* @apiParam {Array} ids
* @apiUse simpleSuccess
*/
func (r *VODRouter) removeBatch(c *gin.Context) {
form := IDSForm{}
if err := c.Bind(&form); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
var vods []video.TVod
db := data.GetDatabase()
tx := db.Begin()
tx.Find(&vods, consts.SqlWhereIDIn, form.IDS)
for _, vod := range vods {
if vod.Status == consts.VodStatusTransing {
tx.Rollback()
AbortWithString(c, http.StatusBadRequest, fmt.Sprintf("%s 正在转码", vod.Name))
return
}
tx.Delete(vod)
}
tx.Commit()
conf := gCfg.VodConfig
for _, vod := range vods {
tsFolder := efile.GetRealPath(filepath.Join(conf.Dir, vod.Folder))
srcFile := efile.GetRealPath(filepath.Join(conf.SrcDir, vod.Path))
if tsFolder != conf.Dir && srcFile != conf.SrcDir {
slog.Info("remove batch video : ", tsFolder)
slog.Info("remove batch video : ", srcFile)
os.RemoveAll(tsFolder)
os.RemoveAll(srcFile)
} else {
CodeWithMsg(c, http.StatusInternalServerError, "内部错误,删除失败。")
return
}
}
Success(c)
}
安全机制:删除操作前会检查文件状态,对于正在转码(VodStatusTransing)的文件会拒绝删除请求,避免破坏转码过程。同时通过efile.GetRealPath确保路径正确性,防止误删根目录。
存储策略优化实践
1. 合理设置过期时间与磁盘阈值
根据业务场景调整文件过期时间和磁盘使用率阈值,是平衡存储成本与服务质量的关键:
| 应用场景 | 推荐过期时间 | 推荐磁盘阈值 | 原因分析 |
|---|---|---|---|
| 实时监控 | 7-30天 | 80% | 需保留一定历史数据用于回溯,磁盘阈值设低些保证写入稳定性 |
| 直播回放 | 24-72小时 | 85% | 内容时效性强,可快速清理释放空间 |
| 点播服务 | 30-90天 | 82% | 根据内容热度动态调整,热门内容延长过期时间 |
配置示例:
// 创建文件管理器实例,设置默认过期时间为30天
engine := finder.NewEngine("/data/vod", 30*24*time.Hour)
// 调整磁盘使用率阈值为80%
engine.SetDiskUsagePercentLimit(80)
// 设置每次清理10个文件
engine.SetDeleteElements(10)
2. 媒体文件存储路径规划
合理的文件路径设计可提高清理效率,避免单目录下文件数量过多影响文件系统性能:
推荐路径格式:{年份}/{月份}/{日期}/{视频ID}/{分辨率}/{文件名}
优点:
- 按时间维度分层,便于按时间段清理过期文件
- 每个目录文件数量可控,提高文件系统操作效率
- 支持多分辨率存储,便于按需求清理低优先级分辨率文件
实现示例:
// 生成按日期分层的存储路径
func generateStoragePath(videoID string) string {
today := time.Now().Format("2006/01/02")
return filepath.Join(today, videoID)
}
3. 转码文件生命周期管理
转码文件通常比源文件小得多,但数量更多,建议采用更短的过期时间:
- 分辨率分级清理:优先清理低分辨率文件,保留高价值内容
- 热度基于清理:结合播放次数,长期未播放的转码文件优先清理
- 索引先行策略:删除文件前先删除索引,确保用户无法访问后再删除实际文件
4. 存储监控与告警机制
结合EasyDarwin的监控接口,实现存储状态监控与告警:
监控指标建议:
- 磁盘使用率及增长趋势
- 每日清理文件数量
- 清理操作成功率
- 各目录文件数量分布
5. 分布式存储扩展
当单节点存储达到瓶颈时,可通过EasyDarwin的分布式架构扩展存储能力:
- 按地理位置分布:将文件存储在离用户最近的节点
- 按内容类型分片:不同类型的内容存储在不同节点
- 冷热数据分离:热门内容存储在高性能存储,冷数据迁移到低成本存储
常见问题与解决方案
问题1:清理不生效,磁盘空间持续增长
排查步骤:
- 检查是否有进程正在写入被标记为删除的文件,导致文件句柄未释放
- 验证
finder.Engine是否正确初始化并启动 - 检查日志确认是否有清理错误:
grep "CleanOldFiles" easydarwin.log - 确认文件系统权限是否允许EasyDarwin删除文件
解决方案:
// 增加清理错误日志详细度
func (e *Engine) deadline() {
// ... 原有代码 ...
if err := os.RemoveAll(path); err != nil {
slog.Error("删除过期文件失败", "path", path, "err", err)
}
// ... 原有代码 ...
}
问题2:清理过程影响系统性能
优化方案:
- 降低清理频率:调整
deadlineDurationTimer参数延长扫描间隔 - 减少每次清理数量:调小
deleteElements参数 - 错峰清理:在系统负载低的时段执行清理任务
- 增加IO优先级控制:降低清理进程的IO优先级
实现示例:
// 设置清理任务在凌晨3点执行
func (e *Engine) SetCleanupSchedule(hour, minute int) {
now := time.Now()
nextRun := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location())
if nextRun.Before(now) {
nextRun = nextRun.Add(24 * time.Hour)
}
delay := nextRun.Sub(now)
time.AfterFunc(delay, func() {
// 执行清理
e.cleanupOnce()
// 每天执行一次
e.SetCleanupSchedule(hour, minute)
})
}
问题3:误删重要文件
预防措施:
- 回收站机制:删除文件前先移动到回收站,保留7-15天再彻底删除
- 删除确认:重要文件删除前要求二次确认
- 操作审计:记录所有删除操作,包括触发方式、执行人、文件信息
- 定期备份:核心文件定期备份到独立存储
回收站实现示例:
// 带回收站的文件删除函数
func safeDelete(path string) error {
recycleBin := filepath.Join(filepath.Dir(path), ".recycle")
os.MkdirAll(recycleBin, 0755)
// 移动到回收站而不是直接删除
recyclePath := filepath.Join(recycleBin, fmt.Sprintf("%s_%d", filepath.Base(path), time.Now().Unix()))
if err := os.Rename(path, recyclePath); err != nil {
return err
}
// 启动定时任务,7天后彻底删除
time.AfterFunc(7*24*time.Hour, func() {
os.RemoveAll(recyclePath)
})
return nil
}
总结与展望
EasyDarwin通过定时过期清理和磁盘阈值清理双机制,结合手动API,构建了完善的媒体文件存储管理体系。合理配置清理参数、优化存储路径、实施分级存储策略,可有效降低存储成本,提高系统稳定性。
未来存储管理将向更智能的方向发展:
- AI预测性清理:基于内容热度和用户行为预测,提前清理低价值文件
- 弹性存储扩展:结合云存储自动扩展存储容量
- 智能转码决策:根据存储状况动态调整转码策略,平衡质量与存储占用
掌握这些存储管理技术,将帮助你构建更可靠、更经济的流媒体服务系统。建议定期审查存储使用情况和清理效果,持续优化存储策略,以适应业务发展变化。
行动指南:
- 今日检查你的EasyDarwin存储配置,确保过期时间和磁盘阈值符合业务需求
- 实施文件路径优化,按时间和ID分层存储
- 部署存储监控,设置关键指标告警
- 制定定期审查计划,每季度评估存储策略有效性
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



