一、框架核心概述(是什么)
这是一套 本地缓存(caffeine)+ 分布式缓存(redis)的多级缓存框架,基于 spring 生态实现,核心目标是兼顾缓存访问性能与分布式环境下的数据一致性。
核心特性
- 多级缓存架构:一级缓存(本地 caffeine)提升响应速度,二级缓存(redis)保证分布式一致性
- 灵活配置:支持过期时间、过期模式、null 值缓存、本地缓存容量限制等
- 动态 key 生成:基于 spel 表达式实现缓存 key 动态拼接
- 并发安全:内置分布式锁(redisson),解决缓存穿透/击穿/雪崩问题
- 完整 api:支持缓存增删改查、原子操作、批量处理等
技术栈依赖
- 核心框架:spring boot、spring context
- 缓存组件:caffeine(本地缓存)、redis(分布式缓存)
- 分布式锁:redisson
- 序列化:jackson
- 表达式解析:spring el
二、框架核心价值(为什么用)
1. 解决的核心问题
| 问题场景 | 框架解决方案 |
|---|---|
| 缓存穿透(查询不存在的数据) | 支持 null 值缓存 + 分布式锁防重复查询 |
| 缓存击穿(热点 key 过期) | 分布式锁 + 缓存自动续期 |
| 缓存雪崩(大量 key 同时过期) | 过期时间随机偏移 + 多级缓存降级 |
| 分布式缓存一致性 | redis 集中存储 + 缓存更新同步 |
| 本地缓存性能瓶颈 | caffeine 本地缓存(o(1) 访问速度) |
2. 相比单一缓存的优势
- 比纯 redis 缓存:减少网络 io 开销,本地缓存响应时间提升 10-100 倍
- 比纯本地缓存:支持分布式部署,避免节点间数据不一致
- 比 spring cache 原生:支持更灵活的配置(过期模式、容量限制)和更完善的并发控制
三、快速上手(怎么做)
1. 依赖配置(maven)
<!-- spring boot 基础依赖 -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
<!-- caffeine 本地缓存 -->
<dependency>
<groupid>com.github.ben-manes.caffeine</groupid>
<artifactid>caffeine</artifactid>
<version>3.1.8</version>
</dependency>
<!-- redisson 分布式锁 -->
<dependency>
<groupid>org.redisson</groupid>
<artifactid>redisson-spring-boot-starter</artifactid>
<version>3.25.0</version>
</dependency>
<!-- jackson 序列化 -->
<dependency>
<groupid>com.fasterxml.jackson.core</groupid>
<artifactid>jackson-databind</artifactid>
</dependency>2. 配置文件(application.yml)
spring:
application:
name: demo-app # 用于 redis key 前缀
redis:
host: 127.0.0.1
port: 6379
password: 123456
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 23. 基础使用示例
3.1 编程式使用(直接操作缓存 api)
import org.springframework.stereotype.service;
import javax.annotation.resource;
import java.util.concurrent.timeunit;
@service
public class userservice {
// 注入缓存管理器
@resource
private cachemanager cachemanager;
// 缓存配置(可通过 @configurationproperties 注入)
private final multicacheconfig usercacheconfig = new multicacheconfig(
30, timeunit.minutes, 20, true, true // ttl=30分钟,允许缓存null,启用本地缓存
);
/**
* 查询用户:缓存命中返回,未命中查库并缓存
*/
public user getuserbyid(long userid) {
// 1. 获取缓存实例(缓存名称:usercache)
cache cache = cachemanager.getcache("usercache", usercacheconfig);
// 2. 生成缓存key(支持spel表达式,这里直接拼接)
string cachekey = "user:" + userid;
// 3. 缓存查询(未命中时执行supplier查库)
return (user) cache.get(cachekey, user.class, () -> {
// 数据库查询逻辑(仅缓存未命中时执行)
return userdao.selectbyid(userid);
});
}
/**
* 更新用户:同步更新数据库和缓存
*/
public void updateuser(user user) {
cache cache = cachemanager.getcache("usercache", usercacheconfig);
string cachekey = "user:" + user.getid();
// 1. 更新数据库
userdao.updatebyid(user);
// 2. 更新缓存(覆盖旧值)
cache.put(cachekey, user);
}
/**
* 删除用户:同步删除数据库和缓存
*/
public void deleteuser(long userid) {
cache cache = cachemanager.getcache("usercache", usercacheconfig);
string cachekey = "user:" + userid;
// 1. 删除数据库记录
userdao.deletebyid(userid);
// 2. 删除缓存(避免脏数据)
cache.evict(cachekey);
}
}3.2 注解式使用(基于 aop 切面)
import java.lang.annotation.*;
// 1. 自定义缓存注解(可扩展)
@target(elementtype.method)
@retention(retentionpolicy.runtime)
@documented
public @interface mycacheable {
string cachename(); // 缓存名称
string key() default ""; // spel表达式key
multicacheconfig config() default @multicacheconfig(); // 缓存配置
}
// 2. 注解切面实现(基于 abstractcacheaspectsupport)
@aspect
@component
public class cacheaspect extends abstractcacheaspectsupport {
@resource
private cachemanager cachemanager;
@around("@annotation(mycacheable)")
public object cachearoundadvice(proceedingjoinpoint joinpoint, mycacheable mycacheable) throws throwable {
// 获取方法信息
method method = getspecificmethod(joinpoint);
object[] args = joinpoint.getargs();
object target = joinpoint.gettarget();
// 解析缓存key(支持spel表达式)
string cachekey = (string) generatekey(mycacheable.key(), method, args, target);
// 获取缓存实例
cache cache = cachemanager.getcache(mycacheable.cachename(), mycacheable.config());
// 缓存查询:命中返回,未命中执行目标方法并缓存
return cache.get(cachekey, method.getreturntype(), () -> {
try {
return joinpoint.proceed();
} catch (throwable e) {
throw new runtimeexception("缓存加载失败", e);
}
});
}
}
// 3. 业务层使用注解
@service
public class productservice {
@mycacheable(
cachename = "productcache",
key = "#root.methodname + ':' + #p0", // spel:方法名+第一个参数
config = @multicacheconfig(ttl = 60, timeunit = timeunit.minutes, cachenullvalues = true)
)
public product getproductbyid(long productid) {
// 数据库查询逻辑(缓存未命中时执行)
return productdao.selectbyid(productid);
}
}四、核心组件详解
1. 缓存核心接口(cache)
定义缓存操作标准 api,所有缓存实现的顶层接口:
public interface cache {
// 获取缓存(未命中返回null)
<t> t get(string key, class<t> resulttype);
// 获取缓存(未命中执行valueloader加载并缓存)
<t> t get(string key, class<t> resulttype, supplier<object> valueloader);
// 添加/覆盖缓存
void put(string key, object value);
// 原子操作:不存在时才添加
boolean putifabsent(string key, object value);
// 单个删除
void evict(string key);
// 批量删除
void multievict(collection<string> keys);
// 清空缓存
void clear();
}2. 多级缓存实现(multilevelcache)
整合 caffeine 本地缓存和 redis 分布式缓存:
public class multilevelcache implements cache {
private final string cachename;
private final caffeinecache localcache; // 一级缓存(本地)
private final rediscache remotecache; // 二级缓存(redis)
private final boolean cachenullvalues; // 是否缓存null值
private final boolean enablelocalcache; // 是否启用本地缓存
// 核心逻辑:查询缓存时先查本地,再查redis,最后查库
@override
public <t> t get(string key, class<t> resulttype, supplier<object> valueloader) {
// 1. 查本地缓存
if (enablelocalcache) {
t localvalue = localcache.get(key, resulttype);
if (resulttype.isinstance(localvalue) && !nullvalue.isnullvalue(localvalue)) {
return localvalue;
}
}
// 2. 查redis缓存
t remotevalue = remotecache.get(key, resulttype);
if (resulttype.isinstance(remotevalue) && !nullvalue.isnullvalue(remotevalue)) {
// 同步到本地缓存
if (enablelocalcache) {
localcache.put(key, remotevalue);
}
return remotevalue;
}
// 3. 缓存未命中,执行加载逻辑(查库)
object value = valueloader.get();
// 4. 缓存结果(支持null值)
put(key, value);
return resulttype.cast(fromstorevalue(value));
}
// 其他方法:put/evict/clear 均同步操作本地和redis缓存
}3. 缓存管理器(cachemanager)
负责缓存实例的创建和管理,采用懒加载模式:
public interface cachemanager {
// 获取缓存(不存在返回null)
cache getcache(string name);
// 获取缓存(不存在则创建)
cache getcache(string name, multicacheconfig config);
}
// 抽象实现类
public abstract class abstractcachemanager implements cachemanager {
// 缓存容器(concurrenthashmap保证线程安全)
private final concurrentmap<string, cache> cachemap = new concurrenthashmap<>(16);
@override
public cache getcache(string name, multicacheconfig config) {
// 双重检查锁:避免重复创建缓存
cache cache = cachemap.get(name);
if (cache != null) {
return cache;
}
synchronized (cachemap) {
cache = cachemap.get(name);
if (cache == null) {
// 创建缓存实例(子类实现具体缓存类型)
cache = createcache(name, config);
cachemap.put(name, cache);
}
return cache;
}
}
// 抽象方法:由子类实现缓存创建逻辑
protected abstract cache createcache(string name, multicacheconfig config);
}4. 分布式锁组件(redisson)
解决并发场景下的缓存问题,核心 api:
public interface distributedlocker {
// 获取单个锁
boolean lock(string lockkey, long leasetime, long waittime, timeunit unit);
// 获取联锁(多个key)
boolean multilock(collection<string> lockkeys, long leasetime, long waittime, timeunit unit);
// 释放锁
void unlock(string lockkey);
// 释放联锁
void multiunlock(collection<string> lockkeys);
}
// 工具类封装(静态调用)
public class distributedlockutils {
private static distributedlocker locker;
// 获取锁并执行逻辑
public static <t> t executewithlock(string lockkey, long leasetime, long waittime, timeunit unit, supplier<t> supplier) {
if (locker.lock(lockkey, leasetime, waittime, unit)) {
try {
return supplier.get();
} finally {
locker.unlock(lockkey);
}
}
throw new runtimeexception("获取锁失败");
}
}5. 工具类
5.1 json 序列化工具(jsonutils)
public class jsonutils {
private static final objectmapper object_mapper = new objectmapper()
.configure(deserializationfeature.fail_on_unknown_properties, false);
// 对象转json字符串
public static string tojson(object obj) {
try {
return object_mapper.writevalueasstring(obj);
} catch (jsonprocessingexception e) {
throw new runtimeexception("json序列化失败", e);
}
}
// json字符串转对象
public static <t> t fromjson(string json, class<t> clazz) {
try {
return object_mapper.readvalue(json, clazz);
} catch (jsonprocessingexception e) {
throw new runtimeexception("json反序列化失败", e);
}
}
}5.2 spel 表达式解析工具(cacheexpressionevaluator)
public class cacheexpressionevaluator extends cachedexpressionevaluator {
private final map<expressionkey, expression> keycache = new concurrenthashmap<>(64);
// 创建表达式上下文(包含方法、参数、目标对象等)
public evaluationcontext createcontext(method method, object[] args, object target) {
cacheexpressionrootobject root = new cacheexpressionrootobject(method, args, target);
return new methodbasedevaluationcontext(root, method, args, getparameternamediscoverer());
}
// 解析spel表达式
public object evaluatekey(string keyexpr, method method, object[] args, object target) {
if (stringutils.isempty(keyexpr)) {
return "";
}
evaluationcontext context = createcontext(method, args, target);
expressionkey key = new expressionkey(keyexpr, method);
return keycache.computeifabsent(key, k -> getexpressionparser().parseexpression(keyexpr))
.getvalue(context);
}
}五、进阶配置与最佳实践
1. 缓存策略配置
1.1 过期模式选择
// 过期模式枚举
public enum expiremode {
write, // 写入后开始计时(适合写少读多场景)
access // 访问后刷新过期时间(适合热点数据)
}
// 配置示例:热点数据使用access模式
multicacheconfig hotdataconfig = new multicacheconfig();
hotdataconfig.setexpiremode(expiremode.access.name());
hotdataconfig.setttl(10); // 10分钟
hotdataconfig.setenablefirstcache(true);1.2 防止缓存雪崩
// 配置过期时间随机偏移(避免大量key同时过期)
public class multicacheconfig {
// 最大偏移比例(10%)
private static final double max_offset_ratio = 0.1;
// 获取带随机偏移的过期时间(秒)
public long getrandomttl() {
long basettl = getttltoseconds();
double offset = basettl * max_offset_ratio;
// 随机增减偏移量
return (long) (basettl + (math.random() * 2 * offset - offset));
}
}2. 性能调优
2.1 本地缓存(caffeine)调优
// 高性能配置:基于访问频率和过期时间 caffeinecacheconfig config = new caffeinecacheconfig(); config.setinitialcapacity(100); // 初始容量 config.setmaximumsize(5000); // 最大容量(避免内存溢出) config.setexpiremode(expiremode.access); // 访问过期 config.setttl(5); // 5分钟过期
2.2 redis 缓存调优
spring:
redis:
lettuce:
pool:
max-active: 16 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 4 # 最小空闲连接
timeout: 2000ms # 超时时间(避免长时间阻塞)3. 常见问题解决方案
3.1 缓存穿透(查询不存在的数据)
// 方案:缓存null值 + 分布式锁
public user getuserbyid(long userid) {
string cachekey = "user:" + userid;
cache cache = cachemanager.getcache("usercache", config);
// 1. 查缓存(包括null值)
user user = cache.get(cachekey, user.class);
if (user != null) {
return user;
}
// 2. 分布式锁:防止并发查询不存在的数据
return distributedlockutils.executewithlock(
"lock:user:" + userid, 5, 3, timeunit.seconds,
() -> {
// 3. 再次查缓存(避免锁等待期间已缓存)
user cacheduser = cache.get(cachekey, user.class);
if (cacheduser != null) {
return cacheduser;
}
// 4. 查库(不存在则返回null)
user dbuser = userdao.selectbyid(userid);
// 5. 缓存结果(包括null值)
cache.put(cachekey, dbuser);
return dbuser;
}
);
}3.2 缓存击穿(热点key过期)
// 方案:缓存自动续期 + 分布式锁
public product gethotproduct(long productid) {
string cachekey = "hot:product:" + productid;
cache cache = cachemanager.getcache("hotproductcache", config);
return distributedlockutils.executewithlock(
cachekey + ":lock", -1, 3, timeunit.seconds, // leasetime=-1:自动续期
() -> {
product product = cache.get(cachekey, product.class);
if (product != null) {
// 访问后刷新过期时间(access模式自动实现)
return product;
}
// 查库并缓存
product dbproduct = productdao.selectbyid(productid);
cache.put(cachekey, dbproduct);
return dbproduct;
}
);
}3.3 缓存一致性(更新/删除后同步缓存)
// 方案:更新数据库后同步更新缓存,删除数据库后删除缓存
@transactional
public void updateproduct(product product) {
// 1. 更新数据库(事务内)
productdao.updatebyid(product);
// 2. 更新缓存(事务提交后执行,避免事务回滚导致缓存脏数据)
transactionsynchronizationmanager.registersynchronization(
new transactionsynchronizationadapter() {
@override
public void aftercommit() {
cache cache = cachemanager.getcache("productcache", config);
cache.put("product:" + product.getid(), product);
}
}
);
}六、完整可复用核心代码
1. 缓存配置类
import org.springframework.boot.autoconfigure.condition.conditionalonbean;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.beans.factory.annotation.value;
@configuration
@conditionalonbean(redistemplate.class)
public class cacheautoconfiguration {
@value("${spring.application.name}")
private string appname;
// 注册缓存管理器
@bean
public cachemanager cachemanager(redistemplate<string, string> redistemplate) {
return new defaultcachemanager(appname, new rediscachetemplate(redistemplate));
}
// 注册分布式锁
@bean
public distributedlocker distributedlocker(redissonclient redissonclient) {
return new redissondistributedlocker(redissonclient);
}
}2. 多级缓存配置模型
import java.util.concurrent.timeunit;
public class multicacheconfig {
// 缓存过期时间(默认10分钟)
private long ttl = 10;
// 时间单位(默认分钟)
private timeunit timeunit = timeunit.minutes;
// 过期时间比例(用于动态调整,1-100)
private int ttlproportion = 10;
// 是否缓存null值(默认false)
private boolean cachenullvalues = false;
// 是否启用本地缓存(默认false)
private boolean enablelocalcache = false;
// 本地缓存初始容量(默认10)
private int initialcapacity = 10;
// 本地缓存最大容量(默认3000)
private long maximumsize = 3000;
// 过期模式(默认write)
private string expiremode = expiremode.write.name();
// getter/setter 省略
// 转换为秒级过期时间
public long getttltoseconds() {
return timeunit.toseconds(ttl);
}
// 带比例的过期时间(最小3秒)
public long getttlwithproportion() {
long base = getttltoseconds();
long proportioned = (base * ttlproportion) / 100;
return math.max(proportioned, 3);
}
}
// 过期模式枚举
enum expiremode {
write, access
}3. 空值标识类
import java.io.serializable;
public class nullvalue implements serializable {
private static final long serialversionuid = 1l;
public static final nullvalue instance = new nullvalue();
private nullvalue() {}
// 反序列化时返回单例
private object readresolve() {
return instance;
}
@override
public boolean equals(object obj) {
return obj == this || obj == null;
}
@override
public int hashcode() {
return nullvalue.class.hashcode();
}
// 判断是否为空值
public static boolean isnull(object value) {
return value == null || value == instance;
}
}4. redis 缓存模板
import org.springframework.data.redis.core.stringredistemplate;
import java.util.concurrent.timeunit;
public class rediscachetemplate {
private final stringredistemplate redistemplate;
public rediscachetemplate(stringredistemplate redistemplate) {
this.redistemplate = redistemplate;
}
// 设置缓存(带过期时间)
public void set(string key, string value, long ttl, timeunit unit) {
redistemplate.opsforvalue().set(key, value, ttl, unit);
}
// 获取缓存
public string get(string key) {
return redistemplate.opsforvalue().get(key);
}
// 删除缓存
public boolean delete(string key) {
return redistemplate.delete(key);
}
// 批量删除
public long delete(collection<string> keys) {
return redistemplate.delete(keys);
}
// 判断key是否存在
public boolean haskey(string key) {
return redistemplate.haskey(key);
}
// 设置过期时间
public boolean expire(string key, long ttl, timeunit unit) {
return redistemplate.expire(key, ttl, unit);
}
}到此这篇关于redis caffeine多级缓存框架的实现的文章就介绍到这了,更多相关redis caffeine多级缓存内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论