• retry重试常见场景及实现


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

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

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

      访问数据库错误:

    • 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请求,可根据状态码识别:

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

       对于数据库访问:

    • 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.

      本文以网络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
    复制代码

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

     本文翻译自

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

  • 相关阅读:
    [20191108]内核参数tcp_keepalive与sqlnet.ora expire_time的一些总结.txt
    [20191106]善用column格式化输出.txt
    [20191101]通过zsh计算sql语句的sql_id.txt
    [20191101]完善vim的bccalc插件8.txt
    [20191031]完善vim的bccalc插件7.txt
    [20191013]oracle number类型存储转化脚本.txt
    [20191012]组成rowid.txt
    文件下载中文问题
    关闭 macOS Google Chrome 黑暗模式风格
    删除最后一次提交
  • 原文地址:https://www.cnblogs.com/yunlongaimeng/p/10876484.html
Copyright © 2020-2023  润新知