溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么集成Spring?Retry實現(xiàn)失敗重試和熔斷器模式

發(fā)布時間:2022-07-20 13:38:45 來源:億速云 閱讀:143 作者:iii 欄目:開發(fā)技術(shù)

這篇“怎么集成Spring Retry實現(xiàn)失敗重試和熔斷器模式”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么集成Spring Retry實現(xiàn)失敗重試和熔斷器模式”文章吧。

    背景

    在我們的大多數(shù)項目中,會有一些場景需要重試操作,而不是立即失敗,讓系統(tǒng)更加健壯且不易發(fā)生故障

    場景如下

    • 瞬時網(wǎng)絡(luò)抖動故障

    • 服務(wù)器重啟

    • 偶發(fā)死鎖

    • 某些上游的異?;蛘唔憫?yīng)碼,需要進行重試

    • 遠程調(diào)用

    • 從數(shù)據(jù)庫中獲取或存儲數(shù)據(jù)

    以上皆為瞬時故障。

    也會有一些場景,例如不是瞬時故障,例如接口響應(yīng)一直很慢,需要的是斷路器,如果還是繼續(xù)重試,會對服務(wù)有很大的影響,例如請求一次需要30s,如果還去不斷的重試,會拖垮我們的系統(tǒng),我們需要一定次數(shù)的失敗后停止向服務(wù)發(fā)送進一步的請求,并在一段時間后恢復(fù)發(fā)送請求

    Spring Retry提供了以下能力:

    • 失敗重試

    • 斷路器模式

    不支持艙壁bulkhead線程隔離
    不支持超時timeout機制

    實戰(zhàn)

    添加依賴

    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        <version>1.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${version}</version>
    </dependency>
    或者
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <aifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    啟用重試

    @Configuration
    @EnableRetry
    public class RetryConfig {
    }

    @Retryable

    在需要重試的方法上加上@Retryable注解

    部分參數(shù)如下

    • label: 重試的名字,系統(tǒng)唯一,默認 “”

    • maxAttempts:異常時重試次數(shù),默認 3

    • maxAttemptsExpression: SpEL表達式 ,從配置文件獲取maxAttempts的值,可以在application.yml設(shè)置,與maxAttempts二選一

    • exceptionExpression: SpEL表達式,匹配異常。例如:exceptionExpression = "#{message.contains('test')}"

    • include:需要重試的異常

    • exclude:不需要重試的異常

    • backoff:重試中的退避策略 ,@Backoff注解,部分參數(shù)如下:

    • value: 重試間隔ms,默認 1000

    • delay: 在指數(shù)情況下用作初始值,在均勻情況下用作最小值, 它與value屬性不能共存,當delay不設(shè)置的時候會去讀value屬性設(shè)置的值,如果delay設(shè)置的話則會忽略value屬性, 默認 0

    • delayExpression: SpEL表達式 ,從配置文件獲取delay的值,可以在application.yml設(shè)置,與delay二選一

    • multiplier: 則用作產(chǎn)生下一個退避延遲的乘數(shù) , 默認 0

    • delay = 2000, multiplier = 2 表示第一次重試間隔為2s,第二次為4秒,第三次為8s

    • maxDelay: 最大的重試間隔,當超過這個最大的重試間隔的時候,重試的間隔就等于maxDelay的值 默認 0

    @Service
    @Slf4j
    public class RetryService {
       
        @Retryable(value = RuntimeException.class)
        public void test(String param){
            log.info(param);
            throw new RuntimeException("laker Error");
        }
    }

    當拋出RuntimeException時會嘗試重試。

    根據(jù)@Retryable的默認行為,重試最多可能發(fā)生 3 次,重試之間有 1 秒的延遲。

    測試日志如下

    2022-07-16 18:23:46.274  INFO 10204 --- [           main] com.example.demo.retry.RetryService      : laker
    2022-07-16 18:23:47.278  INFO 10204 --- [           main] com.example.demo.retry.RetryService      : laker
    2022-07-16 18:23:48.289  INFO 10204 --- [           main] com.example.demo.retry.RetryService      : laker

    java.lang.RuntimeException: laker Error
        at com.example.demo.retry.RetryService.test(RetryService.java:18)
        at com.example.demo.retry.RetryService$$FastClassBySpringCGLIB$$41aa3d8d.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

    @Recover

    @Retryable方法重試失敗之后,最后就會調(diào)用@Recover方法。用于@Retryable失敗時的兜底處理方法。

    @Recover的方法必須要與@Retryable注解的方法保持一致,第一入?yún)橐卦嚨漠惓?,其他參?shù)與@Retryable保持一致,返回值也要一樣,否則無法執(zhí)行!,方法可以是public、private.

    @Service
    @Slf4j
    public class RetryService {
        @Retryable(value = RuntimeException.class)
        public void test(String param) {
            log.info(param);
            throw new RuntimeException("laker Error");
        }
        @Recover
        void recover(RuntimeException e, String param) {
            log.info("recover e:{},param:{}", e, param);
        }
    }

    在這里,當拋出RuntimeException時會嘗試重試。

    test方法在 3 次嘗試后不斷拋出 RuntimeException,則會調(diào)用recover()方法。

    測試日志如下:

    2022-07-16 18:40:19.828  INFO 4308 --- [           main] com.example.demo.retry.RetryService      : laker
    2022-07-16 18:40:20.834  INFO 4308 --- [           main] com.example.demo.retry.RetryService      : laker
    2022-07-16 18:40:21.848  INFO 4308 --- [           main] com.example.demo.retry.RetryService      : laker
    2022-07-16 18:40:21.849  INFO 4308 --- [           main] com.example.demo.retry.RetryService      : recover e:java.lang.RuntimeException: laker Error,param:laker

    @CircuitBreaker

    熔斷模式:指在具體的重試機制下失敗后打開斷路器,過了一段時間,斷路器進入半開狀態(tài),允許一個進入重試,若失敗再次進入斷路器,成功則關(guān)閉斷路器,注解為@CircuitBreaker,具體包括熔斷打開時間、重置過期時間。

    同一個方法上與@Retryable注解只能二選一,否則注解失效

    相關(guān)代碼參見CircuitBreakerRetryPolicy.java

    主要參數(shù)如下:

    • maxAttempts: 最大嘗試次數(shù)(包括第一次失?。?,默認為 3

    • maxAttemptsExpression: SpEL表達式 ,從配置文件獲取maxAttempts的值,可以在application.yml設(shè)置,與maxAttempts二選一

    • openTimeout:當在此超時時間內(nèi)達到maxAttempts失敗時,電路會自動打開,防止訪問下游組件。默認為 5000

    • openTimeoutExpression: SpEL表達式

    • resetTimeout: 如果電路打開的時間超過此超時時間,則它會在下一次調(diào)用時重置,以使下游組件有機會再次響應(yīng)。默認為 20000

    • resetTimeoutExpression: SpEL表達式

    • label:短路器的名字,系統(tǒng)唯一

    • include:需要短路的異常

    • exclude:不需要短路的異常

       @CircuitBreaker(maxAttempts = 2, openTimeout = 1000, resetTimeout = 2000, value = RuntimeException.class)
        public void testCircuitBreaker(String param) {
            log.info(param);
            throw new RuntimeException("laker Error");
        }
    
        @Recover
        void recover(RuntimeException e, String param) {
            log.info("recover e:{},param:{}", e, param);
        }

    當拋出RuntimeException時會嘗試熔斷。

    在openTimeout 1s時間內(nèi),觸發(fā)異常超過2次,斷路器打開,testCircuitBreaker業(yè)務(wù)方法不允許執(zhí)行,直接執(zhí)行恢復(fù)方法recover。

    經(jīng)過resetTimeout 2s后,熔斷器關(guān)閉,繼續(xù)執(zhí)行testCircuitBreaker業(yè)務(wù)方法。

    注意:這里沒有上面@Retryable的能力了哦,但是這個實際項目還是很需要的。

    測試日志如下:

    2022-07-16 19:22:26.195  laker0
    2022-07-16 19:22:26.195  recover e:java.lang.RuntimeException: laker Error,param:laker0
    2022-07-16 19:22:26.196  laker1
    2022-07-16 19:22:26.196  recover e:java.lang.RuntimeException: laker Error,param:laker1
    2022-07-16 19:22:26.196  recover e:java.lang.RuntimeException: laker Error,param:laker2
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker3
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker4
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker5
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker6
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker7
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker8
    2022-07-16 19:22:26.197  recover e:java.lang.RuntimeException: laker Error,param:laker9
    2022-07-16 19:22:32.206  laker3
    2022-07-16 19:22:32.206  recover e:java.lang.RuntimeException: laker Error,param:laker0

    高級實戰(zhàn)

    上面說到了,斷路器@CircuitBreaker 并么有攜帶重試功能,所有我們實際項目要結(jié)合2者使用。

    方式一 @CircuitBreaker + RetryTemplate

    1.自定義RetryTemplate

    @Configuration
    @EnableRetry
    public class RetryConfig {
        @Bean
        public RetryTemplate retryTemplate() {
            RetryTemplate retryTemplate = new RetryTemplate();
            FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
            // 退避策略 因為是瞬時異常 所以不宜過大,100ms即可
            fixedBackOffPolicy.setBackOffPeriod(100L);
            retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
            SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
            // 重試3次
            retryPolicy.setMaxAttempts(3);
            retryTemplate.setRetryPolicy(retryPolicy);
            return retryTemplate;
        }
    }

    2.在斷路器中用retryTemplate包裹一層

       @CircuitBreaker(maxAttempts = 2, openTimeout = 1000, resetTimeout = 2000, value = RuntimeException.class)
        public String testCircuitBreaker(String param) {
            return retryTemplate.execute(context -> {
                log.info(String.format("Retry count %d", context.getRetryCount()) + param);
                throw new RuntimeException("laker Error");
            });
        }
        @Recover
        String recover(RuntimeException e, String param) {
            log.info("recover e:{},param:{}", e, param);
            return "";
        }

    測試日志如下:

    2022-07-16 20:14:11.385 Retry count 0laker0
    2022-07-16 20:14:11.496 Retry count 1laker0
    2022-07-16 20:14:11.606 Retry count 2laker0
    2022-07-16 20:14:11.607 recover e:java.lang.RuntimeException: laker Error,param:laker0
    2022-07-16 20:14:11.608 Retry count 0laker1
    2022-07-16 20:14:11.714 Retry count 1laker1
    2022-07-16 20:14:11.826 Retry count 2laker1
    2022-07-16 20:14:11.826 recover e:java.lang.RuntimeException: laker Error,param:laker1
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker2
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker3
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker4
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker5
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker6
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker7
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker8
    2022-07-16 20:14:11.827 recover e:java.lang.RuntimeException: laker Error,param:laker9

    方式二 @CircuitBreaker + @Retryable

    定義2個springBean,一個用于重試,一個用于熔斷,且是熔斷包含著重試,否則會失效。

    @Service
    @Slf4j
    public class RetryService {
        @Autowired
        RetryTemplate retryTemplate;
    
        @Retryable(value = RuntimeException.class,backoff = @Backoff(delay = 100))
        public void test(String param) {
            log.info(param);
            throw new RuntimeException("laker Error");
        }
    }
    
    @Service
    @Slf4j
    public class CircuitBreakerService {
    
        @Autowired
        RetryService retryService;
    
        @CircuitBreaker(maxAttempts = 2, openTimeout = 1000, resetTimeout = 2000, value = RuntimeException.class)
        public void testCircuitBreaker(String param) {
            // 這里是添加了重試注解的方法
            retryService.test(param);
        }
    
        @Recover
        void recover(RuntimeException e, String param) {
            log.info("recover e:{},param:{}", e, param);
        }
    }

    以上就是關(guān)于“怎么集成Spring Retry實現(xiàn)失敗重試和熔斷器模式”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI