Retry & Cache 算是常用到的功能
首先 Retry 常會用在網路存取的錯誤重試, 因為網路最容易有不穩 瞬斷 等狀況
或是跑批次排程時, 有時檔案沒傳完或是沒準備好等等錯誤 都可以讓他自動重試,
有時候重新執行就正常了, 少一點處理善後的機會.
我們只需在 build.gradle 依賴增加宣告就可以, 目前 SpringBoot 1.5.9 搭配的 Retry 版本是 1.2.1.RELEASE
dependencies {
compile 'org.springframework.retry:spring-retry'
}
主程式要啟動 EnableRetry
package com.example.book;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
@EnableRetry
@SpringBootApplication
public class BookApplication {
public static void main(String[] args) {
SpringApplication.run(BookApplication.class, args);
}
}
準備一個假的 BookService.java 來實驗
package com.example.book;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import javax.persistence.NoResultException;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Service
public class BookService {
private final AtomicInteger atomicInteger = new AtomicInteger(0);
@Retryable(include = {NoResultException.class},
maxAttempts = 3,
backoff = @Backoff(value = 2000))
public Book getBook() {
int count = atomicInteger.incrementAndGet();
log.info("count = " + count);
if (count < 5) {
throw new NoResultException();
} else {
return new Book();
}
}
@Recover
public Book recover(NoResultException e) {
log.info("get NoResultException & return null");
return null;
}
}
說明:
Retryable 就是 Spring Retry 提供的註解
當捕捉到 NoResultException 的時候會自動進行重試
maxAttempts 表示最多執行三次
backoff 表示間隔,當捕捉到錯誤時,停多少秒後再重試
@Recover 則是定義該錯誤的處理,只能寫在同一個 Class 裡面喔,
當重試次數超過 maxAttempts 時候會跳到對應的 Recover 來處理,
如果原本的功能有 return 的話, 記得 Recover 也要有 return 喔
再來寫個啟動時候的處理程序來呼叫 BookService 方便測試用
ApplicationLoader.java
package com.example.book;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ApplicationLoader implements CommandLineRunner {
@Autowired
private BookService bookService;
@Override
public void run(String... args) throws Exception {
try {
bookService.getBook();
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看一下測試結果, 確認 Retry 正確運作
build.gradle 依賴增加宣告
dependencies {
compile('org.springframework.boot:spring-boot-starter-cache')
compile 'com.github.ben-manes.caffeine:caffeine:2.6.0'
}
增加 caffeine 是因為 Spring 的 Cache 策略有點少, 加上他會有比較多的彈性控制
主程式記得要 @EnableCaching
package com.example.book;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.retry.annotation.EnableRetry;
@EnableCaching
@EnableRetry
@SpringBootApplication
public class BookApplication {
public static void main(String[] args) {
SpringApplication.run(BookApplication.class, args);
}
}
接下來是我們測試用的程式, 負責回覆目前時間, 如果有被 cache 就不會拿到最新時間
TimeService.java
package com.example.book;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class TimeService {
@Cacheable(cacheNames = "getTime")
public Date getTime() {
return new Date();
}
@Cacheable("currentTimeMillis")
public Long getCurrentTimeMillis() {
return System.currentTimeMillis();
}
}
在我們配置檔 application.yml 內配置 cache 策略
spring.cache.cache-names: getTime,currentTimeMillis
spring.cache.caffeine.spec: maximumSize=100,expireAfterWrite=5s
spring.cache.cache-names 是對應到 cacheNames
spring.cache.caffeine.spec 就是 caffeine 提供給我們的策略了
有哪些策略可以配置請參考 javadoc Class CaffeineSpec
目前我是配置 存入後 cache 5 秒
再來我們測試用的啟動程式改一下 ApplicationLoader.java
package com.example.book;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
@Slf4j
@Component
public class ApplicationLoader implements CommandLineRunner {
@Autowired
private TimeService timeService;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
@Override
public void run(String... args) throws Exception {
for(int i = 0 ; i < 30 ; i++){
System.out.println(sdf.format(timeService.getTime()));
Thread.sleep(1000);
}
}
}
啟動一下看結果
如果想針對每一個 cache 配置不同的時間可以參考 spring-boot中配置和使用Caffeine Cache
Retry & Cache 在 SpringBoot 中都相當方便使用跟配置
但卻可以大大幫忙我們提升效率跟避免偶發性異常失敗
是不是覺得一定要熟悉一下呢