1. 程式人生 > 實用技巧 >本地快取解決方案-Caffeine Cache

本地快取解決方案-Caffeine Cache

1.1 關於Caffeine Cache

​ Google Guava Cache是一種非常優秀本地快取解決方案,提供了基於容量,時間和引用的快取回收方式。基於容量的方式內部實現採用LRU演演算法,基於引用回收很好的利用了Java虛擬機器器的垃圾回收機制。其中的快取構造器CacheBuilder採用構建者模式提供了設定好各種引數的快取物件,快取核心類LocalCache裡面的內部類Segment與jdk1.7及以前的ConcurrentHashMap非常相似,都繼承於ReetrantLock,還有六個佇列,以實現豐富的本地快取方案。

​ 通俗的講,Guva是google開源的一個公共java庫,類似於Apache Commons,它提供了集合,反射,快取,科學計算,xml,io等一些工具類庫。cache只是其中的一個模組。使用Guva cache能夠方便快速的構建本地快取。

Caffeine是使用Java8對Guava快取的重寫版本,在Spring Boot 2.0中將取代Guava。如果出現Caffeine,

CaffeineCacheManager將會自動配置。

1.1.1 為什麼要用本地快取

相對於IO操作

速度快,效率高

相對於Redis

Redis是一種優秀的分散式快取實現,受限於網路卡等原因,遠水救不了近火

DB + Redis + LocalCache = 高效儲存,高效訪問

訪問速度和花費的關係如下圖所示:

1.1.2 什麼時候用
  • 願意消耗一些記憶體空間來提升速度
  • 預料到某些鍵會被多次查詢
  • 快取中存放的資料總量不會超出記憶體容量
1.1.3 怎麼用
  1. 設定快取容量
  2. 設定超時時間
  3. 提供移除監聽器
  4. 提供快取載入器
  5. 構建快取

1.2 使用Caffeine Cache

使用springboot2.x操作Caffeine Cache

搭建工程:Springboot2.x + MyBatis + MySQL + Caffeine Cache

Caffeine是使用Java8對Guava快取的重寫版本,在Spring 5.0或者Spring Boot 2.0中將取代,基於LRU演演算法實現,

支援多種快取過期策略。

1.2.1 準備工作
  • 準備好資料庫和資料表並插入相應實驗資料(MySQL)
-- 新建表
create database if not exists guavach charset utf8;
-- 使用表
use guavach;
-- 建立使用者表tbl_user
create table tbl_user(
id int(10) not null primary key auto_increment,
name varchar(50) not null,
age int(20) not null
)engine=innodb default charset=utf8;
-- 初始化資料
insert into tbl_user values('1','codesheep.cn','25');
insert into tbl_user values('2','hansongwang99','30');
insert into tbl_user values('3','劉能','35');
insert into tbl_user values('4','趙四','38');
1.2.2 java工程
1.2.2.1 新增依賴
        <dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
1.2.2.2 配置類

引入 CaffeineCache的配置檔案 CaffeineCacheConfig

@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CacheManager cacheManager(){
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
//Caffeine配置
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
//最後一次寫入後經過固定時間過期
.expireAfterWrite(10, TimeUnit.SECONDS)
//maximumSize=[long]: 快取的最大條數
.maximumSize(1000);
cacheManager.setCaffeine(caffeine);
return cacheManager;
}
}

說明:

Caffeine配置說明:

  • initialCapacity=[integer]: 初始的快取空間大小
  • maximumSize=[long]: 快取的最大條數
  • maximumWeight=[long]: 快取的最大權重
  • expireAfterAccess=[duration]: 最後一次寫入或訪問後經過固定時間過期
  • expireAfterWrite=[duration]: 最後一次寫入後經過固定時間過期
  • refreshAfterWrite=[duration]: 建立快取或者最近一次更新快取後經過固定的時間間隔,重新整理快取
  • weakKeys: 開啟key的弱引用
  • weakValues:開啟value的弱引用
  • softValues:開啟value的軟引用
  • recordStats:開發統計功能

    注意:
  • expireAfterWrite和expireAfterAccess同事存在時,以expireAfterWrite為準。
  • maximumSize和maximumWeight不可以同時使用
  • weakValues和softValues不可以同時使用
1.2.2.3 配置檔案
server:
port: 9020
# Mysql
spring:
datasource:
url: jdbc:mysql://localhost:3306/guavach?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# mybatis配置
mybatis:
configuration:
map-underscore-to-camel-case: true
#debug: true
1.2.2.4 實體類
public class User implements Serializable {
private static final long serialVersionUID=1L;
private Long id;
private String name;
private Integer age; public static long getSerialVersionUID() {
return serialVersionUID;
} public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
}
}
1.2.2.5 mapper
@Mapper
public interface UserMapper { @Select("select * from tbl_user")
List<User> getUsers(); @Insert("insert into tbl_user values(#{name},#{age})")
int addUser(User user); @Select("select * from tbl_user where name=#{userName}")
List<User> getUserByName(String userName);
}
1.2.2.6 service
@Service
public class UserService{
@Autowired
private UserMapper userMapper; public List<User> getUsers() {
return userMapper.getUsers();
} public int addUser(User user) {
return userMapper.addUser(user);
} @Cacheable(value = "user",key = "#userName")
public List<User> getUserByName(String userName) {
List<User> users=userMapper.getUserByName(userName);
System.out.println("從資料庫中讀取,而非從快取讀取!");
return users;
} }

說明:在 getUsersByName介面上添加了註解:@Cacheable。這是 快取的使用註解之一,除此之外常用的還有 @CachePut@CacheEvit,分別簡單介紹一下:

  1. @Cacheable:配置在 getUsersByName方法上表示其返回值將被加入快取。同時在查詢時,會先從快取中獲取,若不存在才再發起對資料庫的訪問
  2. @CachePut:配置於方法上時,能夠根據引數定義條件來進行快取,其與 @Cacheable不同的是使用 @CachePut標註的方法在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中,所以主要用於資料新增和修改操作上
  3. @CacheEvict:配置於方法上時,表示從快取中移除相應資料。
1.2.2.7 controller
@RestController
public class UserController {
@Autowired
private UserService userService; @Autowired
CacheManager cacheManager; @PostMapping("/getuserbyname")
public List<User> getUserByName(@RequestBody User user){
System.out.println("------------");
System.out.println("call /getuserbyname");
List<User> users = userService.getUserByName(user.getName());
return users; } @GetMapping("/hehe")
public String hehe(){
return "hehhehhehe";
}
}
1.2.2.8 啟動類
@SpringBootApplication
@EnableCaching//新增註解
public class Caffeinecache01Application { public static void main(String[] args) {
SpringApplication.run(Caffeinecache01Application.class, args);
} }
1.3 執行

啟動主類,使用postman測試

開啟postman,輸入json欄位,點選send按鈕後,顯示返回結果

檢視IDEA控制檯輸出

第一次獲取時,為從資料庫讀取

接著點選,間隔時間少於10秒,顯示如下結果,可以看到快取的啟用和失效時的效果如下所示(上文 Guava Cache的配置檔案中設定了快取 user的實效時間為 10s):