Redis+Caffeine多级缓存框架实现

作者:技术拾光

时间:2026-06-07

浏览:569

基于Spring生态,整合Caffeine本地缓存与Redis分布式缓存的多级框架,通过本地高速响应与分布式一致性协同,解决缓存穿透、击穿、雪崩等问题。支持灵活配置、SpEL动态Key、Redisson分布式锁,提供编程式和注解式API,适用于高并发场景。

文章目录

一、框架核心概述(是什么)

该框架的核心定位为一套本地缓存(Caffeine)+ 分布式缓存(Redis)的多级缓存框架,基于 Spring 生态打造,目标明确:既要缓存访问的高性能,又要分布式环境下的数据一致性。

Redis+Caffeine多级缓存框架实现

核心特性

多级缓存框架具备以下关键特性:

  • 多级缓存架构:一级本地 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)



    org.springframework.boot
    spring-boot-starter-web


    org.springframework.boot
    spring-boot-starter-data-redis



    com.github.ben-manes.caffeine
    caffeine
    3.1.8



    org.redisson
    redisson-spring-boot-starter
    3.25.0



    com.fasterxml.jackson.core
    jackson-databind

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: 2

3. 基础使用示例

3.1 编程式使用(直接操作缓存 API)

编程式方式在 Service 中注入 CacheManager 操作缓存:

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class UserService {

    @Resource
    private CacheManager cacheManager;

    private final MultiCacheConfig userCacheConfig = new MultiCacheConfig(
        30, TimeUnit.MINUTES, 20, true, true
    );

    public User getUserById(Long userId) {
        Cache cache = cacheManager.getCache("userCache", userCacheConfig);
        String cacheKey = "user:" + userId;
        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();
        userDao.updateById(user);
        cache.put(cacheKey, user);
    }

    public void deleteUser(Long userId) {
        Cache cache = cacheManager.getCache("userCache", userCacheConfig);
        String cacheKey = "user:" + userId;
        userDao.deleteById(userId);
        cache.evict(cacheKey);
    }
}

3.2 注解式使用(基于 AOP 切面)

若偏好注解风格,可自定义 @MyCacheable:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCacheable {
    String cacheName();
    String key() default "";
    MultiCacheConfig config() default @MultiCacheConfig();
}

@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();
        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);
            }
        });
    }
}

@Service
public class ProductService {
    @MyCacheable(
        cacheName = "productCache",
        key = "#root.methodName + ':' + #p0",
        config = @MultiCacheConfig(ttl = 60, timeUnit = TimeUnit.MINUTES, cacheNullValues = true)
    )
    public Product getProductById(Long productId) {
        return productDao.selectById(productId);
    }
}

四、核心组件详解

1. 缓存核心接口(Cache)

该接口是所有缓存操作的顶层契约,定义标准 API:

public interface Cache {
     T get(String key, Class resultType);
     T get(String key, Class resultType, Supplier valueLoader);
    void put(String key, Object value);
    boolean putIfAbsent(String key, Object value);
    void evict(String key);
    void multiEvict(Collection keys);
    void clear();
}

2. 多级缓存实现(MultiLevelCache)

该类将本地 Caffeine 和远程 Redis 整合,核心思路:先查本地,再查 Redis,最后查库。代码如下:

public class MultiLevelCache implements Cache {
    private final String cacheName;
    private final CaffeineCache localCache;
    private final RedisCache remoteCache;
    private final boolean cacheNullValues;
    private final boolean enableLocalCache;

    @Override
    public  T get(String key, Class resultType, Supplier 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();
        put(key, value);
        return resultType.cast(fromStoreValue(value));
    }
    // put/evict/clear 同步操作两层
}

3. 缓存管理器(CacheManager)

负责缓存的创建和托管,采用懒加载 + 双重检查锁,避免重复创建:

public interface CacheManager {
    Cache getCache(String name);
    Cache getCache(String name, MultiCacheConfig config);
}

public abstract class AbstractCacheManager implements CacheManager {
    private final ConcurrentMap 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)

用于解决并发场景,提供单个锁和联锁操作:

public interface DistributedLocker {
    boolean lock(String lockKey, long leaseTime, long waitTime, TimeUnit unit);
    boolean multiLock(Collection lockKeys, long leaseTime, long waitTime, TimeUnit unit);
    void unlock(String lockKey);
    void multiUnlock(Collection lockKeys);
}

public class DistributedLockUtils {
    private static DistributedLocker locker;
    public static  T executeWithLock(String lockKey, long leaseTime, long waitTime, TimeUnit unit, Supplier 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);
    public static String toJson(Object obj) { /* 略 */ }
    public static  T fromJson(String json, Class clazz) { /* 略 */ }
}

5.2 SpEL 表达式解析工具(CacheExpressionEvaluator)

public class CacheExpressionEvaluator extends CachedExpressionEvaluator {
    private final Map keyCache = new ConcurrentHashMap<>(64);
    public EvaluationContext createContext(Method method, Object[] args, Object target) { /* 略 */ }
    public Object evaluateKey(String keyExpr, Method method, Object[] args, Object target) { /* 略 */ }
}

五、进阶配置与最佳实践

实际落地时需特别注意以下几个关键点。以下给出通用配置和代码模板。

1. 缓存策略配置

1.1 过期模式选择

public enum ExpireMode {
    WRITE,  // 写入后开始计时
    ACCESS  // 访问后刷新过期时间
}

MultiCacheConfig hotDataConfig = new MultiCacheConfig();
hotDataConfig.setExpireMode(ExpireMode.ACCESS.name());
hotDataConfig.setTtl(10);
hotDataConfig.setEnableFirstCache(true);

1.2 防止缓存雪崩

public class MultiCacheConfig {
    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);

2.2 Redis 缓存调优

spring:
  redis:
    lettuce:
      pool:
        max-active: 16
        max-idle: 8
        min-idle: 4
    timeout: 2000ms

3. 常见问题解决方案

3.1 缓存穿透(查询不存在的数据)

public User getUserById(Long userId) {
    String cacheKey = "user:" + userId;
    Cache cache = cacheManager.getCache("userCache", config);
    User user = cache.get(cacheKey, User.class);
    if (user != null) {
        return user;
    }
    return DistributedLockUtils.executeWithLock(
        "lock:user:" + userId, 5, 3, TimeUnit.SECONDS,
        () -> {
            User cachedUser = cache.get(cacheKey, User.class);
            if (cachedUser != null) {
                return cachedUser;
            }
            User dbUser = userDao.selectById(userId);
            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,
        () -> {
            Product product = cache.get(cacheKey, Product.class);
            if (product != null) {
                return product;
            }
            Product dbProduct = productDao.selectById(productId);
            cache.put(cacheKey, dbProduct);
            return dbProduct;
        }
    );
}

3.3 缓存一致性(更新/删除后同步缓存)

@Transactional
public void updateProduct(Product product) {
    productDao.updateById(product);
    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 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 {
    private long ttl = 10;
    private TimeUnit timeUnit = TimeUnit.MINUTES;
    private int ttlProportion = 10;
    private boolean cacheNullValues = false;
    private boolean enableLocalCache = false;
    private int initialCapacity = 10;
    private long maximumSize = 3000;
    private String expireMode = ExpireMode.WRITE.name();

    // getter/setter 省略
    public long getTtlToSeconds() {
        return timeUnit.toSeconds(ttl);
    }
    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 keys) { return redisTemplate.delete(keys); }
    public boolean hasKey(String key) { return redisTemplate.hasKey(key); }
    public boolean expire(String key, long ttl, TimeUnit unit) { return redisTemplate.expire(key, ttl, unit); }
}

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章