一份內容可能被切成幾十甚至上百個片段,如果整個下載流程用同一層 try/except 包起來,任何一個片段失敗,最直覺的處理方式就是整個流程重跑一次——但這代表已經成功下載的片段也要重新走一次,浪費時間也浪費頻寬。
❌ 反例:任何片段失敗,整個流程重跑
async def download_all(segments):
while True:
try:
for segment in segments:
await download(segment) # 任何一個失敗,整個 for 迴圈中斷
return
except Exception:
continue # 從頭開始,已下載的也要重跑一次
✅ 正例:失敗範圍收斂在單一片段,各自重試
async def download_one(segment, max_tries=10):
tries = 0
while True:
try:
await download(segment)
return
except Exception as e:
tries += 1
if tries >= max_tries:
log_failure(segment, e)
return
await asyncio.sleep(10) # 給對方一點喘息空間再重試
async def download_all(segments):
for segment in segments:
await download_one(segment) # 每個片段的失敗互不影響
無限重試看起來「總會成功」,實際上如果失敗原因是永久性的(例如這個片段的網址已經失效),無限重試只會讓程式卡在這裡不動,也不會有任何訊號告訴你發生了什麼事。重試要有明確的上限,超過上限就承認失敗、記錄下原因,讓整個流程可以繼續往下走,而不是卡在一個永遠不會成功的片段上。
把重試範圍收斂到單一片段,也代表要多一層機制去判斷「這批片段最後到底有沒有全部成功」——不能假設「跑完迴圈就等於全部成功」,因為某些片段可能是在上限次數用完後被記錄失敗、但流程仍然往下走。這一層驗證會在後面第三部關於「合併前先確認片段湊齊」的部分再展開。
你的專案裡處理過批次任務嗎?失敗時是整批重跑,還是只重試失敗的那幾筆?如果是整批重跑,重跑的成本你估算過嗎?
明天講斷點續傳:怎麼判斷「這個片段其實已經下載完成」,不需要重新下載一次。