retry重试常见场景及实现

当咱们的代码是有访问网络相关的操做时,好比http请求或者访问远程数据库,常常可能会发生一些错误,有些错误可能从新去发送请求就会成功,本文分析常见可能须要重试的场景,并最后给出python代码实现。html

  常见异常分红两种,一种是请求传输过程出错,另外一种是服务端负载太高致使错误。
  对于第一种错误,可能请求还未到服务端处理程序就已经返回。
  HTTP请求错误:python

  •   DNSError:域名不能解析出ip地址,多是服务端从新部署到其它地方。
  •   ConnectionError:请求创建握手链接的过程出错,多是请求时的网络质量比较差。

  访问数据库错误:mysql

  • OperationalError:与数据库服务器的链接丢失或链接失败时。好比访问PostgreSQL返回码
1 Class 08 — Connection Exception
2 08000 connection_exception
3 08003 connection_does_not_exist
4 08006 connection_failure
5 08001 sqlclient_unable_to_establish_sqlconnection
6 08004 sqlserver_rejected_establishment_of_sqlconnection
  • ProtocolError:属于Redis中的一种常见错误, 当Redis服务器收到一个字节序列并转换为无心义的操做时,会引起异常。因为您在部署以前测试了软件,所以编写错误的代码不太可能发生错误。多是传输层出现了错误。

   对于第二类错误,服务器负载太高致使。对于HTTP请求,可根据状态码识别:git

  •   408 Request Timeout: 当服务器花费不少时间处理您的请求而不能在等待时间返回。可能的缘由:资源被大量传入请求所淹没。一段时间后等待和重试多是最终完成数据处理的好策略。
  •   429 Too Many Requests: 在一段时间内发送的请求数量超过服务器容许的数量。服务器使用的这种技术称为速率限制。良好的服务端应该返回Retry-After标头,它提供建议在下一个请求以前须要等待多长时间。
  •   500 Internal Server Error: 这是最臭名昭着的HTTP服务器错误。错误缘由多样性,对于发生的全部未捕获的异常,都返回这种错误。对于这种错误,应了解背后的缘由再决定是否重试。
  •   503 Service Unavailable:因为临时过载,服务当前没法处理请求。通过一段时间的推迟,能获得缓解。
  •   504 Gateway Timeout:相似于408请求超时,网关或反向代理不能及时从上游服务器获得响应。

   对于数据库访问:github

  • OperationalError. 对于PostgreSQL和MySQL,它还包括不受软件工程师控制的故障。例如:处理期间发生内存分配错误,或没法处理事务。我建议重试它们。
  • IntegrityError: 当违反外键约束时能够引起它,例如当您尝试插入依赖于记录B的记录A时。因为系统的异步性质,可能尚未添加记录B.在这种状况下,进行重试。另外一方面,当您尝试添加记录致使重复惟一键时,也会引起这种异常,这种状况下不须要重试。那么如何去识别这种状况,DBMS能返回状态码,假如mysql驱动能在状态码和异常类之间映射,就能识别这种须要重试的场景,在python3中,库pymysql能够在数据库返回码和异常之间映射。地址以下:

      constants for MySQL errors
      the mapping between exception types in PyMYSQL and error codes.sql

  本文以网络IO为例,利用python装饰器实现重试机制。用fetch函数去发送http请求下载网页
  数据库

复制代码
# Example is taken from http://aiohttp.readthedocs.io/en/stable/#getting-started
import aiohttp
import asyncio

async def fetch(session, url):
async with session.get(url) as response:
return await response.text()

# Client code, provided for reference
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://python.org')
print(html)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
复制代码

 

  fetch函数并非可靠的服务,可能存在失败的状况,这时候根据上文所列的状况实现重试机制,代码以下:
  服务器

import aiohttp
@retry(aiohttp.DisconnectedError, aiohttp.ClientError,
aiohttp.HttpProcessingError)
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()

 

  retry实现以下,利用装饰器模式
  网络

复制代码
import logging

from functools import wraps

log = logging.getLogger(__name__)

def retry(*exceptions, retries=3, cooldown=1, verbose=True):
    """Decorate an async function to execute it a few times before giving up.
    Hopes that problem is resolved by another side shortly.

    Args:
        exceptions (Tuple[Exception]) : The exceptions expected during function execution
        retries (int): Number of retries of function execution.
        cooldown (int): Seconds to wait before retry.
        verbose (bool): Specifies if we should log about not successful attempts.
    """

    def wrap(func):
        @wraps(func)
        async def inner(*args, **kwargs):
            retries_count = 0

            while True:
                try:
                    result = await func(*args, **kwargs)
                except exceptions as err:
                    retries_count += 1
                    message = "Exception during {} execution. " \
                              "{} of {} retries attempted".
                              format(func, retries_count, retries)

                    if retries_count > retries:
                        verbose and log.exception(message)
                        raise RetryExhaustedError(
                            func.__qualname__, args, kwargs) from err
                    else:
                        verbose and log.warning(message)

                    if cooldown:
                        await asyncio.sleep(cooldown)
                else:
                    return result
        return inner
    return wrap
复制代码

 

  基本思想是在达到重试次数限制以前捕获预期的异常。在每次执行之间,等待固定时间。此外,若是咱们想要详细,会写每一个失败尝试的日志。固然,本例子只提供了几个重试选项,一个完备的重试库应该提供更多重试配置,好比指数退避时间、根据返回结果重试等,这里推荐几个第三方库:session

 本文翻译自

Never Give Up, Retry: How Software Should Deal with Failures

相关文章
相关标签/搜索