Skip to content

视频监控存储与调度方案技术文档

约 1663 字大约 6 分钟

MediaMTX视频

2025-12-24

视频监控系统存储架构与调度方案技术

1. 概述 (Overview)

本通过旨在解决大规模视频监控场景下的海量数据存储与存储空间扩展问题。

在安防监控(NVR)系统中,随着摄像头数量增加和录像周期的延长,单一硬盘无法满足存储需求。系统必须支持多块物理硬盘(或网络存储),并具备在某个存储卷写满时,动态、无感地切换到下一个存储卷的能力。 核心设计思想是:解耦。将物理存储设备的差异性通过操作系统和数据库层进行抽象,应用程序(MediaMTX)仅通过统一的“文件路径”进行读写。

2.存储拓扑架构 (Storage Topologies)

根据项目规模和硬件环境,支持以下两种主流存储架构。软件层逻辑对两者完全兼容。

2.1 架构一:单机多盘 (DAS - Direct Attached Storage)

适用于中小规模项目(Demo、单体 NVR 服务器)。

  • 物理层:服务器机箱内通过 SATA/SAS 接口直接插入多块物理硬盘。
  • 网络特征:所有硬盘共享服务器的同一个 IP 地址。
  • 扩展方式:受限于主板插槽数量(通常 4-8 个)。
graph TD
Server[NVR 服务器 IP: 192.168.1.100]
Server -->|SATA接口| DiskA[物理硬盘 A]
Server -->|SATA接口| DiskB[物理硬盘 B]

    subgraph 操作系统层
    DiskA --> PathA[路径 D:/]
    DiskB --> PathB[路径 E:/]
    end

2.2 架构二:网络存储 (NAS / IPSAN)

适用于大规模、企业级项目,支持无限扩容。

  • 物理层:存储设备与计算服务器分离。 存储设备(NAS/磁盘阵列)拥有独立的 IP 地址。
  • 网络特征:通过网络协议(SMB/NFS/iSCSI)传输数据。
  • 系统层抽象:通过操作系统的挂载(Mount)或映射(Map)功能,将远程 IP 的存储伪装成本地路径。
graph TD
Server[NVR 计算节点 IP: 192.168.1.100]
NAS1[网络存储 NAS-01 IP: 192.168.1.200]
NAS2[网络存储 NAS-02 IP: 192.168.1.201]

    NAS1 -.->|SMB/NFS 挂载| Server
    NAS2 -.->|SMB/NFS 挂载| Server

    subgraph 操作系统层 [Server OS View]
    Server --> PathZ[路径 Z:/ (映射自 NAS-01)]
    Server --> PathY[路径 Y:/ (映射自 NAS-02)]
    end

3. 核心机制:路径抽象与无感切换

3.1 为什么物理隔离也能切换?

尽管物理硬盘是隔离的,甚至分布在不同 IP 的机器上,但MediaMTX(流媒体服务)并不直接操作硬件。

  1. 统一命名空间:操作系统(Windows/Linux)将所有物理设备统一抽象为文件系统路径(File System Path)。
    • 本地硬盘 D: \rightarrow D:/videos
    • 网络 NAS 192.168.1.200 \rightarrow Z:/videos
  2. 切换原理:
  • 应用程序只需修改配置路径。
  • 应用程序执行 Close(旧文件句柄) \rightarrow Open(新路径文件句柄)。
  • 操作系统负责底层的寻址和数据落盘(无论是写本地磁头还是通过网卡发包)。

3.2 限制因素

架构类型瓶颈/限制适用场景
DAS (本地)物理插槽限制:机箱插满即止。单点故障:服务器宕机则数据不可读。1-32 路相机\单机部署
NAS (网络)网络带宽限制:所有视频流需经过网卡写入 NAS。例如千兆网卡(1000Mbps)理论上限约写入 100 路 1080P 视频。32-1000+ 路相机集群部署

4.数据库设计 (Database Schema)

为了实现灵活调度,必须将“存储卷”与“录像计划”分离。

4.1 存储卷表 (hm_storage_volumes)

管理物理磁盘资源的注册表。

CREATE TABLE `hm_storage_volumes` (
   `id` INT NOT NULL AUTO_INCREMENT,
   `name` VARCHAR(50) NOT NULL COMMENT '卷名称,如: Disk_Data_01',
   `root_path` VARCHAR(255) NOT NULL COMMENT '操作系统内的挂载路径,如: D:/recordings  /mnt/nas01',
   `type` VARCHAR(10) DEFAULT 'local' COMMENT '类型: local(本地), nas(网络)',
   `status` TINYINT(1) DEFAULT 1 COMMENT '状态: 1-读写(Active), 2-只读(Full), 0-离线',
   `capacity_total` BIGINT COMMENT '总容量(Byte)',
   `capacity_used` BIGINT COMMENT '已用容量(Byte)',
   `priority` INT DEFAULT 0 COMMENT '调度优先级,值越大越优先写入',
   PRIMARY KEY (`id`)
   );

4.2 录像计划表 (hm_recording_plans)

记录相机与存储卷的绑定关系。

CREATE TABLE `hm_recording_plans` (
   `id` BIGINT NOT NULL AUTO_INCREMENT,
   `camera_id` VARCHAR(50) NOT NULL,
   `stream_name` VARCHAR(100) NOT NULL COMMENT '流唯一标识',

-- 关键关联
`volume_id` INT NOT NULL COMMENT '当前正在使用的存储卷ID,关联 hm_storage_volumes.id',

`status` TINYINT(1) DEFAULT 0 COMMENT '录像状态: 1-开启, 0-停止',
`retention_days` INT DEFAULT 7 COMMENT '保留天数',
`record_format` VARCHAR(10) DEFAULT 'fmp4',

`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);

5. 调度逻辑与实现流程

5.1 切换流程图

sequenceDiagram
   participant Admin as 调度系统/管理员
   participant Java as Java 后端
   participant DB as MySQL 数据库
   participant MTX as MediaMTX 服务
   participant OS as 操作系统

   Note over Admin: 监测到卷A(D:/)即将写满
   Admin->>Java: 发起切换指令: Cam01 -> 卷B(E:/)

   Java->>DB: 查询卷B信息 (root_path = "E:/data")
   Java->>DB: 更新 Cam01 计划 (volume_id = 卷B.id)

   Java->>Java: 拼接新路径: "E:/data/cam01/%Y..."

   Java->>MTX: API调用: /v3/config/paths/update <br/> { recordPath: "E:/data/..." }

   MTX->>OS: Close(旧文件)
   MTX->>OS: Open(新文件 E:/...)

   Note over MTX: 视频流开始写入新磁盘

5.2 Java 伪代码实现

@Service
   public class StorageSchedulingService {

   public void switchVolume(String streamName, Integer newVolumeId) {
   // 1. 获取新卷的物理路径
   StorageVolume newVol = volumeRepo.findById(newVolumeId);
   String physicalRoot = newVol.getRootPath(); // e.g., "E:/recordings"

        // 2. 构造 MediaMTX 动态路径模板
        // 最终路径: 物理根目录 + 流名称 + 时间分层
        String newRecordPath = physicalRoot + "/" + streamName + "/%Y-%m-%d/%H-%M-%S.mp4";

        // 3. 下发配置给流媒体服务 (热更新)
        mediaMtxClient.updatePathConfig(streamName, newRecordPath);

        // 4. 更新数据库关联状态
        RecordingPlan plan = planRepo.findByStreamName(streamName);
        plan.setVolumeId(newVolumeId);
        planRepo.save(plan);
        
        log.info("相机 {} 存储路径已切换至卷: {}", streamName, newVol.getName());
   }
   }

6. 常见问题 (FAQ)

Q1: 切换存储后,以前的录像还在吗?

A: 还在旧的磁盘里。切换只是改变了“新数据写入的位置”,不会移动或删除旧数据。

Q2: 既然旧录像在旧盘,新录像在新盘,前端怎么回放?

A:Demo 阶段:只回放当前挂载盘的录像。生产阶段:需要引入文件索引表 (hm_recording_files)。MediaMTX 生成文件后通知后端记录文件路径。回放时,后端根据时间段查询索引表,无论文件在 D 盘还是 E 盘,直接返回对应的文件 URL 给前端。

Q3: NAS 存储如果不稳定断开了怎么办?

A: MediaMTX 会报错停止录像。建议在 Java 端增加心跳检测,如果检测到存储卷状态异常(IO Error),自动触发调度逻辑,切换到备用本地磁盘。

贡献者

  • flycodeuflycodeu

公告板

2025-03-04正式迁移知识库到此项目