Spring Boot 2.x 把 Guava 干掉了,選擇本地緩存之王 Caffeine!


作者:超級小豆丁
來源:http://www.mydlq.club/article/56/

環境配置:

  • JDK 版本:1.8
  • Caffeine 版本:2.8.0
  • SpringBoot 版本:2.2.2.RELEASE

一、本地緩存介紹

緩存在日常開發中啟動至關重要的作用,由於是存儲在內存中,數據的讀取速度是非常快的,能大量減少對數據庫的訪問,減少數據庫的壓力。

之前介紹過 Redis 這種 NoSql 作為緩存組件,它能夠很好的作為分布式緩存組件提供多個服務間的緩存,但是 Redis 這種還是需要網絡開銷,增加時耗。本地緩存是直接從本地內存中讀取,沒有網絡開銷,例如秒殺系統或者數據量小的緩存等,比遠程緩存更合適。

二、緩存組件 Caffeine 介紹

按 Caffeine Github 文檔描述,Caffeine 是基於 JAVA 8 的高性能緩存庫。並且在 spring5 (springboot 2.x) 后,spring 官方放棄了 Guava,而使用了性能更優秀的 Caffeine 作為默認緩存組件。

1、Caffeine 性能

可以通過下圖觀測到,在下面緩存組件中 Caffeine 性能是其中最好的。

2、Caffeine 配置說明

參數 類型 描述
initialCapacity integer 初始的緩存空間大小
maximumSize long 緩存的最大條數
maximumWeight long 緩存的最大權重
expireAfterAccess duration 最后一次寫入或訪問后經過固定時間過期
refreshAfterWrite duration 最后一次寫入后經過固定時間過期
refreshAfterWrite duration 創建緩存或者最近一次更新緩存后經過固定的時間間隔,刷新緩存
weakKeys boolean 打開 key 的弱引用
weakValues boolean 打開 value 的弱引用
softValues boolean 打開 value 的軟引用
recordStats - 開發統計功能

注意:

  • weakValuessoftValues 不可以同時使用。
  • maximumSizemaximumWeight 不可以同時使用。
  • expireAfterWriteexpireAfterAccess 同事存在時,以 expireAfterWrite 為准。

3、軟引用與弱引用

  • 軟引用: 如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。
  • 弱引用: 弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存
// 軟引用
Caffeine.newBuilder().softValues().build();

// 弱引用
Caffeine.newBuilder().weakKeys().weakValues().build();

三、SpringBoot 集成 Caffeine 兩種方式

SpringBoot 有倆種使用 Caffeine 作為緩存的方式:

方式一: 直接引入 Caffeine 依賴,然后使用 Caffeine 方法實現緩存。

方式二: 引入 Caffeine 和 Spring Cache 依賴,使用 SpringCache 注解方法實現緩存。

下面將介紹下,這倆中集成方式都是如何實現的。

Spring Boot 基礎就不介紹了,推薦看下這個教程:

https://github.com/javastacks/spring-boot-best-practice

四、SpringBoot 集成 Caffeine 方式一

1、Maven 引入相關依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <groupId>mydlq.club</groupId>
    <artifactId>springboot-caffeine-cache-example-1</artifactId>
    <version>0.0.1</version>
    <name>springboot-caffeine-cache-example-1</name>
    <description>Demo project for Spring Boot Cache</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、配置緩存配置類

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {

    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                // 設置最后一次寫入或訪問后經過固定時間過期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // 初始的緩存空間大小
                .initialCapacity(100)
                // 緩存的最大條數
                .maximumSize(1000)
                .build();
    }

}

3、定義測試的實體對象

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class UserInfo {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
}

4、定義服務接口類和實現類

UserInfoService

import mydlq.club.example.entity.UserInfo;

public interface UserInfoService {

    /**
     * 增加用戶信息
     *
     * @param userInfo 用戶信息
     */
    void addUserInfo(UserInfo userInfo);

    /**
     * 獲取用戶信息
     *
     * @param id 用戶ID
     * @return 用戶信息
     */
    UserInfo getByName(Integer id);

    /**
     * 修改用戶信息
     *
     * @param userInfo 用戶信息
     * @return 用戶信息
     */
    UserInfo updateUserInfo(UserInfo userInfo);

    /**
     * 刪除用戶信息
     *
     * @param id 用戶ID
     */
    void deleteById(Integer id);

}

UserInfoServiceImpl

import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;

@Slf4j
@Service
public class UserInfoServiceImpl implements UserInfoService {

    /**
     * 模擬數據庫存儲數據
     */
    private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();

    @Autowired
    Cache<String, Object> caffeineCache;

    @Override
    public void addUserInfo(UserInfo userInfo) {
        log.info("create");
        userInfoMap.put(userInfo.getId(), userInfo);
        // 加入緩存
        caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
    }

    @Override
    public UserInfo getByName(Integer id) {
        // 先從緩存讀取
        caffeineCache.getIfPresent(id);
        UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));
        if (userInfo != null){
            return userInfo;
        }
        // 如果緩存中不存在,則從庫中查找
        log.info("get");
        userInfo = userInfoMap.get(id);
        // 如果用戶信息不為空,則加入緩存
        if (userInfo != null){
            caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
        }
        return userInfo;
    }

    @Override
    public UserInfo updateUserInfo(UserInfo userInfo) {
        log.info("update");
        if (!userInfoMap.containsKey(userInfo.getId())) {
            return null;
        }
        // 取舊的值
        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
        // 替換內容
        if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
            oldUserInfo.setAge(userInfo.getAge());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getName())) {
            oldUserInfo.setName(userInfo.getName());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
            oldUserInfo.setSex(userInfo.getSex());
        }
        // 將新的對象存儲,更新舊對象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 替換緩存中的值
        caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);
        return oldUserInfo;
    }

    @Override
    public void deleteById(Integer id) {
        log.info("delete");
        userInfoMap.remove(id);
        // 從緩存中刪除
        caffeineCache.asMap().remove(String.valueOf(id));
    }

}

5、測試的 Controller 類

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping
public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    @GetMapping("/userInfo/{id}")
    public Object getUserInfo(@PathVariable Integer id) {
        UserInfo userInfo = userInfoService.getByName(id);
        if (userInfo == null) {
            return "沒有該用戶";
        }
        return userInfo;
    }

    @PostMapping("/userInfo")
    public Object createUserInfo(@RequestBody UserInfo userInfo) {
        userInfoService.addUserInfo(userInfo);
        return "SUCCESS";
    }

    @PutMapping("/userInfo")
    public Object updateUserInfo(@RequestBody UserInfo userInfo) {
        UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
        if (newUserInfo == null){
            return "不存在該用戶";
        }
        return newUserInfo;
    }

    @DeleteMapping("/userInfo/{id}")
    public Object deleteUserInfo(@PathVariable Integer id) {
        userInfoService.deleteById(id);
        return "SUCCESS";
    }

}

五、SpringBoot 集成 Caffeine 方式二

1、Maven 引入相關依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <groupId>mydlq.club</groupId>
    <artifactId>springboot-caffeine-cache-example-2</artifactId>
    <version>0.0.1</version>
    <name>springboot-caffeine-cache-example-2</name>
    <description>Demo project for Spring Boot caffeine</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、配置緩存配置類

@Configuration
public class CacheConfig {

    /**
     * 配置緩存管理器
     *
     * @return 緩存管理器
     */
    @Bean("caffeineCacheManager")
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                // 設置最后一次寫入或訪問后經過固定時間過期
                .expireAfterAccess(60, TimeUnit.SECONDS)
                // 初始的緩存空間大小
                .initialCapacity(100)
                // 緩存的最大條數
                .maximumSize(1000));
        return cacheManager;
    }

}

3、定義測試的實體對象

@Data
@ToString
public class UserInfo {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
}

4、定義服務接口類和實現類

服務接口

import mydlq.club.example.entity.UserInfo;

public interface UserInfoService {

    /**
     * 增加用戶信息
     *
     * @param userInfo 用戶信息
     */
    void addUserInfo(UserInfo userInfo);

    /**
     * 獲取用戶信息
     *
     * @param id 用戶ID
     * @return 用戶信息
     */
    UserInfo getByName(Integer id);

    /**
     * 修改用戶信息
     *
     * @param userInfo 用戶信息
     * @return 用戶信息
     */
    UserInfo updateUserInfo(UserInfo userInfo);

    /**
     * 刪除用戶信息
     *
     * @param id 用戶ID
     */
    void deleteById(Integer id);

}

服務實現類

import lombok.extern.slf4j.Slf4j;
import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;

@Slf4j
@Service
@CacheConfig(cacheNames = "caffeineCacheManager")
public class UserInfoServiceImpl implements UserInfoService {

    /**
     * 模擬數據庫存儲數據
     */
    private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();

    @Override
    @CachePut(key = "#userInfo.id")
    public void addUserInfo(UserInfo userInfo) {
        log.info("create");
        userInfoMap.put(userInfo.getId(), userInfo);
    }

    @Override
    @Cacheable(key = "#id")
    public UserInfo getByName(Integer id) {
        log.info("get");
        return userInfoMap.get(id);
    }

    @Override
    @CachePut(key = "#userInfo.id")
    public UserInfo updateUserInfo(UserInfo userInfo) {
        log.info("update");
        if (!userInfoMap.containsKey(userInfo.getId())) {
            return null;
        }
        // 取舊的值
        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
        // 替換內容
        if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
            oldUserInfo.setAge(userInfo.getAge());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getName())) {
            oldUserInfo.setName(userInfo.getName());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
            oldUserInfo.setSex(userInfo.getSex());
        }
        // 將新的對象存儲,更新舊對象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 返回新對象信息
        return oldUserInfo;
    }

    @Override
    @CacheEvict(key = "#id")
    public void deleteById(Integer id) {
        log.info("delete");
        userInfoMap.remove(id);
    }

}

5、測試的 Controller 類

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping
public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    @GetMapping("/userInfo/{id}")
    public Object getUserInfo(@PathVariable Integer id) {
        UserInfo userInfo = userInfoService.getByName(id);
        if (userInfo == null) {
            return "沒有該用戶";
        }
        return userInfo;
    }

    @PostMapping("/userInfo")
    public Object createUserInfo(@RequestBody UserInfo userInfo) {
        userInfoService.addUserInfo(userInfo);
        return "SUCCESS";
    }

    @PutMapping("/userInfo")
    public Object updateUserInfo(@RequestBody UserInfo userInfo) {
        UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
        if (newUserInfo == null){
            return "不存在該用戶";
        }
        return newUserInfo;
    }

    @DeleteMapping("/userInfo/{id}")
    public Object deleteUserInfo(@PathVariable Integer id) {
        userInfoService.deleteById(id);
        return "SUCCESS";
    }

}

參考地址:

https://www.jianshu.com/p/c72fb0c787fc
https://www.cnblogs.com/rickiyang/p/11074158.html
https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-caffeine-cache-example

近期熱文推薦:

1.Java 15 正式發布, 14 個新特性,刷新你的認知!!

2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!

3.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看。。

4.吊打 Tomcat ,Undertow 性能很炸!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM