결제 시스템 구축 - 4

  1. 결제 Retry 구현하기
    1. 결제 Retry 구현
    2. 재시도 할때 고려해야 할 상황들
      1. 재시도 제한 횟수
      2. 지수 백오프
      3. jitter
    3. Resilience4j Retry 구현

결제 Retry 구현하기

retry 다이어그램.png

결제 Retry 구현

‘효성’ 에서 결제 승인시 중복 요청으로 인해 에러를 발생 할 경우 아래와 같은 응답으로 호출 해주고 있습니다.
여기서 말하는 결제 전용 멱등성키 값 경우 효성에서는 ‘transactionId’ 값으로 지칭 하고 있습니다.
동일한 ‘transactionId’ 값으로 중복 호출시 아래와 같은 에러가 발생되면 이미 결제 승인 했기 때문에
결제 관련 상태값을 성공 프로세스로 진행 해야 합니다. 중요한 것은 다시 결제 승인이 일어나면 절대 안된다는 점 입니다.

1
2
3
4
5
6
{
"error": {
"message":"중복 요청 입니다.",
"developerMessage":"이미 사용한 transactionId는 다시 사용할 수 없습니다."
}
}

재시도 할때 고려해야 할 상황들

  • 재시도 제한 횟수 (Retry Limited Count)
  • 지수 백오프 (Exponential Backoff)
  • 지터 (Jitter)

재시도 제한 횟수

재시도 제한 횟수는 말그대로 에러 발생시 재시도를 몇회 다시 시도 할것인가 입니다.

지수 백오프

지수 백오프는 재시도와 재시도 간격은 얼마나 해야할까 입니다. 지수 백오프 사이에 시간 간격이 너무 짧으면 서버 과부하로 이어집니다.
그래서 예시로 1회 재시도시 간격을 1초로 했다면 2회 재시도시에는 간격을 2초로 이렇게 설정 하도록 합니다.

jitter

요청이 동시에 재시도 되지 않도록 지수 백오프 (Exponential Backoff) 외에 무작위 지연을 추가적으로 부여하는 것 입니다.

Resilience4j Retry 구현

Resilience4j 에서 지원하는 핵심 기능은 많이 있습니다. 대표적으로 CircuitBreaker, Retry 기능이 있습니다. 이중에서 Retry 기능을 활용 해보고자 합니다.
앞써 이야기 한데로 PSP Server 가 만약 일시적으로 장애로 인해 결제 관련 API 호출이 실패 했다면 그리고 다시 시도 통해 문제를 해결 할수 있을때 Retry 기능을 활용 해야 합니다.

보통 Retry 기능을 구현하기 위해서는 결제 관련 비지니스 로직에 Retry 관련 로직이 포함 되다는게 문제가 있습니다. 즉 중요한 코드와 중요하지 않은 코드 (Retry 기능을 위한 코드)가 뒤섞여 있으므로 ‘OCP’에 준수 하지 않습니다.
Resilience4j Retry 기능 통해 이 문제를 극복 하고자 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resilience4j.retry:
configs:
paymentRetryConfig:
maxAttempts: 3 # 재시도 제한 횟수 3회로 합니다.
waitDuration: 1000 # 재시도 할때 1초 동안 기다리고 재시도 한다.
enableExponentialBackoff: true
exponentialBackoffMultiplier: 5 # backoff시 multipilier를 지정 합니다.
retryExceptions:
- com.dfs.api.error.exception.PSPConfirmationException # retryExceptions에 지정된 예외는 재시도
# ignoreExceptions:
# - # retryExceptions에 지정되지 않은 예외는 ignoreExceptions로 처리됨
instances:
paymentRetryConfig:
baseConfig: paymentRetryConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static final String PAYMENT_RETRY_CONFIG = "paymentRetryConfig";


@Retry(name = PAYMENT_RETRY_CONFIG, fallbackMethod = "fallbackPaymentMethodRegisterCreditCard")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public HyosungPayerCreditCardRegisterWithPspRawDataResponse registerCreditCard(PaymentMethodRegisterCreditCardInPut paymentMethodRegisterCreditCard) {

this.feignVerifyPayerNumber(paymentMethodRegisterCreditCard);
return this.feignRegisterCreditCard(paymentMethodRegisterCreditCard);
}

/**
* resilience4j maxAttempts 설정한 횟수 전부 실패시 fallback 이 실행
*/
private HyosungPayerCreditCardRegisterWithPspRawDataResponse fallbackPaymentMethodRegisterCreditCard(PaymentMethodRegisterCreditCardInPut paymentMethodRegisterCreditCard, Exception exception) {
log.error("fallbackPaymentMethodRegisterCreditCard! your request is {}, errorMessage={}", paymentMethodRegisterCreditCard, exception.getMessage());

String pspRawData = this.extractPspRawData(exception);
return HyosungPayerCreditCardRegisterWithPspRawDataResponse.initDueToError(paymentMethodRegisterCreditCard, exception, pspRawData);
}

registerCreditCard 메소드 통해 효성 서버로 부터 문제가 발생되면 Resilience4j Retry Config 에 지정된 ‘재시도 제한 횟수’, ‘지수 백오프’ 이용해 모두 실패시
fallback 함수 (fallbackPaymentMethodRegisterCreditCard 메소드) 를 호출 하게 됩니다.


Copyright 201- syh8088. 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

💰

×

Help us with donation