使用redisson+aop自定义注解解决缓存查询和对应问题

1.问题分析

1.考虑很多线程去查数据库,对数据库冲击较大,效率也比较低。
2.所以需要引入缓存。
3.考虑缓存击穿,缓存失败。
4.考虑redis集群宕机。
5.考虑很多方法都要使用缓存,都要解决这些问题。
6.锁需要支持集群
7.综合以上几点,技术选型:
redisson+aop+redisTemplate

2.思路梳理

1.首先定义一个注解,标识这个注解的方法会被我们自定义的切面获取并解析,实现查询缓存。
2.可以在注解里面定义一个前缀属性,每个方法查询的数据肯定不一样,所以为了防止key重复,
每个方法的前缀key都在标识注解的时候指定。
3.定义切面类,定义环绕方法,@Around("@annotation(com.atguigu.gmall.common.cache.MySelfCache)")
4.首先利用ProceedingJoinPoint 简称point 来获取方法的参数 和方法上的这个注解。
将注解里指定的参数和方法参数拼接成key
5.考虑一个大情况 ,redis集群宕机,此时拿着key去缓存查就会抛出异常,不会继续去查数据库。
解决办法:用try-catch包裹接下来所有代码,只在方法最后return 一个null,兜底。
6.先去拿着key查缓存,缓存有的话没直接返回。
7.缓存没有的话,就得去数据库查,但是考虑到需要防止缓存穿透,所以就需要用redisson上锁。
8.上锁之后,还需要判断,假如多线程,只有一个线程获取到了锁,所以,其他没获取到锁的线程,只需要自旋即可。
9.获取到锁的线程,直接去数据库查。此时还需要考虑2个情况。
10.数据库没有这个数据,考虑到防止缓存击穿,利用反射,获取方法的返回值类型,造一个空对象放到redis,并直接返回,
注意:因为数据库暂时并没有这个数据,所以我们给这个数据需要设置一个时间不太长的过期时间。
11.数据库有这个数据,直接将数据加入redis并返回。

3.代码实现

/**
 * @author yinhuidong
 * @createTime 2020-08-02-7:41
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface MySelfCache {
    String prefix() default "cache";
}
/**
 * @author yinhuidong
 * @createTime 2020-08-02-7:42
 */
@Component
@Aspect
public class MySelfCacheAspect {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(com.atguigu.gmall.common.cache.MySelfCache)")
    public Object aroundMethod(ProceedingJoinPoint point) {
        //声明返回结果
        Object result = null;
        //获取中间变量
        MethodSignature signature = (MethodSignature) point.getSignature();
        //获取方法上的注解
        MySelfCache mySelfCache = signature.getMethod().getAnnotation(MySelfCache.class);
        //获取方法参数
        Object[] args = point.getArgs();
        //组装key
        String key = mySelfCache.prefix() + Arrays.asList(args);
        //假如redis集群宕机,程序并不知道,因此还回去缓存查数据,此时就会抛出异常,所以需要try-catch,如果发生异常,直接return
        try {
            //去缓存查
            result = searchCache(key);
            //如果缓存没数据
            if (StringUtils.isEmpty(result)) {
                //防止缓存穿透,加锁
                RLock lock = redissonClient.getLock(key + ":cache");
                //配置看门狗 最大等待时间 等待时间  时间单位
                boolean flag = lock.tryLock(10, 1, TimeUnit.SECONDS);
                //如果加锁成功
                if (flag) {
                    try {
                        //从数据库查
                        result = point.proceed(args);
                        //如果数据库没有这个ID对应的数据,防止缓存击穿
                        if (StringUtils.isEmpty(result)) {
                            //反射获取方法的返回值类型,创建空对象,加入redis缓存
                            Object o = signature.getReturnType().newInstance();
                            redisTemplate.opsForValue().set(key, o, 10, TimeUnit.MINUTES);
                            return o;
                        }
                        //如果有数据,加入缓存,直接return
                        redisTemplate.opsForValue().set(key, result, 24 * 7, TimeUnit.HOURS);
                    } catch (Throwable throwable) {
                        System.out.println("server-product" + "**************" + signature.getMethod() + "方法业务逻辑出现异常");
                    } finally {
                        //解锁
                        lock.unlock();
                    }
                } else {//加锁失败,考虑自旋
                    TimeUnit.SECONDS.sleep(1);
                    result = searchCache(key);
                    return result;
                }
            } else {//缓存有数据
                return result;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return result;
    }

    public Object searchCache(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45596022/article/details/108232053