本文实现了一个重试的装饰器,并且使用了指数退避算法。指数退避算法实现还是很简单的。先上代码再详细解释。
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