基于AOP注解+Redisson实现Cache-Aside缓存模式实战

(2) 缓存更新注解一、场景需求

在高并发系统中,缓存是提升性能的关键组件。而Cache-Aside模式作为最常用的缓存策略之一,要求开发者手动管理缓存与数据库的交互。本文将结合自定义注解与Redisson客户端,实现声明式的缓存管理方案。

二、方案亮点

🚀 零侵入性:通过注解实现缓存逻辑

🔒 完整防护:解决缓存穿透/击穿/雪崩问题

⚡ 双删策略:保障数据库与缓存一致性

🛠️ 逻辑删除:支持数据恢复与审计需求

三、核心实现

  1. 环境准备

Maven依赖

java 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.23.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 定义注解

(1) 缓存查询注解

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheableData {
    String key();              // 缓存键(支持SpEL)
    int expire() default 3600; // 过期时间(秒)
    int nullExpire() default 300; // 空值缓存时间
}

(2) 缓存更新注解

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheUpdateData {
    String key();              // 需删除的缓存键
    boolean logicalDelete() default false; 
}

(3) 分布式锁注解

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheLock {
    String lockKey();         // 锁的键(SpEL)
    int waitTime() default 3; // 获取锁等待时间(秒)
    int leaseTime() default 10; // 锁持有时间
}
  1. AOP切面实现
java 复制代码
@Aspect
@Component
public class CacheAspect {
    @Autowired
    private RedissonClient redisson;
    private static final String NULL_PLACEHOLDER = "##NULL##";

    // 读操作切面
    @Around("@annotation(cacheable)")
    public Object aroundCacheable(ProceedingJoinPoint joinPoint, 
                                 CacheableData cacheable) throws Throwable {
        // 解析SpEL生成缓存键
        String key = parseKey(cacheable.key(), joinPoint);
        
        // 1. 检查缓存
        RBucket<Object> bucket = redisson.getBucket(key);
        Object cachedValue = bucket.get();
        
        if (cachedValue != null) {
            if (NULL_PLACEHOLDER.equals(cachedValue)) return null;
            if (isLogicalDeleted(cachedValue)) return null;
            return cachedValue;
        }
        
        // 2. 执行原方法(查询数据库)
        Object dbResult = joinPoint.proceed();
        
        // 3. 回写缓存
        if (dbResult == null) {
            bucket.set(NULL_PLACEHOLDER, cacheable.nullExpire(), TimeUnit.SECONDS);
        } else {
            bucket.set(dbResult, cacheable.expire(), TimeUnit.SECONDS);
        }
        return dbResult;
    }

    // 更新操作切面(带双删)
    @Around("@annotation(cacheUpdate)")
    public Object aroundUpdate(ProceedingJoinPoint joinPoint, 
                              CacheUpdateData cacheUpdate) throws Throwable {
        String key = parseKey(cacheUpdate.key(), joinPoint);
        
        // 第一次删除
        redisson.getBucket(key).delete();
        
        // 执行数据库操作
        Object result = joinPoint.proceed();
        
        // 延迟双删(1秒后二次删除)
        redisson.getDelayedQueue(redisson.getQueue("cache:delete:queue"))
               .offer(key, 1, TimeUnit.SECONDS);
        
        // 处理逻辑删除
        if (cacheUpdate.logicalDelete()) {
            markLogicalDelete(key);
        }
        return result;
    }
    
    // 其他辅助方法省略,完整代码见文末Github链接
}
  1. 业务层使用示例
java 复制代码
@Service
public class UserService {
    
    // 带防击穿的查询方法
    @CacheableData(key = "user:#userId", expire = 7200)
    @CacheLock(lockKey = "user_lock:#userId")
    public User getUserById(Long userId) {
        return userDao.findById(userId);
    }

    // 更新用户信息
    @CacheUpdateData(key = "user:#user.id")
    public void updateUser(User user) {
        userDao.update(user);
    }

    // 逻辑删除用户
    @CacheUpdateData(key = "user:#id", logicalDelete = true)
    public void deleteUser(Long id) {
        userDao.logicalDelete(id);
    }
}

四、方案总结

七:踩坑指南

  1. 序列化问题:推荐使用JSON序列化,避免Java序列化的版本兼容问题
  2. 锁超时设置:分布式锁的leaseTime应大于业务执行时间
  3. 内存泄漏:逻辑删除数据必须设置TTL
  4. SpEL解析:复杂表达式建议使用Spring的ExpressionParse
相关推荐
大猫子的技术日记8 小时前
[百题重刷]前缀和 + Hash 表:缓存思想, 消除重复计算
java·缓存·哈希算法
愤怒的山羊10 小时前
jetcache List 缓存, json 序列化 泛型解析成了 JsonObject 处理
缓存·json·list
树在风中摇曳10 小时前
带哨兵位的双向循环链表详解(含 C 代码)+ LeetCode138 深度解析 + 顺序表 vs 链表缓存机制对比(图解 CPU 层级)
c语言·链表·缓存
斯文~14 小时前
「玩透ESA」站点配置阿里云ESA全站加速+自定义规则缓存
阿里云·缓存·云计算·cdn·esa
S***t71414 小时前
Python装饰器实现缓存
缓存
天硕国产存储技术站14 小时前
3000次零失误验证,天硕工业级SSD筑牢国产SSD安全存储方案
缓存·固态硬盘·国产ssd
前端炒粉17 小时前
35.LRU 缓存
开发语言·javascript·数据结构·算法·缓存·js
努力发光的程序员1 天前
互联网大厂Java面试:从Spring Boot到微服务架构
spring boot·缓存·微服务·消息队列·rabbitmq·spring security·安全框架
zero13_小葵司1 天前
JavaScript性能优化系列(八)弱网环境体验优化 - 8.3 数据预加载与缓存:提前缓存关键数据
javascript·缓存·性能优化
CS_浮鱼1 天前
【Linux进阶】mmap实战:文件映射、进程通信与LRU缓存
linux·运维·c++·缓存