redis缓存原理与实现_基于Redis与切面实现方法通用缓存

简介

随着系统QPS逐渐提高,在一些读取频繁变更较少数据场景下,适当添加缓存不仅能提升用户访问速度还可以减轻系统压力。

使用缓存主要有两个用途:高性能、高并发

  • 高性能:

    • 查询mysql耗时需要300ms,存到缓存redis,每次查询仅需要几毫秒,性能瞬间提升百倍。

  • 高并发

    • mysql 单机支撑2K QPS就容易报警了,使用缓存的话,单机支撑的并发量轻松1s几万~十几万。

    • 缓存位于内存,内存对高并发的良好支持。

目的

  • 通过统一缓存方式,规范缓存实现。

  • 缓存命中走缓存。不命中加锁查询防止大量穿透引起雪崩;

  • 通过统一注解方式,也减少了大量的冗余代码 。

  • 减少重复,干掉重复。

本文主要讲解,一款基于自定义注解拦截+切面+redis的通用方法缓存方式 ;

支持场景

  • 业务自定义的查询方法均可使用;

  • 包括无参数的方法,有参数的方法;

  • 可以根据不同的参数值做缓存 ;

技术位面

  • Redis

  • Redisson

  • fastjson

  • 切面(Aspect)

  • 序列化 (Serializable)

  • SpringBoot (示例工程)

功能示例图:

9b4d746d993c3fc85020348805644fb2.png

核心实现

实现原理简介

  • 自定义注解 @CacheData ;

  • 通过切面 CacheAspect 拦截添加自定义注解的方法 ;

  • 通过参数生成Redis缓存key

    • 解析方法的参数并按照参数顺序排序

    • 将转换好的参数转换为Json字符串

      • 本来想用toString的考虑到继承等没有重写方法就改用了Json

    • 将参数进行MD5,一方面安全性,一方面保证Redis key不要过长,节约空间 ;

代码实现

1.自定义注解

/** * 自定义缓存注解 * * @author 程序员小强 * @see CacheAspect */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface CacheData {
        /**     * 自定义缓存key前缀     * 默认:方法名     */    String keyPrefix() default "";    /**     * 缓存过期时间     * 单位默认:秒,不设置默认10分钟     */    int expireTime() default 600;    /**     * 缓存过期-随机数     * 单位默认:秒,默认0分钟     * 注:设置后实际过期时间,     * 会在expireTime基础上继续累积(0~randomExpire)之间的秒数,防止缓存大量失效大面积穿透,造成雪崩     */    int randomExpire() default 0;    /**     * 是否存储为null 的返回     * 注:防止缓存穿透,默认true,建议查询为空时,也进行缓存     */    boolean storageNullFlag() default true;}

2.拦截切面实现

/** * 统一缓存自定义注解拦截实现 * * @author 程序员小强 */@Aspect@Componentpublic class CacheAspect {
        private static final Logger log = LoggerFactory.getLogger(CacheAspect.class);    private static final String EMPTY = "";    private static final String POINT = ".";    private static final String CACHE_KEY_PREFIX = "cache.aspect:";    private static final String LOCK_KEY_PREFIX = "lock.";    @Resource    private RedisCache redisCache;    /**     * redisson client对象     */    @Resource    private RedissonClient redisson;    /**     * 匹配所有使用以下注解的方法     * 注意:缓存是基于类+方法+参数内容做的缓存key,重载方法可能会出现问题     * 禁止在同一个类,方法名相同的两个方法使用     *     * @see CacheData     */    @Pointcut("@annotation(com.example.cache.annotation.CacheData)")    public void pointCut() {
        }    /**     * 拦截添加缓存注解的方法     *     * @param pjpParam     * @return     * @throws Throwable     * @see CacheData     */    @Around("pointCut()&&@annotation(cacheData)")    public Object logAround(ProceedingJoinPoint pjpParam, CacheData cacheData) throws Throwable {
            //注解为空        if (null == cacheData) {
                return pjpParam.proceed(pjpParam.getArgs());        }        //仅用于方法        if (null == pjpParam.getSignature() || !(pjpParam.getSignature() instanceof MethodSignature)) {
                return pjpParam.proceed(pjpParam.getArgs());        }        //方法实例        Method method = ((MethodSignature) pjpParam.getSignature()).getMethod();        //方法类名        String className = pjpParam.getSignature().getDeclaringTypeName();        //方法名        String methodName = method.getName();        log.debug("[ CacheAspect ] >> notReadCacheFlag:{} , refreshCacheFlag :{} , method:{} ",                CacheContext.notReadCacheFlag.get(), CacheContext.refreshCacheFlag.get(), className + methodNam
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值