Python requests 重试机制



一、遇到问题


 response = requests.post(cgi, data=body, headers=http_headers, timeout=5, verify=False)

偶发报错:
… requests/adapters.py", line 449, in send
raise ReadTimeout(e, request=request)
ReadTimeout: HTTPSConnectionPool(host=‘weixin.oa.com’, port=443): Read timed out. (read timeout=5)



二、增加重试


try:
   requests.adapters.DEFAULT_RETRIES = 2
   response = requests.post(cgi, data=body, headers=http_headers, timeout=5, verify=False)
except Exception as e:
  LOGGER.error(e)


三、DEFAULT_RETRIES 解析


class HTTPAdapter(BaseAdapter):

    """The built-in HTTP Adapter for urllib3.
    :param max_retries: The maximum number of retries each connection
        should attempt. Note, this applies only to failed DNS lookups, socket
        connections and connection timeouts, never to requests where data has
        made it to the server. By default, Requests does not retry failed
        connections. If you need granular control over the conditions under
        which we retry a request, import urllib3's ``Retry`` class and pass
        that instead.
    Usage::
      >>> import requests
      >>> s = requests.Session()
      >>> a = requests.adapters.HTTPAdapter(max_retries=3)
      >>> s.mount('http://', a)
    """
    
    __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
                 '_pool_block']
                 
    def __init__(self, pool_connections=DEFAULT_POOLSIZE,
                 pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
                 pool_block=DEFAULT_POOLBLOCK):
                 
        if max_retries == DEFAULT_RETRIES:
            self.max_retries = Retry(0, read=False)
        else:
            self.max_retries = Retry.from_int(max_retries)

Retry设计:
在HTTPConnectionPool中,根据返回异常和方法,区分具体是哪种链接失败 (connect or read?),
然后减少对应的值,
接着再判断是否所有的操作重试都归零量,
归零后则报 MaxRetries 异常。

每次重试之间的间隔,使用了 backoff 算法。



四、 backoff 算法


class Retry(object):

    """ Retry configuration.
    Each retry attempt will create a new Retry object with updated values, so
    they can be safely reused.
    Retries can be defined as a default for a pool::
        retries = Retry(connect=5, read=2, redirect=5)
        http = PoolManager(retries=retries)
        response = http.request('GET', 'http://example.com/')
    Or per-request (which overrides the default for the pool)::
        response = http.request('GET', 'http://example.com/', retries=Retry(10))
    Retries can be disabled by passing ``False``::
        response = http.request('GET', 'http://example.com/', retries=False)
    Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless
    retries are disabled, in which case the causing exception will be raised.
    :param int total:
        Total number of retries to allow. Takes precedence over other counts.
        Set to ``None`` to remove this constraint and fall back on other
        counts. It's a good idea to set this to some sensibly-high value to
        account for unexpected edge cases and avoid infinite retry loops.
        Set to ``0`` to fail on the first retry.
        Set to ``False`` to disable and imply ``raise_on_redirect=False``.
    ....
    
    :param iterable method_whitelist:
        Set of uppercased HTTP method verbs that we should retry on.
        By default, we only retry on methods which are considered to be
        indempotent (multiple requests with the same parameters end with the
        same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`.
    :param iterable status_forcelist:
        A set of HTTP status codes that we should force a retry on.
        By default, this is disabled with ``None``.
    :param float backoff_factor:
        A backoff factor to apply between attempts. urllib3 will sleep for::
            {backoff factor} * (2 ^ ({number of total retries} - 1))
        seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
        for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer
        than :attr:`Retry.BACKOFF_MAX`.
        By default, backoff is disabled (set to 0).
        
    """
    
    DEFAULT_METHOD_WHITELIST = frozenset([
        'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'])
        
    #: Maximum backoff time.
    BACKOFF_MAX = 120
    
    def __init__(self, total=10, connect=None, read=None, redirect=None,
                 method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None,
                 backoff_factor=0, raise_on_redirect=True, raise_on_status=True,
                 _observed_errors=0):
        self.total = total
        self.connect = connect
        self.read = read
        if redirect is False or total is False:
            redirect = 0
            raise_on_redirect = False
        self.redirect = redirect
        self.status_forcelist = status_forcelist or set()
        self.method_whitelist = method_whitelist
        self.backoff_factor = backoff_factor
        self.raise_on_redirect = raise_on_redirect
        self.raise_on_status = raise_on_status
        self._observed_errors = _observed_errors  # TODO: use .history instead?
        
    def get_backoff_time(self):
        """ Formula for computing the current backoff
        :rtype: float
        """
        if self._observed_errors <= 1:
            return 0
        # 重试算法, _observed_erros就是第几次重试,每次失败这个值就+1.
        # backoff_factor = 0.1, 重试的间隔为[0.1, 0.2, 0.4, 0.8, ..., BACKOFF_MAX(120)]
        backoff_value = self.backoff_factor * (2 ** (self._observed_errors - 1))
        return min(self.BACKOFF_MAX, backoff_value)
        
    def sleep(self):
        """ Sleep between retry attempts using an exponential backoff.
        By default, the backoff factor is 0 and this method will return
        immediately.
        """
        backoff = self.get_backoff_time()
        if backoff <= 0:
            return
        time.sleep(backoff)
        
    def is_forced_retry(self, method, status_code):
        """ Is this method/status code retryable? (Based on method/codes whitelists)
        """
        if self.method_whitelist and method.upper() not in self.method_whitelist:
            return False
        return self.status_forcelist and status_code in self.status_forcelist
        
# For backwards compatibility (equivalent to pre-v1.9):
Retry.DEFAULT = Retry(3)

1、使用requests.get,默认Retry 3 次;

2、默认Retry时机:
DNS解析错误
链接错误
链接超时

3、读取超时、写超时、HTTP协议错误等默认不会Retry 。




参考文档 : requestsAPI文档

发布了37 篇原创文章 · 获赞 24 · 访问量 3090

猜你喜欢

转载自blog.csdn.net/weixin_44648216/article/details/103697496