本文實現了一個重試的裝飾器,並且使用了指數退避算法。指數退避算法實現還是很簡單的。先上代碼再詳細解釋。
1、指數退避算法
2、重試裝飾器retry實現
# -*- coding:utf-8 -*- import time from random import randint from struct import Result, ProcedureException def retry(max_retries=3, max_wait_interval=10, period=1, rand=False): def _retry(func): def __retry(*args, **kwargs): MAX_RETRIES = max_retries MAX_WAIT_INTERVAL = max_wait_interval PERIOD = period RAND = rand retries = 0 error = None while retries < MAX_RETRIES: try: result = func(*args, **kwargs) if result.code == Result.ERROR: raise ProcedureException("procedure occur error") if result.code == Result.TIMEOUT: raise ProcedureException("procedure request time out") if result.code == Result.SUCCESS: return result except Exception, ex: error = ex finally: sleep_time = min(2 ** retries * PERIOD if not RAND else randint(0, 2 ** retries) * PERIOD, MAX_WAIT_INTERVAL) time.sleep(sleep_time) retries += 1 print "第", retries, "次重試, ", "等待" , sleep_time, "秒" if retries == MAX_RETRIES: if error: raise error else: raise ProcedureException("unknown") return __retry return _retry
這里我們自己定義了兩個東西:
1)枚舉類Result,標識過程調用的狀態,其中有三個狀態,成功SUCCESS,失敗ERROR,超時TIMEOUT;
2)異常ProcedureException,在retry裝飾器中我們判斷了狀態,如果是失敗和超時場景,我們將會拋出這個異常。
這兩個東西的實現如下
from enum import Enum, unique @unique class Result(Enum): SUCCESS = 0 TIMEOUT = 1 ERROR = 2 class ProcedureException(Exception): def __init__(self, message): Exception.__init__(self, message)
retry裝飾器會重試以下兩個場景:
1)Procedure函數func出現異常:TIMEOUT和ERROR
2)未知異常:Procedure函數func可以拋出未能處理的異常,例如func函數可能是網絡讀寫,遇到網絡超時,鏈接斷開等,拋出timeout或者broken pipe。
是否隨機:
1)不隨機,將會以2**retries,作為重試時間
2)隨機,將會在(0,2**retries)之間隨機一個數,作為重試時間
其實指數退避算法就是使用隨機“抖動”的方式來解決高並發場景下信道碰撞的,但是我們的應用場景也有需要持續增加重試間隔(而不是增加幾率)的情況。
3、測試一下
我們測試兩個場景,重試10次和隨機,重試5次不隨機。
1)重試10次,隨機,最大間隔10s
# -*- coding:utf-8 -*- from decorator import retry from struct import Result @retry(rand=True, max_retries=10, max_wait_interval=10) def do_something(): class result(object): def __init__(self, code): self.code = code print "########## 調用結果", Result.ERROR, " ############" return result(Result.ERROR) do_something()
輸出結果
/Users/didi/anaconda/bin/python /Users/didi/test/pythoneer/retry/test.py ########## 調用結果 Result.ERROR ############ 第 1 次重試, 等待 0 秒 ########## 調用結果 Result.ERROR ############ 第 2 次重試, 等待 1 秒 ########## 調用結果 Result.ERROR ############ 第 3 次重試, 等待 2 秒 ########## 調用結果 Result.ERROR ############ 第 4 次重試, 等待 0 秒 ########## 調用結果 Result.ERROR ############ 第 5 次重試, 等待 10 秒 ########## 調用結果 Result.ERROR ############ 第 6 次重試, 等待 10 秒 ########## 調用結果 Result.ERROR ############ 第 7 次重試, 等待 10 秒 ########## 調用結果 Result.ERROR ############ 第 8 次重試, 等待 10 秒 ########## 調用結果 Result.ERROR ############ 第 9 次重試, 等待 10 秒 ########## 調用結果 Result.ERROR ############ Traceback (most recent call last): 第 10 次重試, 等待 10 秒 File "/Users/didi/test/pythoneer/retry/test.py", line 14, in <module> do_something() File "/Users/didi/test/pythoneer/retry/decorator.py", line 36, in __retry if error: struct.ProcedureException: procedure occur error
2)重試5次,不隨機,最大間隔10s
# -*- coding:utf-8 -*- from decorator import retry from struct import Result @retry(rand=False, max_retries=5, max_wait_interval=10) def do_something(): class result(object): def __init__(self, code): self.code = code print "########## 調用結果", Result.ERROR, " ############" return result(Result.ERROR) do_something()
輸出結果
/Users/didi/anaconda/bin/python /Users/didi/test/pythoneer/retry/test.py ########## 調用結果 Result.ERROR ############ 第 1 次重試, 等待 1 秒 ########## 調用結果 Result.ERROR ############ 第 2 次重試, 等待 2 秒 ########## 調用結果 Result.ERROR ############ 第 3 次重試, 等待 4 秒 ########## 調用結果 Result.ERROR ############ 第 4 次重試, 等待 8 秒 ########## 調用結果 Result.ERROR ############ 第 5 次重試, 等待 10 秒 Traceback (most recent call last): File "/Users/didi/test/pythoneer/retry/test.py", line 14, in <module> do_something() File "/Users/didi/test/pythoneer/retry/decorator.py", line 37, in __retry raise error struct.ProcedureException: procedure occur error