SpringFrameworkSSM(Spring+SpringMVC+MyBatis)

Spring Retry 详细教程

2020-12-14  本文已影响0人  liuliuzo

什么时候用可以重试

无状态(Stateless) 重试

有状态(Stateful) 重试

有状态重试,有两种情况需要使用有状态重试,事务操作需要回滚或者熔断器模式。 事务操作需要回滚场景时,当整个操作中抛出的是数据库异常DataAccessException,异常会往外抛,使事务回滚,这里不能进行重试,而抛出其他异常则可以进行重试。

Spring Retry 注解

@EnableRetry

@EnableRetry能否重试。当proxyTargetClass属性为true时,使用CGLIB代理。默认使用标准JAVA注解。

package com.liuliu.demo.springretry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry
//proxyTargetClass属性为true时,使用CGLIB代理。默认使用标准JAVA注解。
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringretryApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringretryApplication.class, args);
    }
}

Maven dependency

   <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
   </dependency>
   <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
   </dependency>

@Retryable

注解需要被重试的方法

@Retryable(value = { RemoteAccessException.class }, 
                    maxAttempts = 3, 
                    backoff = @Backoff(delay = 500l, multiplier = 1))
    public void call() throws Exception {
        logger.info(LocalTime.now() + " do something...");
        throw new RemoteAccessException("RPC调用异常");
    }

使用了@Retryable的方法里面不能使用try...catch包裹,要在发放上抛出异常,不然不会触发。

@Backoff

重试回退策略(立即重试还是等待一会再重试)

@Retryable(value = { Exception.class }, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5))
public int minGoodsnum(int num) throws Exception {
        logger.info("minGoodsnum开始" + LocalTime.now());
    if (num <= 0) {
        throw new Exception("数量不对");
    }
    logger.info("minGoodsnum执行结束");
    return totalNum - num;
}

@Recover

用于方法。用于@Retryable失败时的“兜底”处理方法。

@Service
public class PayService {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private final int totalNum = 100000;

    @Retryable(value = { Exception.class }, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5))
    public int minGoodsnum(int num) throws Exception {
        logger.info("minGoodsnum开始" + LocalTime.now());
        if (num <= 0) {
            throw new Exception("数量不对");
        }
        logger.info("minGoodsnum执行结束");
        return totalNum - num;
    }

    @Recover
    public void recover(Exception e) {
        logger.info("=>>>>>" + e.getMessage());
    }
}

@CircuitBreaker

@Service
class ShakyBusinessService {

    @Recover
    public int fallback(BoomException ex) {
        return 2;
    }

    @CircuitBreaker(include = BoomException.class, openTimeout = 20000L, resetTimeout = 5000L, maxAttempts = 1)
    public int desireNumber() throws Exception {
        System.out.println("calling desireNumber()");
        if (Math.random() > .5) {
            throw new BoomException("Boom");
        }
        return 1;
    }
}

RetryTemplate

什么时候使用RetryTemplate?

RetryPolicy 重试策略

BackOffPolicy 退避策略

DEMO RetryTemplate

TimeoutRetryPolicy
public class RetryTemplate01 {
    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();
        TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
        template.setRetryPolicy(policy);
        String result = template.execute(new RetryCallback<String, Exception>() {
            public String doWithRetry(RetryContext arg0) throws Exception {
                return "Retry";
            }
        });
        System.out.println(result);
    }
}
SimpleRetryPolicy

代码重试两次后,仍然失败,RecoveryCallback被调用,返回”recovery callback”。如果没有定义RecoveryCallback,那么重试2次后,将会抛出异常。

public class RetryTemplate02 {

    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();
        SimpleRetryPolicy policy = new SimpleRetryPolicy();
        policy.setMaxAttempts(2);
        template.setRetryPolicy(policy);
        String result = template.execute(new RetryCallback<String, Exception>() {
            public String doWithRetry(RetryContext arg0) throws Exception {
                throw new NullPointerException("nullPointerException");
            }
        }, new RecoveryCallback<String>() {
            public String recover(RetryContext context) throws Exception {
                return "recovery callback";
            }
        });
        System.out.println(result);
    }
}

该策略定义了对指定的异常进行若干次重试。默认情况下,对Exception异常及其子类重试3次。 如果创建SimpleRetryPolicy并指定重试异常map,可以选择性重试或不进行重试。下面的代码定义了对TimeOutException进行重试。

public class RetryTemplate03 {

    public static void main(String[] args) throws Exception {
        
        RetryTemplate template = new RetryTemplate();
        Map<Class<? extends Throwable>, Boolean> maps = new HashMap<Class<? extends Throwable>, Boolean>();
        maps.put(TimeoutException.class, true);
        SimpleRetryPolicy policy2 = new SimpleRetryPolicy(2, maps);
        template.setRetryPolicy(policy2);

        String result = template.execute(new RetryCallback<String, Exception>() {
            public String doWithRetry(RetryContext arg0) throws Exception {
                throw new TimeoutException("TimeoutException");
            }
        }, new RecoveryCallback<String>() {
            public String recover(RetryContext context) throws Exception {
                return "recovery callback";
            }
        });
        System.out.println(result);
    }

}
ExceptionClassifierRetryPolicy

通过PolicyMap定义异常及其重试策略。下面的代码在抛出NullPointerException采用NeverRetryPolicy策略,而TimeoutException采用AlwaysRetryPolicy。

public class RetryTemplate04 {

    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();
        ExceptionClassifierRetryPolicy policy = new ExceptionClassifierRetryPolicy();
        Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<Class<? extends Throwable>, RetryPolicy>();
        policyMap.put(TimeoutException.class, new AlwaysRetryPolicy());
        policyMap.put(NullPointerException.class, new NeverRetryPolicy());
        policy.setPolicyMap(policyMap);
        template.setRetryPolicy(policy);
        String result = template.execute(new RetryCallback<String, Exception>() {
            public String doWithRetry(RetryContext arg0) throws Exception {
                if (arg0.getRetryCount() >= 2) {
                    Thread.sleep(1000);
                    throw new NullPointerException();
                }
                throw new TimeoutException("TimeoutException");
            }
        }, new RecoveryCallback<String>() {
            public String recover(RetryContext context) throws Exception {
                return "recovery callback";
            }
        });
        System.out.println(result);
    }

}
CompositeRetryPolicy
public class RetryTemplate05 {

    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();

        CompositeRetryPolicy policy = new CompositeRetryPolicy();
        RetryPolicy[] polices = { new SimpleRetryPolicy(), new AlwaysRetryPolicy() };

        policy.setPolicies(polices);
        policy.setOptimistic(true);
        template.setRetryPolicy(policy);

        String result = template.execute(new RetryCallback<String, Exception>() {
            public String doWithRetry(RetryContext arg0) throws Exception {
                if (arg0.getRetryCount() >= 2) {
                    Thread.sleep(1000);
                    throw new NullPointerException();

                }
                throw new TimeoutException("TimeoutException");
            }
        }, new RecoveryCallback<String>() {
            public String recover(RetryContext context) throws Exception {
                return "recovery callback";
            }
        });
        System.out.println(result);
    }

}
ExponentialRandomBackOffPolicy
public class RetryTemplate06 {

    public static void main(String[] args) throws Exception {
        
        RetryTemplate template = new RetryTemplate();

        ExponentialRandomBackOffPolicy exponentialBackOffPolicy = new ExponentialRandomBackOffPolicy();
        exponentialBackOffPolicy.setInitialInterval(1500);
        exponentialBackOffPolicy.setMultiplier(2);
        exponentialBackOffPolicy.setMaxInterval(6000);

        CompositeRetryPolicy policy = new CompositeRetryPolicy();
        RetryPolicy[] polices = { new SimpleRetryPolicy(), new AlwaysRetryPolicy() };

        policy.setPolicies(polices);
        policy.setOptimistic(true);

        template.setRetryPolicy(policy);
        template.setBackOffPolicy(exponentialBackOffPolicy);

        template.registerListener(new RetryListener() {
            public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                System.out.println("open");
                return true;
            }
            public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                    Throwable throwable) {
                System.out.println("onError");
            }
            public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
                    Throwable throwable) {
                System.out.println("close");
            }
        });

        template.registerListener(new RetryListener() {
            public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                System.out.println("open2");
                return true;
            }
            public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                    Throwable throwable) {
                System.out.println("onError2");
            }
            public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
                    Throwable throwable) {
                System.out.println("close2");
            }
        });
        String result = template.execute(new RetryCallback<String, Exception>() {
            public String doWithRetry(RetryContext arg0) throws Exception {
                arg0.getAttribute("");
                if (arg0.getRetryCount() >= 2) {
                    throw new NullPointerException();
                }
                throw new TimeoutException("TimeoutException");
            }
        });
        System.out.println(result);
    }

}

有状态RetryTemplate

当把状态放入缓存时,通过该key查询获取,全局模式 DataAccessException进行回滚

public class RetryTemplate07 {
    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();
        Object key = "mykey";
        boolean isForceRefresh = true;
        BinaryExceptionClassifier rollbackClassifier = new BinaryExceptionClassifier(
                Collections.<Class<? extends Throwable>>singleton(DataAccessException.class));
        RetryState state = new DefaultRetryState(key, isForceRefresh, rollbackClassifier);
        String result = template.execute(new RetryCallback<String, RuntimeException>() {
            @Override
            public String doWithRetry(RetryContext context) throws RuntimeException {
                System.out.println("retry count:" + context.getRetryCount());
                throw new TypeMismatchDataAccessException();
            }
        }, new RecoveryCallback<String>() {
            @Override
            public String recover(RetryContext context) throws Exception {
                return "default";
            }
        }, state);
        System.out.println(result);
    }
}

熔断器场景。在有状态重试时,且是全局模式,不在当前循环中处理重试,而是全局重试模式(不是线程上下文),如熔断器策略时测试代码如下所示:

public class RetryTemplate08 {

    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();
        CircuitBreakerRetryPolicy retryPolicy = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(3));
        retryPolicy.setOpenTimeout(5000);
        retryPolicy.setResetTimeout(20000);
        template.setRetryPolicy(retryPolicy);
        for (int i = 0; i < 10; i++) {
            try {
                Object key = "circuit";
                boolean isForceRefresh = false;
                RetryState state = new DefaultRetryState(key, isForceRefresh);
                String result = template.execute(new RetryCallback<String, RuntimeException>() {
                    @Override
                    public String doWithRetry(RetryContext context) throws RuntimeException {
                        System.out.println("retry count:" + context.getRetryCount());
                        throw new RuntimeException("timeout");
                    }
                }, new RecoveryCallback<String>() {
                    @Override
                    public String recover(RetryContext context) throws Exception {
                        return "default";
                    }
                }, state);
                System.out.println(result);
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }

}

XML Configuration

xml配置可以在不修改原来代码的情况下通过,添加spring retry的功能。

@SpringBootApplication
@EnableRetry
@EnableAspectJAutoProxy
@ImportResource("classpath:/retryadvice.xml")
public class XmlApplication {
    public static void main(String[] args) {
        SpringApplication.run(XmlApplication.class, args);
    }
}
public class XmlRetryService {
    public void xmlRetryService(String arg01) throws Exception {
        System.out.println("xmlRetryService do something...");
        throw new RemoteAccessException("RemoteAccessException....");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:config>
        <aop:pointcut id="transactional" expression="execution(*XmlRetryService.xmlRetryService(..))" />
        <aop:advisor pointcut-ref="transactional" advice-ref="taskRetryAdvice" order="-1" />
    </aop:config>
    <bean id="taskRetryAdvice" class="org.springframework.retry.interceptor.RetryOperationsInterceptor">
        <property name="RetryOperations" ref="taskRetryTemplate" />
    </bean>
    <bean id="taskRetryTemplate" class="org.springframework.retry.support.RetryTemplate">
        <property name="retryPolicy" ref="taskRetryPolicy" />
        <property name="backOffPolicy" ref="exponentialBackOffPolicy" />
    </bean>
    <bean id="taskRetryPolicy" class="org.springframework.retry.policy.SimpleRetryPolicy">
        <constructor-arg index="0" value="5" />
        <constructor-arg index="1">
            <map>
                <entry key="org.springframework.remoting.RemoteAccessException" value="true" />
            </map>
        </constructor-arg>
    </bean>
    <bean id="exponentialBackOffPolicy"
        class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
        <property name="initialInterval" value="300" />
        <property name="maxInterval" value="30000" />
        <property name="multiplier" value="2.0" />
    </bean>
</beans>

学习源代码地址

上一篇下一篇

猜你喜欢

热点阅读