Spring Boot 2.x 把 Guava 幹掉了選擇本地快取之王 Caffeine(推薦)
環境配置:
- 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 | - | 開發統計功能 |
注意:
weakValues
和softValues
不可以同時使用。maximumSize
和maximumWeight
不可以同時使用。expireAfterWrite
和expireAfterAccess
同事存在時,以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
到此這篇關於Spring Boot 2.x 把 Guava 幹掉了選擇本地快取之王 Caffeine的文章就介紹到這了,更多相關Spring Boot 2.x Caffeine內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!