退避和重试的函数装饰
项目描述
backoff
退避和重试的函数装饰
此模块提供函数装饰器,可用于包装函数以便在满足某些条件之前进行重试。它旨在在访问不可靠资源且可能存在间歇性失败的情况下使用,例如网络资源和外部API。更普遍地说,它也可能用于动态轮询资源以获取外部生成的内容。
装饰器支持同步代码的常规函数和asyncio的协程。
示例
由于Kenneth Reitz的requests模块已成为Python中同步HTTP客户端的事实标准,以下网络示例使用它编写,但backoff模块并不要求使用它。
@backoff.on_exception
当抛出指定异常时,使用on_exception装饰器进行重试。以下是一个在抛出任何requests异常时使用指数退避的示例
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException)
def get_url(url):
return requests.get(url)
装饰器还可以接受一个异常元组,用于在需要为多个异常类型实现相同的退避行为时。
@backoff.on_exception(backoff.expo,
(requests.exceptions.Timeout,
requests.exceptions.ConnectionError))
def get_url(url):
return requests.get(url)
放弃条件
可选的关键字参数可以指定放弃的条件。
关键字参数 max_time 指定在放弃之前可以经过的最大总时间(以秒为单位)。
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
max_time=60)
def get_url(url):
return requests.get(url)
关键字参数 max_tries 指定在放弃之前对目标函数进行调用的最大次数。
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
max_tries=8,
jitter=None)
def get_url(url):
return requests.get(url)
在某些情况下,可能需要检查引发的异常实例本身以确定它是否是可重试的条件。可以使用 giveup 关键字参数指定一个函数,该函数接受异常并返回一个真值,如果异常不应该重试。
def fatal_code(e):
return 400 <= e.response.status_code < 500
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
max_time=300,
giveup=fatal_code)
def get_url(url):
return requests.get(url)
默认情况下,当发生放弃事件时,相关的异常将被重新抛出,因此调用 on_exception 装饰的函数的代码可能仍然需要进行异常处理。可以使用 raise_on_giveup 关键字参数选择性地禁用此行为。
在下面的代码中,当发生放弃时,将不会引发 requests.exceptions.RequestException。请注意,在这种情况下,装饰的函数将返回 None,而不管 on_exception 处理器中的逻辑如何。
def fatal_code(e):
return 400 <= e.response.status_code < 500
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
max_time=300,
raise_on_giveup=False,
giveup=fatal_code)
def get_url(url):
return requests.get(url)
这对于非关键代码很有用,尽管你仍然希望在 backoff.on_exception 内部重试代码,但即使所有重试都失败,也希望继续执行。
@backoff.on_predicate
on_predicate 装饰器用于在目标函数的返回值满足特定条件时进行重试。当需要轮询外部生成内容时,这可能很有用。
以下是一个示例,当目标函数的返回值为空列表时,使用斐波那契数列退避。
@backoff.on_predicate(backoff.fibo, lambda x: x == [], max_value=13)
def poll_for_messages(queue):
return queue.get()
初始化等待生成器时传递了额外的关键字参数,因此上面的 max_value 参数在初始化 fibo 生成器时作为关键字参数传递。
如果没有指定,默认的谓词参数为假值测试,因此上述内容可以更简洁地写为
@backoff.on_predicate(backoff.fibo, max_value=13)
def poll_for_message(queue):
return queue.get()
更简单地说,可以定义一个函数,它每秒继续轮询,直到获得一个非假值的结果,如下所示
@backoff.on_predicate(backoff.constant, jitter=None, interval=1)
def poll_for_message(queue):
return queue.get()
禁用抖动以保持轮询频率固定。
@backoff.runtime
您还可以使用 backoff.runtime 生成器来利用装饰方法返回的值或抛出的异常。
例如,使用响应的 Retry-After 标头中的值
@backoff.on_predicate(
backoff.runtime,
predicate=lambda r: r.status_code == 429,
value=lambda r: int(r.headers.get("Retry-After")),
jitter=None,
)
def get_url():
return requests.get(url)
抖动
可以通过将抖动算法作为 jitter 关键字参数提供给任一退避装饰器。此参数应是一个接受原始未处理的退避值并返回其抖动版本的函数。
从版本 1.2 开始,默认抖动函数 backoff.full_jitter 实现了 AWS 架构博客中定义的“完全抖动”算法,见 指数退避和抖动 文章。请注意,使用此算法时,等待生成器产生的时间实际上是等待的最大时间。
backoff 的早期版本默认将一些随机毫秒数(最多 1 秒)添加到原始睡眠值。如果需要,此行为现在作为 backoff.random_jitter 可用。
使用多个装饰器
退避装饰器也可以组合起来,为不同的情况指定不同的退避行为。
@backoff.on_predicate(backoff.fibo, max_value=13)
@backoff.on_exception(backoff.expo,
requests.exceptions.HTTPError,
max_time=60)
@backoff.on_exception(backoff.expo,
requests.exceptions.Timeout,
max_time=300)
def poll_for_message(queue):
return queue.get()
运行时配置
装饰器函数 on_exception 和 on_predicate 通常在导入时进行评估。当关键字参数作为常量值传递时,这是可以的,但是假设我们想在运行时查询配置选项的字典,这些选项在运行时才可用。相关值在导入时不可用。相反,装饰器函数可以传递可调用对象,这些对象在运行时评估以获取值。
def lookup_max_time():
# pretend we have a global reference to 'app' here
# and that it has a dictionary-like 'config' property
return app.config["BACKOFF_MAX_TIME"]
@backoff.on_exception(backoff.expo,
ValueError,
max_time=lookup_max_time)
事件处理器
两个退避装饰器都可选地接受事件处理器函数,使用关键字参数 on_success、on_backoff 和 on_giveup。这可能有助于报告统计信息或执行其他自定义日志记录。
处理器必须是接受字典参数的可调用对象,该字典包含调用的详细信息。有效的键包括
target:被调用函数或方法的引用
args:传递给函数的位置参数
kwargs:传递给函数的关键字参数
tries:迄今为止的调用尝试次数
elapsed:迄今为止经过的时间(以秒为单位)
wait:要等待的秒数(仅 on_backoff 处理器)
value:触发退避的值(仅 on_predicate 装饰器)
可以实现一个打印退避事件详细信息的处理器,如下所示
def backoff_hdlr(details):
print ("Backing off {wait:0.1f} seconds after {tries} tries "
"calling function {target} with args {args} and kwargs "
"{kwargs}".format(**details))
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
on_backoff=backoff_hdlr)
def get_url(url):
return requests.get(url)
每个事件类型多个处理器
在所有情况下,也接受处理器函数的迭代器,按顺序调用。例如,您可能将简单的处理器函数列表作为 on_backoff 关键字参数的值提供。
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
on_backoff=[backoff_hdlr1, backoff_hdlr2])
def get_url(url):
return requests.get(url)
获取异常信息
对于 on_exception 装饰器,所有 on_backoff 和 on_giveup 处理器都在处理异常的 except 块内部被调用。因此,异常信息可以通过 Python 标准库中的 sys.exc_info() 或 traceback 模块提供给处理器函数。异常也存在于传递给处理器的 details 字典中的 exception 键中。
异步代码
Backoff 支持在 Python 3.5 及以上版本中执行异步操作。
要在基于 asyncio 的异步代码中使用 backoff,只需将 backoff.on_exception 或 backoff.on_predicate 应用于协程。您还可以使用协程作为 on_success、on_backoff 和 on_giveup 事件处理器的函数,其他接口保持不变。
以下示例使用 aiohttp 异步 HTTP 客户端/服务器库。
@backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60)
async def get_url(url):
async with aiohttp.ClientSession(raise_for_status=True) as session:
async with session.get(url) as response:
return await response.text()
日志配置
默认情况下,退避和重试尝试被记录到“backoff”记录器。默认情况下,该记录器配置为 NullHandler,因此除非您配置处理器,否则将没有输出。程序化地,这可能可以通过以下简单方法实现:
logging.getLogger('backoff').addHandler(logging.StreamHandler())
默认日志级别是 INFO,这意味着在任何重试事件发生时都会记录日志。如果您只想在放弃事件发生时记录日志,请将记录器级别设置为 ERROR。
logging.getLogger('backoff').setLevel(logging.ERROR)
您还可以使用 logger 关键字参数指定一个不同的记录器。如果指定了一个字符串值,则将通过名称查找记录器。
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
logger='my_logger')
# ...
也可以直接指定 Logger(或 LoggerAdapter)对象。
my_logger = logging.getLogger('my_logger')
my_handler = logging.StreamHandler()
my_logger.addHandler(my_handler)
my_logger.setLevel(logging.ERROR)
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException,
logger=my_logger)
# ...
可以通过指定 logger=None 完全禁用默认日志记录。在这种情况下,如果需要,可以通过使用自定义事件处理器定义替代的日志记录行为。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分发
构建分发
backoff-2.2.1.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba |
|
MD5 | b91bb50f190d683e166b9cdf13252493 |
|
BLAKE2b-256 | 47d75bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619 |
backoff-2.2.1-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8 |
|
MD5 | fbf5b674712ef46c2fed4c12073193cd |
|
BLAKE2b-256 | df73b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09 |