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

相關文章
相關標籤/搜索