分片下载、断点续传与实时速度显示的实现方法

本篇文章以接入百度网盘为例,简单介绍怎么实现这些下载功能。

准备

我们先看一下百度网盘的下载的接口文档:

以一个10G的文件为例,我们下载的时候需要传入Range参数:0 ~ 499、500 ~ 999....一直到10个G下载完成。

我们以一个10GB的文件为例,使用百度网盘下载接口时,需要将文件分成多个段进行下载。每个下载请求都需要通过传入Range参数来指定下载的范围。例如,第一个请求的Range参数为0 ~ 499,第二个请求为500 ~ 999,依此类推,直到整个文件下载完毕。

Range参数的格式为:Range: bytes=起始字节-结束字节。例如:

  • 第一个分块:Range: bytes=0-499
  • 第二个分块:Range: bytes=500-999
  • ......
  • 最后一个分块:Range: bytes=9999999500-9999999999
    通过这种方式,可以将大文件分段下载,提高下载效率。每次下载一个分块,直到所有分块下载完成并合并,最终得到完整的原始文件。

分片下载

分片下载是一种提高数据传输效率和速度的技术,尤其适用于大文件的高效下载。通过将大文件分割成多个小块(称为分片),每个分片可以独立下载,从而充分利用多线程的优势,提升整体下载速度。

例如,一个10G的文件可以被分割成100个100M的分片,每个分片作为一个独立的任务。多线程下载机制允许每个线程同时处理不同的分片,从而并行执行下载任务。这样可以有效减少总的下载时间,特别是在带宽受限的网络环境中,分片下载能够更高效地利用网络资源,避免单线程下载时可能产生的瓶颈。

断点下载

断点下载是一种在下载过程中间断后,能够从断点继续下载的技术。比如,在下载服务重启时,如果已经下载了99个分片,可以通过断点下载功能,继续下载未完成的分片,无需重新开始下载。

断点下载的核心在于保存已下载的信息,通常以JSON文件的形式存储。例如,断点文件的内容可能如下:

yaml 复制代码
[{
  "start": 0,
  "mid": 100,
  "end": 100
}, {
  "start": 101,
  "mid": 200,
  "end": 200
}, {
  "start": 9900,
  "mid": 9950,
  "end": 10000
}]

其中:

  • start 表示该分片的起始位置。
  • end 表示该分片的结束位置。
  • mid 表示该分片中已下载的部分。
    以最后一个分片为例,start: 9900 表示下载从第9900字节开始,end: 10000 表示下载到第10000字节,而 mid: 9950 表示已经下载了50字节。当服务重启后,通过读取断点文件,可以继续从第9951字节开始下载,确保下载的连续性。

刷新频率

断点文件的刷新频率可以通过以下两种方式控制:

  • 完成一个分片后刷新:在每个分片下载完成后,立即更新断点文件并刷新到磁盘,确保断点信息是最新的。
  • 定时刷新:设置一个固定的时间间隔(如每5秒或10秒),定期将内存中的断点信息刷新到磁盘。这种机制可以减少I/O操作的频率,同时也能避免数据丢失。

优化: 如果服务运行在内存充足的环境中,可以将断点文件映射到内存中。这意味着断点文件的内容直接加载到内存,每次更新时只需修改内存中的数据,而不需要频繁地进行磁盘操作。这种优化可以显著提高断点文件的读写速度。

写入文件

为了高效地将下载的分片数据写入文件,确保每个分片写入正确的位置,可以按照以下步骤进行:

  1. 文件预分配 :创建一个10G的空文件,并预先分配所需的空间,避免写入过程中因文件扩展导致的性能问题。例如,使用文件系统提供的预分配功能,如Linux中的fallocate
  2. 确定分片位置:计算每个分片的起始位置和大小。例如,每个分片为100M,第二个分片应从100M的位置开始写入。
  3. 同步机制:使用互斥锁或文件锁,确保在多线程或多进程环境中,每次只有一个线程移动指针或写入数据,避免数据混乱或覆盖。
  4. 移动指针并写入:对于每个分片,先将文件指针移动到该分片的起始位置,然后写入对应的下载数据。使用缓冲机制或块写入,提高写入效率。

实时速度的显示

我们下载文件的时候是需要显示实时速度的,如图所示:

这个6.42MB每秒是怎么做的呢?

探速机制

  • 初始测试:使用一个较小的数据包(如1MB或10MB)进行初始速度测试,计算初始下载速度。
  • 多线程分摊:根据初始速度和线程数,估算每个线程的理论下载速度。

举例

  • 假设初始测试下载10MB数据,耗时5秒,平均速度为2MB/s。
  • 初始线程数设置为10,每个线程的理论下载速度为200KB/s。也就是初始速度

阈值调整机制

通过优化下载包大小的调整机制,使速度显示呈现出更自然、真实的随机效果,主要手段包括随机波动和动态调整。

  • 随机波动:引入随机性,避免速度显示过于机械。每次下载的包大小在上一次大小的基础上进行调整。
  • 动态调整:根据下载时间长短,动态调整包大小:
    • 如果下载时间较短(网速较好),增加包大小。
    • 如果下载时间较长(网速较差),减少包大小。

以下是举例说明,可以根据具体需求自己编写公式。

  1. 设定初始值
    • 初始包大小:200kb
    • 初始阈值:20kb(初始包大小的十分之一)
  1. 下载时间分类及调整幅度范围
    • t < 0.5秒:增加幅度为阈值的80%-100%
    • 0.5 ≤ t < 0.8秒:增加幅度为50%-80%
    • 0.8 ≤ t < 1秒:增加幅度为0%-50%
    • t ≥ 1秒:减少幅度为50%-100%
  1. 引入随机值计算调整幅度
    • 在每个调整幅度范围内,使用随机数生成具体的调整值。
    • 使用公式:调整幅度 = 区间下限 + (区间上限 - 区间下限) × random()
      其中random()是一个0到1之间的随机数。
  1. 计算具体的调整幅度
    • t < 0.5秒
      调整幅度 = 0.8 × 阈值 + (0.2 × 阈值) × random()
    • 0.5 ≤ t < 0.8秒
      调整幅度 = 0.5 × 阈值 + (0.3 × 阈值) × random()
    • 0.8 ≤ t < 1秒
      调整幅度 = 0 × 阈值 + (0.5 × 阈值) × random()
    • t ≥ 1秒
      调整幅度 = 0.5 × 阈值 + (0.5 × 阈值) × random()
      然后从包大小中减去这个调整幅度。
  1. 计算下次包大小
    • 根据下载时间t和计算出的调整幅度,确定下次包大小:
      • 如果t < 1秒,包大小 += 调整幅度
      • 如果t ≥ 1秒,包大小 -= 调整幅度
  1. 设置包大小的上下限(可选):
    • 设置包大小的最小值(如初始值的80%)和最大值(如初始值的200%),以防止调整幅度过大导致包大小异常。
  1. 示例应用
    • 第一次下载
      • 包大小:200kb
      • 下载时间:0.7秒(0.5 ≤ t < 0.8秒)
      • 调整幅度 = 0.5 × 20kb + (0.3 × 20kb) × random()
      • 假设random()=0.6,调整幅度=10kb + 3.6kb=13.6kb
      • 下次包大小:200kb +13.6kb=213.6kb
    • 第二次下载
      • 包大小:213.6kb
      • 下载时间:0.8秒(0.8 ≤ t < 1秒)
      • 调整幅度 = 0 × 20kb + (0.5 × 20kb) × random()
      • 假设random()=0.6,调整幅度=0 + 6kb=6kb
      • 下次包大小:213.6kb +6kb=219.6kb
    • 第三次下载
      • 包大小:219.6kb
      • 下载时间:1.2秒(t ≥ 1秒)
      • 调整幅度 = 0.5 × 20kb + (0.5 × 20kb) × random()
      • 假设random()=0.8,调整幅度=10kb +8kb=18kb
      • 下次包大小:219.6kb -18kb=201.6kb
        通过引入随机因素,下载包大小的调整更加平滑和自然,模拟了实际网络环境中的波动,提升了真实感。

实时速度的计算

我们可以将每秒所有线程下载的包统计汇总后,存储到一个队列中。随后,后台任务将定时每秒执行一次计算操作:将队列中的所有数据复制出来进行求和,并清空队列。

优化:为了提升系统性能和减少资源竞争,我们采用了读写分离的优化方案。具体来说,我们使用两个队列来进行数据交换,后台任务每秒执行一次队列指针的交换操作。这种做法有效地分离了读写操作,避免了多线程环境下的竞争问题,提升了系统的整体吞吐量和响应速度。

其它操作(暂停、继续、删除、重新下载等)

这些页面上的操作其实都需要我们自己实现:

  1. 开始下载:创建断点文件并创建多线程进行下载
  2. 暂停下载:将所有线程都终止并保存断点文件
  3. 继续下载:读取断点文件并创建多线程下载
  4. 删除下载:将所有线程都终止并删除下载的文件和断点文件
  5. 重新下载:先删除下载,再开始下载。

优化:可以把每一个操作都封装事件,搞一个事件驱动模型。

后记

实现后,速度基本达到百度网盘客户端的水平,甚至有时能快5%。本次对思路进行了简单总结。如有需要,也愿意将代码进行开源。如果有更好的建议或想法,也欢迎各位大佬多多指正。

相关推荐
跟着珅聪学java30 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
吞掉星星的鲸鱼33 分钟前
使用高德api实现天气查询
前端·javascript·css
lilye6636 分钟前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
zhougl9963 小时前
html处理Base文件流
linux·前端·html
花花鱼3 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_3 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
careybobo4 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue