一、先搞懂啥是DASH协议

很多人刷视频时会遇到这种情况:网络卡的时候,视频会自动变模糊,等网络好了又变清晰,这个自动切换清晰度的功能,背后大多用到了DASH协议。

简单说,DASH协议就是把一个完整的视频拆成好多个小片段,每个片段还对应不同的清晰度(比如1080P、720P、480P),服务器把这些片段存在不同地方,用户看的时候,会根据自己的网速自动选合适清晰度的片段来播放。

举个例子,一部10分钟的电影,会被拆成120个5秒的小片段,每个片段都有1080P、720P、480P三个版本,服务器分别存这三个版本的片段,用户网速快就拿1080P的,网速慢就拿480P的,这样不会卡。

二、为啥要做分片存储架构设计

如果把所有片段都存在一个地方,会有三个大问题: 第一个是访问压力大。比如热门视频同时有几十万人看,都去同一个地方读片段,服务器会被挤爆,就像学校食堂饭点只开一个窗口,肯定会排队。 第二个是成本高。如果把所有片段都存在贵的高速存储里,比如SSD,成本会特别高;如果存在便宜的低速存储,比如机械硬盘,读的速度又跟不上。 第三个是扩展性差。如果视频越来越多,原来的存储地方不够用,很难扩容。

所以我们需要设计一种架构,把不同的片段存在不同的存储位置,既能扛住访问压力,又能控制成本,还方便扩容。

三、具体的架构设计方案

3.1 整体架构逻辑

这个架构的核心是“分类存储,按需调用”,我们把存储分成三类:热存储、温存储、冷存储。 热存储存最热门的视频片段,比如刚上线的新剧前3集、热门综艺的片段,用高速SSD,读写速度快,能扛大访问量; 温存储存热度下降的视频片段,比如上线半个月的剧,用中速存储,比如混合硬盘,成本比热存储低; 冷存储存很久没人看的视频片段,比如上线半年以上的老剧,用便宜的低速存储,比如磁带、云存储的归档类型,成本最低。

同时,我们还会有一个专门的“调度服务”,负责两件事:一是告诉用户该拿哪个清晰度的片段,二是告诉用户该去哪个存储位置拿片段。

3.2 详细的分片规则

为了让存储更合理,我们会给每个片段加三个标签:热度标签、清晰度标签、内容类型标签。 热度标签:根据视频的播放量、点赞量、转发量计算,分为热门、中等、冷门三个等级; 清晰度标签:分为1080P、720P、480P三个等级; 内容类型标签:分为剧情、综艺、直播回放、短视频四个类型。

举个具体的例子: 一部刚上线的热门1080P剧情剧,它的片段标签就是“热门、1080P、剧情”,会被存在热存储里; 这部剧上线半个月后,热度降为中等,片段标签变成“中等、1080P、剧情”,会被自动转移到温存储里; 这部剧上线半年后,热度降为冷门,片段标签变成“冷门、1080P、剧情”,会被自动转移到冷存储里。

3.3 调度服务的工作流程

调度服务是整个架构的核心,它的工作流程很简单,分三步: 第一步,用户打开视频时,先给调度服务发一个请求,告诉调度服务自己的网速、想播放的视频ID; 第二步,调度服务根据视频的标签,找到对应的存储位置,再根据用户的网速,选择合适的清晰度; 第三步,调度服务把片段的存储地址和清晰度信息发给用户,用户去对应的存储位置拿片段播放。

四、实际的代码实现示例

4.1 示例技术栈

本次示例使用的技术栈是:Python 3.8 + 轻量级调度服务逻辑,所有代码都是纯Python实现,不依赖复杂框架,方便理解。

4.2 调度服务核心代码

首先,我们需要定义一个存储位置的配置,用来记录不同标签的片段存在哪里:

# 存储位置配置:key是标签组合,value是存储地址
STORAGE_CONFIG = {
    # 热门、1080P、剧情的存储地址
    ("热门", "1080P", "剧情"): "http://hot-storage.example.com/segment/",
    # 热门、720P、剧情的存储地址
    ("热门", "720P", "剧情"): "http://hot-storage.example.com/segment/",
    # 热门、480P、剧情的存储地址
    ("热门", "480P", "剧情"): "http://hot-storage.example.com/segment/",
    # 中等、1080P、剧情的存储地址
    ("中等", "1080P", "剧情"): "http://warm-storage.example.com/segment/",
    # 中等、720P、剧情的存储地址
    ("中等", "720P", "剧情"): "http://warm-storage.example.com/segment/",
    # 中等、480P、剧情的存储地址
    ("中等", "480P", "剧情"): "http://warm-storage.example.com/segment/",
    # 冷门、1080P、剧情的存储地址
    ("冷门", "1080P", "剧情"): "http://cold-storage.example.com/segment/",
    # 冷门、720P、剧情的存储地址
    ("冷门", "720P", "剧情"): "http://cold-storage.example.com/segment/",
    # 冷门、480P、剧情的存储地址
    ("冷门", "480P", "剧情"): "http://cold-storage.example.com/segment/",
}

# 视频标签配置:key是视频ID,value是(热度, 内容类型)
VIDEO_TAG_CONFIG = {
    # 视频ID为"v123"的视频,热度是热门,内容类型是剧情
    "v123": ("热门", "剧情"),
    # 视频ID为"v456"的视频,热度是中等,内容类型是剧情
    "v456": ("中等", "剧情"),
    # 视频ID为"v789"的视频,热度是冷门,内容类型是剧情
    "v789": ("冷门", "剧情"),
}

然后,我们实现调度服务的核心逻辑,用来给用户返回片段的存储地址:

def get_segment_url(video_id, segment_index, user_speed):
    """
    获取片段的存储地址
    :param video_id: 视频ID
    :param segment_index: 片段的序号(比如第5个片段,序号就是5)
    :param user_speed: 用户的网速,单位是Mbps
    :return: 片段的完整存储地址
    """
    # 第一步:根据视频ID获取视频的热度和内容类型
    if video_id not in VIDEO_TAG_CONFIG:
        raise ValueError("视频不存在")
    heat, content_type = VIDEO_TAG_CONFIG[video_id]

    # 第二步:根据用户网速选择合适的清晰度
    # 网速大于等于10Mbps,选1080P
    if user_speed >= 10:
        resolution = "1080P"
    # 网速大于等于5Mbps,小于10Mbps,选720P
    elif user_speed >= 5:
        resolution = "720P"
    # 网速小于5Mbps,选480P
    else:
        resolution = "480P"

    # 第三步:根据标签组合获取存储地址
    tag_key = (heat, resolution, content_type)
    if tag_key not in STORAGE_CONFIG:
        raise ValueError("没有对应的存储配置")
    storage_base_url = STORAGE_CONFIG[tag_key]

    # 第四步:拼接片段的完整地址,格式是:存储地址/视频ID_片段序号_清晰度.mp4
    segment_url = f"{storage_base_url}{video_id}_{segment_index}_{resolution}.mp4"
    return segment_url

4.3 代码测试示例

我们来测试一下这个调度服务的逻辑是否正确:

# 测试1:用户网速15Mbps,请求视频v123的第5个片段
try:
    url = get_segment_url("v123", 5, 15)
    print(f"测试1结果:{url}")
except Exception as e:
    print(f"测试1错误:{e}")

# 测试2:用户网速7Mbps,请求视频v123的第5个片段
try:
    url = get_segment_url("v123", 5, 7)
    print(f"测试2结果:{url}")
except Exception as e:
    print(f"测试2错误:{e}")

# 测试3:用户网速3Mbps,请求视频v123的第5个片段
try:
    url = get_segment_url("v123", 5, 3)
    print(f"测试3结果:{url}")
except Exception as e:
    print(f"测试3错误:{e}")

# 测试4:用户网速15Mbps,请求视频v789的第10个片段
try:
    url = get_segment_url("v789", 10, 15)
    print(f"测试4结果:{url}")
except Exception as e:
    print(f"测试4错误:{e}")

测试结果如下: 测试1结果:http://hot-storage.example.com/segment/v123_5_1080P.mp4 测试2结果:http://hot-storage.example.com/segment/v123_5_720P.mp4 测试3结果:http://hot-storage.example.com/segment/v123_5_480P.mp4 测试4结果:http://cold-storage.example.com/segment/v789_10_1080P.mp4

这个结果完全符合我们的预期:热门视频的片段都存在热存储,冷门视频的片段存在冷存储,用户网速不同,拿到的清晰度也不同。

五、应用场景、优缺点和注意事项

5.1 应用场景

这个架构最适合的场景是长视频平台,比如腾讯视频、爱奇艺、优酷这类,因为长视频的数量多、热度变化大,需要根据热度调整存储位置;其次是短视频平台,比如抖音、快手,热门短视频的访问量特别大,需要存在高速存储;还有直播平台的回放视频,刚结束的直播回放热度高,存在热存储,过几天热度降了,转移到温存储,几个月后转移到冷存储。

5.2 技术优缺点

优点有三个:第一是成本低,把不同热度的片段存在不同成本的存储里,比把所有片段都存在高速存储里,成本能降低50%以上;第二是性能好,热门片段存在高速存储,能扛住大访问量,不会卡;第三是扩展性强,只要增加新的存储节点,就能容纳更多的视频片段。

缺点也有两个:第一是架构复杂,需要维护不同的存储系统,还要做数据迁移,比如把热度下降的片段从热存储转移到温存储,需要额外的开发和维护工作;第二是数据迁移有风险,迁移过程中如果出错,可能会导致用户拿不到片段,影响播放。

5.3 注意事项

第一,热度计算要合理,不能只看播放量,还要考虑点赞量、转发量、评论量,比如一个视频播放量高,但都是机器人刷的,就不能算热门;第二,数据迁移要做备份,迁移前先把片段复制一份到新的存储位置,确认复制成功后,再删除旧的片段,避免数据丢失;第三,要做降级处理,如果调度服务出问题,或者某个存储节点出问题,要能自动切换到其他存储位置,比如热存储出问题,就自动去温存储拿片段,保证用户能正常播放。

六、文章总结

DASH协议的服务端分片存储架构,核心是通过对视频片段的分类存储和调度,实现性能和成本的平衡。它的本质是“物尽其用”,把高速存储留给最需要的热门片段,把低速存储留给没人看的冷门片段,既保证了用户的播放体验,又降低了平台的运营成本。

这个架构不是一成不变的,平台可以根据自己的实际情况调整分类规则和存储配置,比如有的平台可能会把存储分成四类,有的平台可能会调整热度的计算周期。只要抓住“分类存储、按需调用”这个核心,就能设计出适合自己的DASH分片存储架构。