一个并发/异步的下载服务设计
接收任务(步骤1)
- 提供批量任务/单个任务接口
- 收到任务之后随即对每一个任务启动process_task线程去处理
处理任务(步骤2)
-
根据每个任务的 {数据源}_{文件名} 作为redis的key去集群中查出该任务的值
-
每个任务有一个状态码,status_code为3位数字, abc, a代表是否下载完成,b代表是否正在下载,c代表第n次下载
-
若redis已有该任务status_code
-
状态a或b为true,不予下载,线程退出
-
状态a与b都为false,则根据c进行判断将采用哪个级别的代理(默认I、II、III级代理的速度、稳定性、IP陌生性递增)
- 此时c值必然大于0,若c为1,则说明此次为第二次下载,第一次的代理有问题,则此次采用II级代理
- 若c为2,则采用III级代理
- 若c为3,说明已经下载过3次,没有更好/更陌生的代理,任务失败,退出
-
若成功选择代理,则先将redis的status_code置为(a=false,b=true,c=c+1)
-
下载完成后,将redis的status_code置为(a=true,b=false,c)
-
-
若redis中没有该key,则说明此任务为第一次下载
-
则本地有一个hash结构,key为目标url的域名,value为该域名下已经执行过的任务数量,暂时叫它host_count
- 按照host_count按照一定比例(x:y:z)在I、II、III级代理之间进行轮询取代理,例如III级代理的稳定性、速度较高,则III级代理的比重设置大一点,让该host下的大多数任务都采用III级代理
-
选择代理后执行下载,在此之前先将redis的status_code置为(a=false,b=true,c=1)
-
下载完成后,将redis的status_code置为(a=true,b=false,c)
-
关键问题
-
redis并不能去重所有的重复,假设ms级别内有同一个 {数据源}_{文件名} 的多个任务发送过来,那么有可能同时启n个线程去同时取redis的value,此时这n个线程均会去执行下载任务,这样浪费了代理资源
- 解决方案:以 {数据源}_{文件名} 为key,本地存一个hashset,采用锁进行线程间互斥读写,任务准备执行下载动作时,立即将其写入set,下载完成后,在redis-value更新之后立即从set中去除
-
服务重启难:服务关闭时,有可能具有多个正在执行下载的任务,重启之后,这些下载任务有可能不会再被扫描列表页的程序发送过来,那么这些任务就被永久丢失了
- 解决方案:任务准备执行下载动作时,立即创建 {数据源}_{文件名}.temp 缓冲文件,下载完成后,先将响应的二进制数组写入该temp文件,写完成后直接将该temp文件重命名为html/pdf/doc等实际文件格式