iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 8
0
Software Development

30天從零開始 使用 Spring Boot 跟 Spring Cloud 建構完整微服務架構系列 第 8

Day 08 - 如何在 SpringBoot 中使用 Retry & Cache

Retry & Cache 算是常用到的功能

Retry

首先 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 正確運作
測試結果

Cache

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 結果

如果想針對每一個 cache 配置不同的時間可以參考 spring-boot中配置和使用Caffeine Cache

Retry & Cache 在 SpringBoot 中都相當方便使用跟配置
但卻可以大大幫忙我們提升效率跟避免偶發性異常失敗

是不是覺得一定要熟悉一下呢


上一篇
Day 07 - 寫 SpringBoot 的 Unit test
下一篇
Day 09 - yaml 配置規劃
系列文
30天從零開始 使用 Spring Boot 跟 Spring Cloud 建構完整微服務架構35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言