基于內(nèi)存和 Redis 的兩級(jí) Java 緩存框架
環(huán)境:SpringBoot2.7.12 + j2cache2.8.5
1. 簡介
J2Cache 是 OSChina 目前正在使用的兩級(jí)緩存框架(要求至少 Java 8)。第一級(jí)緩存使用內(nèi)存(同時(shí)支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine),第二級(jí)緩存使用 Redis(推薦)/Memcached 。由于大量的緩存讀取會(huì)導(dǎo)致 L2 的網(wǎng)絡(luò)成為整個(gè)系統(tǒng)的瓶頸,因此 L1 的目標(biāo)是降低對(duì) L2 的讀取次數(shù)。該緩存框架主要用于集群環(huán)境中。單機(jī)也可使用,用于避免應(yīng)用重啟導(dǎo)致的緩存冷啟動(dòng)后對(duì)后端業(yè)務(wù)的沖擊。
數(shù)據(jù)讀取
- 讀取順序 -> L1 -> L2 -> DB
- 數(shù)據(jù)更新
從數(shù)據(jù)庫中讀取最新數(shù)據(jù),依次更新 L1 -> L2 ,發(fā)送廣播清除某個(gè)緩存信息
接收到廣播(手工清除緩存 & 一級(jí)緩存自動(dòng)失效),從 L1 中清除指定的緩存信息
2. 實(shí)戰(zhàn)案例
2.1 依賴管理
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-core</artifactId>
<version>2.8.5-release</version>
</dependency>
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-spring-boot2-starter</artifactId>
<version>2.8.0-release</version>
</dependency>
2.2 配置
redis:
# 地址, 多個(gè)地址使用‘,’逗號(hào)分割
hosts: localhost:6379
# 數(shù)據(jù)庫索引
database: 11
# 密碼
password: xxxooo
# 連接超時(shí)時(shí)間
timeout: 10s
# 連接池中的最小空閑連接
min-idle: 0
# 連接池中的最大空閑連接
max-idle: 8
# 連接池的最大數(shù)據(jù)庫連接數(shù)
max-active: 8
# #連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
max-wait: -1ms
---
j2cache:
openSpringCache: true
# 緩存中不存在時(shí),運(yùn)行緩存空對(duì)象
allowNullValues: true
redisClient: lettuce
l2CacheOpen: true
# 一級(jí)緩存使用caffeine
L1:
provider_class: caffeine
L2:
#使用springRedis替換二級(jí)緩存
provider_class: net.oschina.j2cache.cache.support.redis.SpringRedisProvider
config_section: redis
#使用springRedis進(jìn)行廣播通知緩失效
broadcast: net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
# 上面配置的一級(jí)緩存為caffeine, 那么這里對(duì)一級(jí)緩存的配置就必須以這個(gè)caffeine開頭
caffeine:
# 配置一級(jí),二級(jí)緩存的region,有效時(shí)間
region.xj: 10000, 120s
---
spring:
cache:
# 一級(jí)緩存使用caffeine
type: caffeine
2.3 核心操作類
@Service
public class UserService {
private final UserRepository userRepository ;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository ;
}
@Transactional
public User save(User user) {
return this.userRepository.saveAndFlush(user) ;
}
@Cacheable(value = {"xj"}, key = "#id")
public User get(Long id) {
return this.userRepository.findById(id).orElse(null) ;
}
@Transactional
@CacheEvict(value = {"xj"}, key = "#id")
public void remove(Long id) {
this.userRepository.deleteById(id) ;
}
}
以上是基本的操作,非常簡單。
2.4 Controller接口
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService ;
// 通過CacheChannel操作j2cache緩存方法
private final CacheChannel cacheChannel;
public UserController(UserService userService, CacheChannel cacheChannel) {
this.userService = userService ;
this.cacheChannel = cacheChannel ;
}
@GetMapping("/save")
public User save() {
User user = new User() ;
int num = new Random().nextInt(80);
user.setAge(num) ;
user.setName("姓名 - " + num) ;
user.setSex(num >= 50 ? "男" : "女") ;
return this.userService.save(user) ;
}
@GetMapping("/{id}")
public Object get(@PathVariable("id") Long id) {
// 從指定的region,指定的key獲取數(shù)據(jù),如果一級(jí),二級(jí)緩存中不存在,則通過第三個(gè)參數(shù)Function手動(dòng)獲取
// 如果緩存中不存在時(shí),同時(shí)配置了允許緩存空對(duì)象,則會(huì)緩存一個(gè)空對(duì)象到緩存中
return this.cacheChannel.get("xj", id.toString(), key -> this.userService.get(id) , true) ;
}
@GetMapping("/delete/{id}")
public Object remove(@PathVariable("id") Long id) {
this.userService.remove(id) ;
return "success" ;
}
}
2.5 測試
先通過save接口添加數(shù)據(jù)
圖片
查詢id=2的數(shù)據(jù)
圖片
level=3 表示本次數(shù)據(jù)緩存中不存在,從數(shù)據(jù)庫中獲取的。刷新頁面
圖片
level=2,本次數(shù)據(jù)從二級(jí)緩存redis中獲取。再次刷新頁面
圖片
level=1,本次數(shù)據(jù)從一級(jí)緩存caffeine中獲取。后續(xù)再怎么刷新只要緩存沒有過期都將從一級(jí)緩存中獲取。
測試不存在的數(shù)據(jù)
圖片
從數(shù)據(jù)庫中查詢不存在的數(shù)據(jù)。
圖片
緩存了空對(duì)象。
測試刪除數(shù)據(jù)
圖片
緩存中會(huì)立即清除
圖片
以上是本篇文章的全部內(nèi)容,希望對(duì)你有幫助。
完畢?。?!