LabHub
배우기 러닝패스 코스

マイクロサービスアーキテクチャ

サービス間REST呼び出しにタイムアウトとリトライを入れる

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

불안정한 다운스트림을 상대로 타임아웃, 지수 백오프, 지터, 재시도 금지 조건, 폴백을 직접 구현해 동기 호출의 기본기를 완성한다.

왜 중요한가

동기 호출에서 사고는 대개 "타임아웃이 없어서" 또는 "재시도를 잘못해서" 납니다. 타임아웃이 없으면 다운스트림이 느려질 때 업스트림의 커넥션 풀이 먼저 마르고, 다운스트림과 무관한 요청까지 실패합니다. 반대로 재시도를 고정 간격으로 무제한 걸면 다운스트림이 회복하려는 순간마다 같은 파도가 덮칩니다. 실제 사례에서 주문 인스턴스 20대가 초당 100건을 처리하며 maxAttempts 5 로 재시도하자 결제 서비스가 초당 10,000건을 받았습니다. 재시도는 부하를 더하는 것이 아니라 곱합니다. 그래서 이 실습은 값을 넣는 순서를 중요하게 다룹니다 — 먼저 타임아웃, 그다음 재시도 조건, 그다음 백오프와 지터, 마지막에 폴백입니다.

단계

  1. /opt/app/flaky.py 를 127.0.0.1:8110 에 띄운다. GET /fast 가 200 이어야 한다.
  2. /root/rest/call.py 를 만들어 /fast 를 호출하고 응답 본문을 /root/rest/fast.out 에 저장한다.
  3. /root/rest/timeout.py 를 만들어 /slow 를 1.0초 타임아웃으로 호출한다. /root/rest/timeout.out 첫 줄에 TIMEOUT, 둘째 줄에 elapsed=<초> 를 적는다. 경과 시간은 2.0초 미만이어야 한다.
  4. /root/rest/retry.py 를 만들어 /flaky?key=lab&fail=2 를 지수 백오프로 재시도해 최종 성공시킨다. /root/rest/retry.logattempt=<n> wait=<초> 형식으로 시도마다 한 줄씩 남기고 총 3줄이 되게 한다.
  5. retry.py 의 대기 계산을 full jitter 로 바꾼다. 시도 번호 2의 대기 시간을 20번 뽑아 /root/rest/jitter.txt 에 한 줄씩 적는다. 서로 다른 값이 15개 이상이어야 한다.
  6. /root/rest/retry400.py/bad 를 호출한다. 400 은 재시도하지 않으므로 /root/rest/retry400.log 는 정확히 1줄이어야 한다.
  7. /root/rest/budget.txtinstances=20, rps=100, max_attempts=5, worst_rps=10000 네 줄을 적는다.
  8. /root/rest/gateway.py 를 127.0.0.1:8111 에 띄운다. GET /order 는 다운스트림 /always500 을 호출하되, 타임아웃과 재시도를 적용하고 최종 실패 시 200 과 {"degraded":true} 를 준다.

참고

불안정한 다운스트림 띄우기

/opt/app/flaky.py 를 127.0.0.1:8110 에 띄운다. GET /fast 가 200 이어야 한다.

/opt/app/flaky.py 를 실행하면 8110 에 뜹니다. /fast, /slow, /flaky, /bad 네 경로의 동작을 먼저 눈으로 확인하세요.

정상 호출로 기준선 잡기

/root/rest/call.py 를 만들어 /fast 를 호출하고 응답 본문을 /root/rest/fast.out 에 저장한다.

httpx 나 urllib 로 /fast 를 부르고 본문을 파일로 남깁니다. 아직 타임아웃은 신경 쓰지 않아도 됩니다.

느린 응답을 타임아웃으로 끊기

/root/rest/timeout.py 를 만들어 /slow 를 1.0초 타임아웃으로 호출한다. /root/rest/timeout.out 첫 줄에 TIMEOUT, 둘째 줄에 elapsed=<초> 를 적는다. 경과 시간은 2.0초 미만이어야 한다.

/slow 는 3초 뒤에 답합니다. 클라이언트 타임아웃을 1초로 두면 예외가 납니다. 예외를 잡아 결과와 경과 시간을 함께 기록하세요.

지수 백오프 재시도 구현하기

/root/rest/retry.py 를 만들어 /flaky?key=lab&fail=2 를 지수 백오프로 재시도해 최종 성공시킨다. /root/rest/retry.logattempt=<n> wait=<초> 형식으로 시도마다 한 줄씩 남기고 총 3줄이 되게 한다.

간격을 매 시도마다 두 배로 늘립니다. 각 시도의 번호와 대기 시간을 로그에 한 줄씩 남겨야 채점됩니다.

지터로 재시도 시점 흩뜨리기

retry.py 의 대기 계산을 full jitter 로 바꾼다. 시도 번호 2의 대기 시간을 20번 뽑아 /root/rest/jitter.txt 에 한 줄씩 적는다. 서로 다른 값이 15개 이상이어야 한다.

full jitter 는 0부터 계산된 백오프 사이의 난수를 기다립니다. 같은 시도 번호에서도 값이 매번 달라야 합니다.

확정적 오류는 재시도하지 않기

/root/rest/retry400.py/bad 를 호출한다. 400 은 재시도하지 않으므로 /root/rest/retry400.log 는 정확히 1줄이어야 한다.

400 은 몇 번을 보내도 400 입니다. 상태 코드로 재시도 여부를 가르는 분기를 넣으세요.

재시도 예산 계산하기

/root/rest/budget.txtinstances=20, rps=100, max_attempts=5, worst_rps=10000 네 줄을 적는다.

인스턴스 수 x 초당 요청 수 x 최대 시도 수 가 다운스트림이 받는 최악의 초당 요청 수입니다. 세 값과 결과를 각각 적으세요.

폴백이 있는 게이트웨이로 종합하기

/root/rest/gateway.py 를 127.0.0.1:8111 에 띄운다. GET /order 는 다운스트림 /always500 을 호출하되, 타임아웃과 재시도를 적용하고 최종 실패 시 200 과 {"degraded":true} 를 준다.

다운스트림이 죽어도 사용자에게는 200 을 주되, 응답에 품질 저하 표시를 남깁니다. 앞 스텝의 타임아웃과 재시도를 함께 씁니다.