案例,一组可用的资源列表, (test.zip大小100M) http://127.0.0.1:10001/down/test.zip http://127.0.0.1:10002/down/test.zip 403 forbidden http://127.0.0.1:10003/down/test.zip 502 down掉 http://127.0.0.1:10004/down/test.zip http://127.0.0.1:10005/down/test.zip 404 not found (下文简称10001,10002,10003,10004,10005服务器)lua-resty-http是基于ngx.cosocket封装的一个socket库,我就用它来实现模拟“多线程”从后台服务器多个资源列表同时下载一个文件,主要是考虑几个关键的业务问题。
sock:settimeout(500) ok, err = sock:connect(host, port or 80)2,在遍历每个资源尝试连接时,需要记录一个可用连接
uri_available = nil while i < n do if ret200 or ret206 then uri_available = dl_uris[i] end i = i + 1 end -- 此处判断是否存在可用连接,不然则返回404 if uri_available == nil then ngx.exit(404) end
socket[1] -> http://127.0.0.1:10001/down/test.zip socket[2] -> http://127.0.0.1:10004/down/test.zip socket[3] -> http://127.0.0.1:10004/down/test.zip socket[4] -> http://127.0.0.1:10004/down/test.zip socket[5] -> http://127.0.0.1:10004/down/test.zip (503 not available)--当然,用可用连接重启也不一定会成功,譬如10004服务器端针对连接数做了控制,只允许3个连接,
C,有了一堆下载连接,则需要设计分片的下载业务逻辑了,为了讨论方便,先设计每个连接的文件分片大小一样(这种设计用户体验不好,后文再说)
socket[1] 下载 0~100M ==================== socket[2] 下载 20~100M ================ socket[3] 下载 40~100M ============ socket[4] 下载 60~100M ======== socket[5] 下载 80~100M (503错误,所以这个连接不会真的下载)--为了保证后面的连接出现下载错误,(如:突然连接down掉),
local data, err, partial = sock[i]:receive(16*1024) -- 如果下载断掉,且不是因为文件尾(下载完毕时也会出现not data的情况) if not data and 当前连接下载偏移 + 16*1024 <= 文件大小 then --找一个可用连接再去下载(不能用uri_available,因为有可能就是它断掉了,所以需要重新遍历5个连接,例如10003服务器突然可用),这里假定存在一个可用连接续传,不然其他正在下载的连接也会出错,整个下载就无法继续了 end --这样,我们就起码能保证能够下载完整个文件的数据
local data, err, partial = sock[i]:receive(16*1024) out:seek("set","socket i offset") local block = out:read(16*1024) -- "sock_start[i] > rangesave_start" 后面再解释,参考G if block and block == data and sock_start[i] > rangesave_start then --关闭当前连接 endE,由于用户是通过“下载服务器”下载,所以我们在服务器端帮他多连接下载时也应该实时输出下载内容,不可能等到下载完毕后再把缓存文件一股脑输出出去,这样用户会崩溃的。怎么控制各个连接是否应该将数据输出给用户呢。我们需要记录一个“最靠前连接”的变量,每当一个连接下载好一段数据后判断它是不是最靠前的链接,如果是,则输出内容给客户端,这样就能保证数据块的顺序。其他连接下载到数据后就写缓存。
F,再回到D的问题上,某个连接下载数据时发现缓存中已存在,则除了关闭自身连接外,还需要看看是否应该输出缓存内容
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 socket[1](0-20) ==================== socket[2](21-40)========================================= socket[3](41-60)========== socket[4](61-100)... a,这种情况,socket[2]在下载第41M时发现缓存已存在,是不需要输出缓存内容的,因为socket[1]还没有下载完毕 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 socket[1](0-20) ========================================= socket[2](21-40)======================= socket[3](41-60)========== socket[4](61-100)... b,这种情况,socket[1]在下载第21M时发现缓存已存在,则需要输出缓存到socket[2]正在下载的位置 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 socket[1](0-20) ========================================= socket[2](21-40)========================================= socket[3](41-60)========== socket[4](61-100)... c,这种情况,socket[1]在下载第21M时发现缓存已存在,则需要输出缓存到socket[3]正在下载的位置所以当我们遇到缓存中已存在时,需要判断是否是“最靠前的连接”(socket[i]),如果是,则找到下一个还在下载的连接(不一定是socket[i+1],如情况c),一直输出缓存文件到下一个连接的下载偏移,如果后面连接都下载完毕(找不到活跃的连接),则说明文件已经下载完毕,需要输出到文件结尾
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 socket[1](0-20) ========================================= socket[2](21-40)========================================= socket[3](41-60)========================================= socket[4](61-..)========================================= =========================================处理完缓存输出后需要更新“最靠前连接”的信息,比如情况c中,需要更新为socket[3],因为我们的轮询下载是从1~4一次遍历的。如果不更新为socket[3],则这一次轮询中到socket[3]下载时它发现自己不是最靠前连接,则不会输出数据给客户端。(这个问题很重要,而且容易忽略掉)。
end
断点的位置也是不确定的,有可能我们的连接将下载内容缓存到文件中还来不及更新偏移信息时程序就down掉了,所以还需要注意针对偏移信息不及时的处理
socket[1] 下载 0~100M ==================== socket[2] 下载 10~100M ================== socket[3] 下载 30~100M ============== socket[4] 下载 60~100 ======== socket[5] 下载 80~100M (503错误,所以这个连接不会真的下载) 则速度慢的区间就是0~10M,因为10M开始可以读取缓存,同时我们可以控制不同连接每次下载数据大小 socket[1] 下载 0~100M ==================== 每次下载1M socket[2] 下载 10~100M ================== 每次下载500K socket[3] 下载 30~100M ============== 每次下载250K socket[4] 下载 60~100M ======== 每次下载100K socket[5] 下载 80~100M (503错误,所以这个连接不会真的下载) 则0~10M区间的下载体验会更好(因为后面的连接卡在下载的时间会被缩短),不过这需要动态调整各个连接的“下载速度”,业务逻辑复杂不少。总之,这种轮询方式的设计太过简单,里面存在很多问题,比如某个服务器速度很慢时整个下载都会被拖慢,所以我们还需要加入速度的判断逻辑,比如设置下载超时,想办法用速度快的连接替换速度慢的连接。倘若如果所有资源的速度都平均的话,整个下载速度会非常的快,从我简单的测试结果来看,一般5个并发连接下载的平均速度会是单个连接的3~4倍