Skip to content

Instantly share code, notes, and snippets.

@benelog
Last active October 8, 2017 15:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save benelog/4cfb12f07e7fdc18851f to your computer and use it in GitHub Desktop.
Save benelog/4cfb12f07e7fdc18851f to your computer and use it in GitHub Desktop.
retry.md

Spring retry

pom.xml에 아래와 같이 의존성을 추가하면 사용할수 있습니다.

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.0.3.RELEASE</version>
</dependency>

RetryTemplate 과 RetryOperationsInterceptor 를 제공합니다. RetryTemplate이라는 클래시에 재시도 횟수와 대기시간 정책 등을 지정할 수 있습니다.

Guava-retrying

spring-retry와 기능은 유사하나 API 사용성이 더 좋기 때문에 직접 interceptor를 작성할 때는 Spring의 RetryTemplate 대신 이 라이브러리를 사용해볼만도 합니다.

아래와 같이 의존성을 선언하면 사용할 수 있습니다.

<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>1.0.6</version>
</dependency>

아래와 같이 Retry 횟수를 annotation의 속성으로 지정을 하는 경우,

@Retry(maxAttempt = 4)
public int update(Product product) {
    Date now = new Date();
    System.out.printf("update 시도 - %tT:%tL\n", now, now);
    executeError();
    return 1;
}

이를 지원하는 interceptor등의 예제는 아래에서 확인하실수 있습니다.

예제에서는 횟수만 지정했는데, 자주 쓰는 Retry정책을 Enum으로 만들어서 @Retry 애노테이션의 속성으로 지정을 하도록 만든 사례도 있습니다. 그리고 위와 같은 retry framework를 쓰지 않더라도 Interceptor안에서 try, catch, Thread.sleep(..) 문으로 같은 효과를 내는것도 어렵지는 않습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="net.benelog.retry"/>
<bean id = "retryAdvice" class="net.benelog.retry.AnnotationBasedRetryInterceptor"/>
<aop:config>
<aop:advisor pointcut="@annotation(com.navercorp.weblab.retry.Retry)"
advice-ref="retryAdvice" order="-1"/>
</aop:config>
</beans>
package net.benelog.retry;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.ProxyMethodInvocation;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
public class AnnotationBasedRetryInterceptor implements MethodInterceptor {
private final Logger log = LoggerFactory.getLogger(AnnotationBasedRetryInterceptor.class);
private static final int MAXIUM_WAIT_MILLISEC = 10_000;
public Object invoke(final MethodInvocation invocation) throws Throwable {
Callable<Object> retryCallback = new Callable<Object>() {
@Override
public Object call() throws Exception {
if (invocation instanceof ProxyMethodInvocation) {
try {
return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
} catch (Exception e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} else {
throw new IllegalStateException("MethodInvocation of the wrong type detected ");
}
}
};
Retry retryConfig = invocation.getMethod().getAnnotation(Retry.class);
Retryer<Object> retryer = RetryerBuilder.<Object>newBuilder()
.withStopStrategy(StopStrategies.stopAfterAttempt(retryConfig.maxAttempt()))
.withWaitStrategy(WaitStrategies.exponentialWait(200, MAXIUM_WAIT_MILLISEC, TimeUnit.MILLISECONDS))
.retryIfRuntimeException()
.build();
try {
return retryer.call(retryCallback);
} catch (Exception e) {
Throwable causeEx = e.getCause();
log.error("finally failed" , causeEx);
throw new IllegalStateException(causeEx);
}
}
}
package net.benelog.retry;
import java.util.Date;
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.stereotype.Component;
@Component
public class ProductRecoverer implements MethodInvocationRecoverer <Object> {
@Override
public Object recover(Object[] args, Throwable cause) {
Date now = new Date();
System.out.printf("다 실패했을 때 - %tT:%tL\n", now, now);
if (args[0] instanceof Product) {
Product product = (Product) args[0];
sendMessagingQueue(product);
} else {
throw new IllegalArgumentException("not valid arguemnt for ProductRecoverer!", cause);
}
return 1;
}
private void sendMessagingQueue(Product product) {
System.out.println("메시징 큐 전송 : " + product);
}
}
package com.benelog.retry;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:simple-retry-config.xml")
public class RetryAndRecoverTest {
@Autowired ProductRepository repository;
@Test
public void retryAndRecover() {
assertTrue(AopUtils.isAopProxy(repository));
Product product = new Product();
product.setName("Nexus 5");
repository.insert(product);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="net.benelog.retry"/>
<bean id = "retryAdvice" class="net.benelog.retry.SimpleRetryAdviceFactoryBean"
p:maxAttempts="4" p:initialInterval="500" p:multiplier="2"
p:recoverer-ref="productRecoverer"/>
<bean id="productRecoverer" class="net.benelog.ProductRecoverer"/>
<aop:config>
<aop:advisor pointcut="execution(public * net.benelog.retry..*Repository.*(..))"
advice-ref="retryAdvice" order="-1"/>
</aop:config>
</beans>
package net.benelog.retry;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
public class SimpleRetryAdviceFactoryBean implements FactoryBean<RetryOperationsInterceptor> {
private int maxAttempts;
private long initialInterval;
private double multiplier;
private MethodInvocationRecoverer<?> recoverer;
@Override
public RetryOperationsInterceptor getObject() throws Exception {
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(createRetryPolicy());
template.setBackOffPolicy(createBackOffPolicy());
RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();
interceptor.setRetryOperations(template);
interceptor.setRecoverer(recoverer);
return interceptor;
}
protected BackOffPolicy createBackOffPolicy() {
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(initialInterval);
backOffPolicy.setMultiplier(multiplier);
return backOffPolicy;
}
protected RetryPolicy createRetryPolicy() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(maxAttempts);
return retryPolicy;
}
@Override
public Class<?> getObjectType() {
return RetryOperationsInterceptor.class;
}
@Override
public boolean isSingleton() {
return true;
}
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public double getMultiplier() {
return multiplier;
}
public void setMultiplier(double multiplier) {
this.multiplier = multiplier;
}
public void setInitialInterval(long initialInterval) {
this.initialInterval = initialInterval;
}
public void setRecoverer(MethodInvocationRecoverer<?> recoverer) {
this.recoverer = recoverer;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment