python重试库retryiny源码剖析
上篇博文介绍了常见需要进行请求重试的场景,本篇博文试着剖析有名的python第三方库retrying源码。
在剖析其源码之前,有必要讲一下retrying的用法,方便理解。
安装:
pip install retrying
或者
easy_install retrying
一些用法实例如下:
#example 1
from retrying import retry @retry
def never_give_up_never_surrender():
print "一直重试且两次重试之间无需等待"
#example 2
from retrying import retry @retry(stop_max_attempt_number=7)
def stop_after_7_attempts():
print "重试七次后停止"
#example 3
from retrying import retry @retry(stop_max_delay=10000)
def stop_after_10_s():
print "十秒之后停止重试"
#example 4
from retrying import retry @retry(wait_fixed=2000)
def wait_2_s():
print "每次重试间隔两秒"
#example 5
from retrying import retry @retry(wait_random_min=1000, wait_random_max=2000)
def wait_random_1_to_2_s():
print "每次重试随机等待1到2秒"
#example 6
from retrying import retry @retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
def wait_exponential_1000():
print "指数退避,每次重试等待 2^x * 1000 毫秒,上限是10秒,达到上限后每次都等待10秒"
#example 7
def retry_if_io_error(exception):
"""Return True if we should retry (in this case when it's an IOError), False otherwise"""
return isinstance(exception, IOError) @retry(retry_on_exception=retry_if_io_error)
def might_io_error():
print "IO异常则重试,并且将其它异常抛出" @retry(retry_on_exception=retry_if_io_error, wrap_exception=True)
def only_raise_retry_error_when_not_io_error():
print "IO异常则重试,并且将其它异常用RetryError对象包裹"
#exampe 8,根据返回结果判断是否重试
def retry_if_result_none(result):
"""Return True if we should retry (in this case when result is None), False otherwise"""
return result is None @retry(retry_on_result=retry_if_result_none)
def might_return_none():
print "若返回结果为None则重试"
上面八个例子是retrying的用法,只需在要重试的方法上加上@retry注解,并以相应的条件为参数即可,那么@retry背后到底是如何实现的呢?下面给出@retry注解实现的方法。
#装饰器模式,对需要重试的函数,利用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不带括号时走这条路径,dargs[0]为retry注解的函数,返回函数对象wrapped_f
if len(dargs) == 1 and callable(dargs[0]):
def wrap_simple(f): @six.wraps(f)#注解用于将函数f的签名复制到新函数wrapped_f
def wrapped_f(*args, **kw):
return Retrying().call(f, *args, **kw) return wrapped_f return wrap_simple(dargs[0]) else:#当用法为@retry()带括号时走这条路径,返回函数对象wrapped_f
def wrap(f): @six.wraps(f)#注解用于将函数f的签名复制到新函数wrapped_f
def wrapped_f(*args, **kw):
return Retrying(*dargs, **dkw).call(f, *args, **kw) return wrapped_f return wrap
当用@retry标记函数时,例如实例1,其实执行了
never_give_up_never_surrender = retry(never_give_up_never_surrender)
此时的never_give_up_never_surrender函数实际上是10-19行返回的wrapped_f函数,后续对never_give_up_never_surrender函数的调用都是调用的14行的wrapped_f函数。
当使用@retry()或者带参数的@retry(params)时,如实例2,实际执行了:
stop_after_7_attempts = retry(stop_max_attempt_number)(stop_after_7_attempts)
此时的stop_after_7_attempts函数实际上是22-29行的wrapped_f函数,后续对stop_after_7_attempts函数的调用都是对25行的wrapped_f函数调用。
可以看到实际上@retry将对需要重试的函数调用转化为对Retrying类中call函数的调用,重试逻辑也在这个函数实现,实现对逻辑代码的无侵入,代码如下:
def call(self, fn, *args, **kwargs):
start_time = int(round(time.time() * 1000))
attempt_number = 1
while True:
#_before_attempts为@retry传进来的before_attempts,在每次调用函数前执行一些操作
if self._before_attempts:
self._before_attempts(attempt_number) try:#Attempt将函数执行结果或者异常信息以及执行次数作为内部状态,用True或False标记是内部存的值正常执行结果还是异常
attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
except:
tb = sys.exc_info()#获取异常堆栈信息,sys.exc_info()返回type(异常类型), value(异常说明), traceback(traceback对象,包含更丰富的信息)
attempt = Attempt(tb, attempt_number, True) if not self.should_reject(attempt):#根据本次执行结果或异常类型判断是否应该停止
return attempt.get(self._wrap_exception) if self._after_attempts:#_after_attempts为@retry传进来的after_attempts,在每次调用函数后执行一些操作
self._after_attempts(attempt_number) delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time
if self.stop(attempt_number, delay_since_first_attempt_ms):#根据重试次数和延迟判断是否应该停止
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:#不停止则等待一定时间,延迟时间根据wait函数返回值和_wait_jitter_max计算
sleep = self.wait(attempt_number, delay_since_first_attempt_ms)
if self._wait_jitter_max:
jitter = random.random() * self._wait_jitter_max
sleep = sleep + max(0, jitter)
time.sleep(sleep / 1000.0) attempt_number += 1 #进行下一轮重试
9-13行将函数执行返回结果或异常存入Attempt对象attempt中,Attempt类如下:
class Attempt(object):
"""
An Attempt encapsulates a call to a target function that may end as a
normal return value from the function or an Exception depending on what
occurred during the execution.
"""
#value值为函数返回结果或异常,根据has_exception判断
def __init__(self, value, attempt_number, has_exception):
self.value = value
self.attempt_number = attempt_number
self.has_exception = has_exception
#返回函数执行结果或异常,并根据wrap_exception参数对异常用RetryError包裹
def get(self, wrap_exception=False):
"""
Return the return value of this Attempt instance or raise an Exception.
If wrap_exception is true, this Attempt is wrapped inside of a
RetryError before being raised.
"""
if self.has_exception:
if wrap_exception:
raise RetryError(self)
else:#重新构造原异常抛出
six.reraise(self.value[0], self.value[1], self.value[2])
else:
return self.value def __repr__(self):
if self.has_exception:
return "Attempts: {0}, Error:\n{1}".format(self.attempt_number, "".join(traceback.format_tb(self.value[2])))
else:
return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value)
15行根据should_reject函数的返回值判断是否停止重试,代码如下:
def should_reject(self, attempt):
reject = False
#假如异常在retry_on_exception参数中返回True,则重试,默认不传异常参数时,发生异常一直重试
if attempt.has_exception:
reject |= self._retry_on_exception(attempt.value[1])
else:#假如函数返回结果在retry_on_result参数函数中为True,则重试
reject |= self._retry_on_result(attempt.value) return reject
22行根据重试次数和延迟判断是否应该停止重试,self.stop的赋值代码在构造函数中,代码片段如下:
stop_funcs = []
if stop_max_attempt_number is not None:
stop_funcs.append(self.stop_after_attempt) if stop_max_delay is not None:
stop_funcs.append(self.stop_after_delay) if stop_func is not None:
self.stop = stop_func elif stop is None:#执行次数和延迟任何一个达到限制则停止
self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs) else:
self.stop = getattr(self, stop)
def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Stop after the previous attempt >= stop_max_attempt_number."""
return previous_attempt_number >= self._stop_max_attempt_number def stop_after_delay(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Stop after the time from the first attempt >= stop_max_delay."""
return delay_since_first_attempt_ms >= self._stop_max_delay
29-33行等待一段时间再次重试,其中延迟时间重点是根据29行的wait函数计算,wait函数在构造函数中赋值,代码片段如下:
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)
其中最值得研究的是指数退避延迟时间计算方法,函数为exponential_sleep,代码如下:
def exponential_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):
exp = 2 ** previous_attempt_number
result = self._wait_exponential_multiplier * exp #延迟时间为_wait_exponential_multiplier*2^x
if result > self._wait_exponential_max:#假如大于退避上限_wait_exponential_max,则result为上限值
result = self._wait_exponential_max
if result < 0:
result = 0
return result
python重试库retryiny源码剖析的更多相关文章
- python部分重点底层源码剖析
Python源码剖析—Set容器(hashtable实现) python源码剖析(内存管理和垃圾回收)
- 一个Python开源项目-腾讯哈勃沙箱源码剖析(上)
前言 2019年来了,2020年还会远吗? 请把下一年的年终奖发一下,谢谢... 回顾逝去的2018年,最大的改变是从一名学生变成了一位工作者,不敢说自己多么的职业化,但是正在努力往那个方向走. 以前 ...
- python源码剖析学习记录-01
学习<Python源码剖析-深度探索动态语言核心技术>教程 Python总体架构,运行流程 File Group: 1.Core Modules 内部模块,例如:imp ...
- 《python解释器源码剖析》第0章--python的架构与编译python
本系列是以陈儒先生的<python源码剖析>为学习素材,所记录的学习内容.不同的是陈儒先生的<python源码剖析>所剖析的是python2.5,本系列对应的是python3. ...
- 【Python源码剖析】对象模型概述
Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...
- Python源码剖析|百度网盘免费下载|Python新手入门|Python新手学习资料
百度网盘免费下载:Python源码剖析|新手免费领取下载 提取码:g78z 目录 · · · · · · 第0章 Python源码剖析——编译Python0.1 Python总体架构0.2 Pyth ...
- socket_server源码剖析、python作用域、IO多路复用
本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...
- Python 源码剖析(一)【python对象】
处于研究python内存释放问题,在阅读部分python源码,顺便记录下所得.(基于<python源码剖析>(v2.4.1)与 python源码(v2.7.6)) 先列下总结: ...
- Golang 源码剖析:log 标准库
Golang 源码剖析:log 标准库 原文地址:Golang 源码剖析:log 标准库 日志 输出 2018/09/28 20:03:08 EDDYCJY Blog... 构成 [日期]<空格 ...
随机推荐
- sjms-3 结构型模式
结构型模式 适配器模式 内容:将一个类的接口转换成客户希望的另一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.两种实现方式:类适配器:使用多继承对象适配器:使用组合 角色 ...
- linux系统中使用socket直接发送ARP数据
这个重点是如这样创建socket: sock_send = socket ( PF_PACKET , SOCK_PACKET , htons ( ETH_P_ARP) ) ; 其后所有收发的数据都是 ...
- OC数组的简单使用、NSArray
和上一篇文章一样,数组的重要性不言而喻,在OC编程的过程中我们会不断的使用到NSArray,和C语言不同的是,我们这里的数组只能存OC对象类型,不能存C语言基本数据类型,也不能存NSNull类型,但是 ...
- 阿里云,未找到或无法访问服务器.请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接.
阿里云主机使用SQL Server作为数据库服务器,连接数据库时候出现错误. 按照网上经验,检查SQL服务是否开启,sa用户权限,数据库安全性和连接权限: 关闭服务器防火墙,修改入站规则: 检查阿里云 ...
- 软件测试-homework3
printPrime()代码: public static void printPrimes (int n) { int curPrime; // Value currently considered ...
- mysql中删除重复记录,只保留一条
表结构如下: mysql> desc test1; +--------------+------------------+------+-----+---------+------------- ...
- Jenkins的初级应用(2)-Invoke Phing targets
Invoke Phing targets这个插件主要是读取xml形式包括自动化测试打包部署的配置文件,然后根据流程走下来.用phing命令读取并执行xml配置文件,然后执行定义的步骤.比如check. ...
- CSS中的px与物理像素、逻辑像素、1px边框问题
一直不太清楚CSS中的1px与逻辑像素.物理像素是个什么关系(作为一名前端感觉很惭愧 -_-!),今天终于花时间彻底弄清楚了,其实弄清楚之后就觉得事情很简单,但也只有在弄清楚之后,才会觉得简单(语出& ...
- 基于Fusioncharts的报表统计
先了解fusioncharts插件,fusioncharts是一款基于XML和flash的报表组件,支持Java.PHP.AngularJS等等开发语言,所以,开发出来,加入swf文件,就可以出现动态 ...
- Python爬虫学习之正则表达式爬取个人博客
实例需求:运用python语言爬取http://www.eastmountyxz.com/个人博客的基本信息,包括网页标题,网页所有图片的url,网页文章的url.标题以及摘要. 实例环境:pytho ...