retrying简介
retrying功能是为一些任务添加重试机制,只需要指定不同的参数就能便捷实现重试机制逻辑代码。
retrying特点
1.通用装饰器API
2.指定停止条件(即按尝试次数限制)
3.指定等待条件(即,尝试之间的指数退避休眠)
4.自定义异常重试
5.自定义对预期返回结果的重试
retrying安装
由于retrying是不包含在标准库上,需要使用额外安装:
pip install retrying
retrying参数解析
stop_max_attempt_number 指定重试的次数,默认是5
stop_max_delay 指定重试超时时间,默认是100,单位是ms
wait_fixed 指定每次重试的时间间隔,默认是1000,单位是ms
wait_random_min=None 指定每次重试最小时间,默认为0
wait_random_max=None, 指定每次重试最大时间,默认为1000
wait_incrementing_start 指定每次重试递增的开始时间
wait_incrementing_increment 指定每次重试时间的递增幅度
wait_exponential_multiplier 指定每次重试时间的递增幅度,跟上面的参数算法差异
wait_exponential_max 指定每次重试时间的递增幅度的最大值
retry_on_exception 指定每次出现异常执行的函数
retry_on_result 指定程序运行正常执行的函数
wrap_exception 出现异常后返回的异常类型,True是返回原异常,False返回RetryError
stop_func 自定义停止重试的条件,传入参数是一个函数
wait_func 自定义每次重试的时间间隔,传入参数是一个函数
wait_jitter_max 指定每次重试时间抖动值
stop 指定重试停止后,执行Retrying对象的成员函数
wait 指定重试间隔,执行Retrying对象的成员函数
retrying简单例子
指定重试的次数或者重试的超时时间
import random
from retrying import retry
@retry(stop_max_attempt_number=5, stop_max_delay=2000)
def do_something_unreliable():
rad = random.randint(0, 20)
print("rad = %d" % rad)
if rad > 1:
raise IOError("Broken sauce, everything is hosed!!!111one")
else:
return "Awesome sauce!"
print(do_something_unreliable())
随机获取一个整形数rad = random.randint(0, 20)
,每次判断等于0则报错进行重试。当返回错误的时候则进行重试,重试超过5次或者重试超过两秒则停止重试,程序结束。
指定重试间隔时间
import random
from retrying import retry
@retry(wait_fixed=2000)
def do_something_unreliable():
rad = random.randint(0, 20)
print("rad = %d" % rad)
if rad > 1:
raise IOError("Broken sauce, everything is hosed!!!111one")
else:
return "Awesome sauce!"
print(do_something_unreliable())
@retry(wait_fixed=2000)
指定每次重试的间隔为2秒,由于没有指定重试的限制,所以一直重试直到随机值等于0。
自定义函数的监听重试后的出现异常或者正常运行。
import random
from retrying import retry
def retry_on_result(value):
print("程序正常返回结果")
return False #return True 就算返回正常结果还会继续不断重试
def retry_on_exception(e):
print("出现异常")
return True #return False 重试中断
@retry(retry_on_result=retry_on_result, retry_on_exception=retry_on_exception)
def do_something_unreliable():
rad = random.randint(0, 20)
print("rad = %d" % rad)
if rad > 1:
raise IOError("Broken sauce, everything is hosed!!!111one")
else:
return "Awesome sauce!"
print(do_something_unreliable())
从示例程序看出retry_on_result
是正常结果才调用的,retry_on_exception
每次出现异常会调用。
retrying源码分析
retrying的源码只有200多行,比较容易理解,先看@retry
def retry(*dargs, **dkw):
"""
Decorator function that instantiates the Retrying object
@param *dargs: positional arguments passed to Retrying object
@param **dkw: keyword arguments passed to the Retrying object
"""
# support both @retry and @retry() as valid syntax
#兼容@retry装饰器有参数和无参数的情况
if len(dargs) == 1 and callable(dargs[0]):
#无参数
def wrap_simple(f):
@six.wraps(f)
def wrapped_f(*args, **kw):
return Retrying().call(f, *args, **kw)
return wrapped_f
return wrap_simple(dargs[0])
else:
#有参数
def wrap(f):
@six.wraps(f)
def wrapped_f(*args, **kw):
return Retrying(*dargs, **dkw).call(f, *args, **kw)
return wrapped_f
return wrap
retry
函数首先判断@retry
装饰器是否带参数,这个在上篇文章已经介绍过,这个不在重述。
然后直接调用Retrying
对象的call
函数
call
函数
def call(self, fn, *args, **kwargs):
#记录第一次重试的开始时间
start_time = int(round(time.time() * 1000))
#记录重试的次数
attempt_number = 1
while True:
try:
#把传进来函数fn和参数封装到一个Attempt对象中,并执行fn函数
attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
except:
#执行fn函数如果出现异常也会把异常封装成Attempt对象中
tb = sys.exc_info()
attempt = Attempt(tb, attempt_number, True)
#判断是否存在异常,如果不存在则返回正常结果,
#attempt.get()把得到fn(*args, **kwargs)的执行结果结束循环
if not self.should_reject(attempt):
return attempt.get(self._wrap_exception)
#计算第一次重试到当次重试的时间差
delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time
#判断重试是否达到停止的条件
if self.stop(attempt_number, delay_since_first_attempt_ms):
#raise异常,程序重试结束
if not self._wrap_exception and attempt.has_exception:
# get() on an attempt with an exception should cause it to be raised, but raise just in case
raise attempt.get()
else:
raise RetryError(attempt)
else:
#如果重试没有被停止,则计算下一次重试的等待时间,也就是每次重试的间隔
sleep = self.wait(attempt_number, delay_since_first_attempt_ms)
if self._wait_jitter_max:
jitter = random.random() * self._wait_jitter_max #1
sleep = sleep + max(0, jitter)
time.sleep(sleep / 1000.0)
#重试次数累加1
attempt_number += 1
结合源码和注释,大致了解retrying
核心的重试机制。并且出现了重试等待时间的第一个参数wait_jitter_max
(在Retrying的构造函数被赋值到self._wait_jitter_max
),因此如果传入wait_jitter_max
那会在原来时间上加random.random() * self._wait_jitter_max
,值越大等待时间越大。
再看看self.stop(attempt_number, delay_since_first_attempt_ms)
的代码,直接跳到Retrying
的构造函数上:
########################## 省略了一部分代码#########################
# TODO add chaining of stop behaviors
# stop behavior
stop_funcs = []
#如果传入重试的停止次数stop_max_attempt_number,
#则把self.stop_after_attempt成员函数加入到stop_funcs列表上
if stop_max_attempt_number is not None:
stop_funcs.append(self.stop_after_attempt)
#如果传入重试的的超时时间stop_max_delay
#则把self.stop_after_delay成员函数加入到stop_funcs列表上
if stop_max_delay is not None:
stop_funcs.append(self.stop_after_delay)
#如果传入自定义停止重试函数stop_func
#则把stop_func函数赋值self.stop
if stop_func is not None:
self.stop = stop_func
#如果没有传入stop_func,则遍历stop_funcs函数
#这里使用lambda函数赋值给self.stop
#any是一个内置函数,加入列表中有一个为True,则返回True
#说明了只要满足stop_max_attempt_number 或者 stop_max_delay 的任意一个条件都会停止重试
elif stop is None:
self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs)
#把Retry的内置函数赋值给self.stop
else:
self.stop = getattr(self, stop)
通过这部分的注释可以对停止重试的参数有进一步的了解
接着看看self.wait(attempt_number, delay_since_first_attempt_ms)的代码,也是在Retrying
的构造函数上:
# TODO add chaining of wait behaviors
# wait behavior
########################## 省略了一部分代码#########################
wait_funcs = [lambda *args, **kwargs: 0]
if wait_fixed is not None:
wait_funcs.append(self.fixed_sleep)
if wait_random_min is not None or wait_random_max is not None:
wait_funcs.append(self.random_sleep)
if wait_incrementing_start is not None or wait_incrementing_increment is not None:
wait_funcs.append(self.incrementing_sleep)
if wait_exponential_multiplier is not None or wait_exponential_max is not None:
wait_funcs.append(self.exponential_sleep)
if wait_func is not None:
self.wait = wait_func
elif wait is None:
self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs)
else:
self.wait = getattr(self, wait)
从源码可以看出,self.wait
的处理逻辑跟self.stop
是一致,却被就是函数列表加入不同的成员函数,这里不再详细解释。
剩下的参数可以自行查看代码,例如self.should_reject(attempt)
会看到retry_on_exception
,retry_on_result
这两参数的解释,总体来说retrying库的源码还是比较好理解的,重点还是在对装饰器的掌握上。
总结
retrying库只需要一行代码就可以实现重试机制逻辑,而且还可以指定停止条件、指定等待条件、.自定义异常重试和自定义对预期返回结果的重试。在使用上非常的便捷,能简化有特殊要求的代码结构。