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

下一篇博文將通過分析retrying源碼來深入分析重試機制的實現原理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM