Springboot2.x AOP 实现 @Cacheable自定义TTL和自动刷新 防止缓存雪崩

CacheManager个性化设置

  • 可以根据需要,让某些key,默认TTL与基础设置不同,直接看代码
    这不是重点,只是可以提前DIY一些东西
	@Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 配置序列化
        // 统一默认配置,TTL为60秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(60));

        // 针对不同key可以个性化设置
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("account");
        cacheNames.add("post");

        // 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("account", config.entryTtl(Duration.ofSeconds(30)));
        configMap.put("post", config);

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .initialCacheNames(cacheNames)
                .withInitialCacheConfigurations(configMap)
                .build();
    }

自定义每个缓存的TTL和自动刷新

上面的设置虽然可以提前设定每个cacheName的ttl,但是不够细,虽然Cacheable的sync可以防止缓存击穿,但是无法防止缓存雪崩。
我们可以使用AOP来完成自动刷新和自定义TTL。
代码写的比较糙,只是个例子,可以自己修改。

自己写TTL注解

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface CacheTTL {
    @AliasFor("ttl")
    long value() default 60;

    @AliasFor("value")
    long ttl() default 60;

    long preExpireRefresh() default 10;
}

AOP实现

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

//这里必须要比Cacheable的Aspect优先级要高
@Order(value=1)
@Aspect
@Component
public class CustomCacheAspect {

    private final RedisTemplate<String,Object> redisTemplate;
    private final SimpleKeyGenerator keyGenerator =  new SimpleKeyGenerator();
    private final AtomicInteger asyncLock = new AtomicInteger(0);

    public CustomCacheAspect(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @After(value="execution(* *..*.*(..))")
    public void after(JoinPoint point)
    {
        Object target = point.getTarget();

        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();

        try {
            if (method.isAnnotationPresent(CacheTTL.class) && method.isAnnotationPresent(Cacheable.class)) {
                CacheTTL ttlData = AnnotationUtils.getAnnotation(method, CacheTTL.class);
                Cacheable cacheAbleData = AnnotationUtils.getAnnotation(method, Cacheable.class);

                long ttl = ttlData.ttl();
                long preExpireRefresh = ttlData.preExpireRefresh();

                String[] cacheNames = cacheAbleData.cacheNames();
                //默认的keyGenerator生成,如果自定义了自己改一下
                Object key = keyGenerator.generate(target, method, point.getArgs());
                
                updateExpire(cacheNames,key,preExpireRefresh,ttl);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void updateExpire(String[] cacheNames,Object key,long preExpireRefresh,long ttl){
        if (asyncLock.compareAndSet(0,1)){
            Arrays.stream(cacheNames).parallel().forEach(cacheName->{
                cacheName = cacheName + "::" + key;
                long expire = redisTemplate.getExpire(cacheName,TimeUnit.SECONDS);
                if (expire>0 && expire<=preExpireRefresh || expire > ttl || expire == -1){
                    redisTemplate.expire(cacheName, ttl, TimeUnit.SECONDS);
                }
            });
            asyncLock.set(0);
        }

    }

}

使用

    @Cacheable(cacheNames = "account",key="#id",sync = true)
    @CacheTTL(ttl = 60,preExpireRefresh = 10)
    public Account findAccountByID(int id) {
        return accountDao.findAccountByID(id);
    }
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没事干写博客玩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值