resilience4j-retry 重试

简介

resilience4j-retry在服务调用返回失败时提供了额外尝试调用的功能

配置介绍

配置属性 默认值 描述
maxAttempts 3 最大的尝试服务调用次数,包括第一次的调用
waitDuration 500ms 两次重试的时间间隔
intervalFunction numOfAttempts -> waitDuration 自定义的IntervalFunction,可以根据当前尝试的次数动态的修改重试的时间间隔
intervalBiFunction (numOfAttempts, Either<throwable, result) -> waitDuration 自定义的IntervalBiFunction,根据当前尝试的次数和返回的结果或异常动态的修改重试的时间间隔
retryOnResultPredicate result -> false 自定义的Predicate,根据服务返回的结果判断是否应该重试。如果需要重试Predicate应返回true,否则返回false
retryOnExceptionPredicate throwable -> true 自定义的Predicate,根据服务返回的异常判断是否应该重试。如果需要重试Predicate应返回true,否则返回false
retryExceptions empty 异常列表,遇到列表中的异常或其子类则重试
ignoreExceptions empty 异常列表,遇到列表中的异常或其子类则不重试

演示使用

  1. 创建一个模拟的外部服务,60%的几率返回成功,20%的几率返回失败,20%的几率返回自定义异常
  2. 自定义RetryConfig,
    • 最多尝试2次(即重试1次),重试间隔1s
    • 遇到返回失败和自定义异常时重试
  3. 依次进行20次服务调用

需要准备的依赖包,resilience4j的spring boot包,里面包括了resilience4j所有功能的包和自动配置功能

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.7.0</version>
</dependency>

模拟服务

60%的几率返回成功:"SUCCESS",20%的几率返回失败:"FAILURE",20%的几率返回自定义异常BadProcessingException

@Service
public class ExternalRandomFailService {

    public String callService() {
        double random = Math.random();
        if (random < 0.6) {
            System.out.println("\t" + LocalTime.now().truncatedTo(ChronoUnit.MILLIS) + " : Processing finished. Status = SUCCESS");
            return "SUCCESS";
        } else if (random < 0.8) {
            System.out.println("\t" + LocalTime.now().truncatedTo(ChronoUnit.MILLIS) + " : Processing finished. Status = FAILURE");
            return "FAILURE";
        } else {
            System.out.println("\t" + LocalTime.now().truncatedTo(ChronoUnit.MILLIS) + " : Processing finished. Status = BadProcessingException");
            throw new BadProcessingException("Bad processing");
        }
    }
}

retry配置和使用

@SpringBootTest
class ExternalRandomFailServiceTest {

    @Autowired
    ExternalRandomFailService service;

    @Test
    void callService() {
        /*
         * RetryConfig: 总共尝试调用服务2次,间隔1s,返回结果是FAILURE或异常时进行重试
         */
        RetryConfig config = RetryConfig.custom()
            .maxAttempts(2)
            .waitDuration(Duration.ofSeconds(1))
            .retryOnResult(response -> response.equals("FAILURE"))
            .retryOnException(e -> e instanceof BadProcessingException)
            .build();
        Retry retry = Retry.of("externalRandomFailService", config);
        for (int i = 1; i <= 20; i++) {
            System.out.println(">> Call count = " + i);
            try {
                String result = retry.executeSupplier(service::callService);
                System.out.println("\tFinal Result = " + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果

  • 第一次调用就返回SUCCESS,例如count = 5
  • 第一次返回FAILURE或异常,重试返回SUCCESS,例如count=2和count=13
  • 第一次返回FAILURE或异常,重试仍返回FAILURE或异常,例如count=1和count=4
  • 最大尝试次数为2次,所有重试最多只有1次,重试的间隔为1s
>> Call count = 1
    20:04:14.445 : Processing finished. Status = BadProcessingException
    20:04:15.465 : Processing finished. Status = BadProcessingException
    io.ysh.eurekacientconsumer.service.BadProcessingException: Bad processing
    at io.ysh.eurekacientconsumer.service.ExternalRandomFailService.callService(ExternalRandomFailService.java:21)
    at io.github.resilience4j.retry.Retry.lambda$decorateSupplier$2(Retry.java:213)
    at io.github.resilience4j.retry.Retry.executeSupplier(Retry.java:430)
    at io.ysh.eurekacientconsumer.service.ExternalRandomFailServiceTest.callService(ExternalRandomFailServiceTest.java:33)
>> Call count = 2
    20:04:15.473 : Processing finished. Status = BadProcessingException
    20:04:16.484 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 3
    20:04:16.492 : Processing finished. Status = BadProcessingException
    20:04:17.498 : Processing finished. Status = FAILURE
    Final Result = FAILURE
>> Call count = 4
    20:04:17.504 : Processing finished. Status = FAILURE
    20:04:18.519 : Processing finished. Status = FAILURE
    Final Result = FAILURE
>> Call count = 5
    20:04:18.519 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 6
    20:04:18.520 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 7
    20:04:18.520 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 8
    20:04:18.520 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 9
    20:04:18.520 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 10
    20:04:18.520 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 11
    20:04:18.521 : Processing finished. Status = FAILURE
    20:04:19.527 : Processing finished. Status = FAILURE
    Final Result = FAILURE
>> Call count = 12
    20:04:19.527 : Processing finished. Status = BadProcessingException
    20:04:20.537 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 13
    20:04:20.537 : Processing finished. Status = FAILURE
    20:04:21.542 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 14
    20:04:21.542 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 15
    20:04:21.543 : Processing finished. Status = BadProcessingException
    20:04:22.550 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 16
    20:04:22.550 : Processing finished. Status = FAILURE
    20:04:23.556 : Processing finished. Status = FAILURE
    Final Result = FAILURE
>> Call count = 17
    20:04:23.557 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 18
    20:04:23.557 : Processing finished. Status = BadProcessingException
    20:04:24.561 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 19
    20:04:24.561 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS
>> Call count = 20
    20:04:24.561 : Processing finished. Status = SUCCESS
    Final Result = SUCCESS

springboot自动配置

配置在application.yml中

  • configs:retry配置,可以配置多个
    • retryExceptions:以列表的形式配置,需要全限定名称,ignoreExceptions一样配置
    • resultPredicate:自定义的Predicate,需要全限定名称
  • instances:retry实例名称,可以配置baseConfig指定具体的retry配置,也可以自行配置
resilience4j.retry:
  configs:
    default:
      maxRetryAttempts: 2
      waitDuration: 1s
      resultPredicate: io.ysh.eurekacientconsumer.RetryPredicate
      retryExceptions:
        - io.ysh.eurekacientconsumer.service.BadProcessingException
  instances:
    externalRandomFailService:
      baseConfig: default
      waitDuration: 1s

自定义的retryOnResultPredicate:如果返回结果为FAILURE,进行重试

public class RetryPredicate implements Predicate<String> {
    @Override
    public boolean test(String s) {
        return s.equals("FAILURE");
    }
}

注解@Retry有两个属性:

  • name:指定配置文件中retry instance
  • fallbackMethod:方法名,如果最大尝试次数后仍返回失败,执行该方法
@Service
public class ExternalRandomFailService {

    @Retry(name = "externalRandomFailService")
    public String callService() {
        double random = Math.random();
        if (random < 0.6) {
            System.out.println("\t" + LocalTime.now().truncatedTo(ChronoUnit.MILLIS) + " : Processing finished. Status = SUCCESS");
            return "SUCCESS";
        } else if (random < 0.8) {
            System.out.println("\t" + LocalTime.now().truncatedTo(ChronoUnit.MILLIS) + " : Processing finished. Status = FAILURE");
            return "FAILURE";
        } else {
            System.out.println("\t" + LocalTime.now().truncatedTo(ChronoUnit.MILLIS) + " : Processing finished. Status = BadProcessingException");
            throw new BadProcessingException("Bad processing");
        }
    }
}

测试方法,去掉retryconfig,之间执行服务调用即可,输出结果跟之前的一样

@SpringBootTest
class ExternalRandomFailServiceTest {

    @Autowired
    ExternalRandomFailService service;

    @Test
    void callService() {
        for (int i = 1; i <= 20; i++) {
            System.out.println(">> Call count = " + i);
            try {
                String result = service.callService();
                System.out.println("\tFinal Result = " + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

intervalBiFunction的使用,根据返回修改重试的间隔时间,可以在输出结果中观察到:

  • 第一次返回FAILURE,间隔2s后重试
  • 第一次返回异常,间隔3s后重试

在config.default中加入配置:

intervalBiFunction: io.ysh.eurekacientconsumer.CustomIntervalFunction
public class CustomIntervalFunction implements IntervalBiFunction<String> {

    @Override
    public Long apply(Integer integer, Either<Throwable, String> either) {
        long duration = 1000;
        if (either.isRight() && either.get().equals("FAILURE")) {
            duration = 2000;
        } else if (either.isLeft() && either.getLeft() instanceof BadProcessingException) {
            duration = 3000;
        }
        return duration;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容