2020년 4월 16일 목요일

[Python] 재시도 로직이 추가된 requests 모듈 사용 예제

파이썬엔 간단하게 HTTP 요청을 할 수 있게 해주는 requests 모듈이 있다. 렌더링을 위한 데이터 획득이 아니라면 굳이 브라우저를 이용할 필요가 없기 때문에 json 데이터 등을 가져오는게(API 호출) 목적이라면 requests 모듈을 이용하는 것이 보다 합리적이다. 여기서 한가지 주의할 점은 네트워크 환경이 불안정한 경우 일시적으로 요청 작업이 실패할 수 있다는 부분이다. 따라서 사용자는 요청 실패에 대한 로직 흐름까지 고려할 필요가 있다.

아주 단순하게 문제를 해결하자면 루프 구조 내에서 원하는 결과가 나올 때까지 응답을 계속하는 방법이 있을 수 있다.

# 방법 1
def get(url):
    try:
        return requests.get(url)
    except Exception:
        time.sleep(1)
        return get(url) # 재시도

# 방법 2
while True:
    response = get(url)
    if response.status_code != 500: # 500, server error
        break
    else:
        time.sleep(1)

방법 1의 문제점은 url 문자열이 잘못되어있을 경우 무한정 요청 작업을 수행한다는 것이고, 방법 2의 문제점은 다양한 네트워크 에러를 로직이 올바르게 처리해주지 못한다는 점이다.

제안되는 솔루션은 다음과 같다.

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def requests_retry_session(
    retries=3,
    backoff_factor=0.3,
    status_forcelist=(500, 502, 504),
    session=None,
):
    session = session or requests.Session()
    retry = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=backoff_factor,
        status_forcelist=status_forcelist,
    )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session

사용은 아래와 같이 한다. session 객체 전달은 선택 사양으로, 사용자가 session 객체를 따로 인자로 넘겨주지 않으면 requests_retry_session() 함수 내부에서 session 객체를 새로 생성하는 것을 위 코드에서 볼 수 있다. 그리고 status_forcelist엔 요청 실패로 간주할 응답 코드를 넣어줄 수 있다.


# 단순 get
response = requests_retry_session().get(url)

# 단순 get, timeout이 있는 경우.
response = requests_retry_session().get(url, timeout=5)

# 헤더 정보가 필요한 경우
s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

response = requests_retry_session(session=s).get(url)

Retry 모듈과 HTTPAdapter 모듈을 이용해 요청을 할 때 재시도 정보를 포함시키는 것이다. backoff 팩터는 retries 값과 연계해 요청과 요청 사이에 쉬어줄 시간을 정해주게 된다.

쉬어줄 시간에 대한 로직은 다음과 같다.
슬립 = backoff factor * 2^(재시도 횟수 - 1)

따라서 위 함수의 기본 값으로 지정된 3과 0.3을 그대로 쓰는 경우
• 1회 재시도 sleep : 0초 (0.3 * 0)
• 2회 재시도 sleep : 0.6초 (0.3 * 2)
• 3회 재시도 sleep : 1.2초 (0.3 * 4)
값을 갖게 된다.

따라서 3회 요청에 실패하는 경우 사용자는 최대 1.8초 + @(통신 비용)을 기다리게 되는 셈이다.
위 함수는 당연히 post() 호출 시에도 유효하다.

출처: https://www.peterbe.com/plog/best-practice-with-retries-with-requests

아래는 유사 한글 포스팅 참고.
https://knight76.tistory.com/entry/python-%EC%9B%B9-%EC%9A%94%EC%B2%AD-%EC%98%88%EC%8B%9C-requests-HTTPAdapter-Retry

댓글 없음:

댓글 쓰기