精华内容
下载资源
问答
  • spring redis 手动刷新功能 背景 当我们在使用redis作为后台数据库缓存时,通常会有一套比较完善的更新策略,比如:...提取出更新redis缓存的方法,注册在容器中,在需要的时候通过程序执行容器中的方法。 代码 ...

    spring redis 手动刷新缓存功能

    背景

    当我们在使用redis作为后台数据库缓存时,通常会有一套比较完善的更新策略,比如:按照某个固定时间过期。但是,在某些特定情况下(意料之外),后台数据已经更新,缓存中的数据需要手动同步。

    思路

    提取出刷新redis缓存的方法,注册在容器中。在需要的时候调用已注册的刷新方法,更新redis缓存。

    实现

    1. 注解

    被注解的方法为刷新Redis的方法:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface RefreshCache {
        String value() default "";
    }
    

    2. 容器

    存储所有刷新缓存的方法:

    @Data
    @Slf4j
    public class CacheInvoker {
    
        private static List<CacheInvoker> invokers = new ArrayList<>();
    
        private Object targetObj;
        private Method targetMethod;
    
        public CacheInvoker(Object targetObj, Method targetMethod) {
            this.targetObj = targetObj;
            this.targetMethod = targetMethod;
        }
    
        public static void addInvoker(CacheInvoker cacheInvoker) {
            invokers.add(cacheInvoker);
        }
    
        public static void addInvokers(List<CacheInvoker> otherInvokers) {
            invokers.addAll(otherInvokers);
        }
    
        public void doInvoke() {
            Method method = getTargetMethod();
            Object target = getTargetObj();
            log.info("执行方法:{}", method.toString());
            ReflectionUtils.makeAccessible(method);
            ReflectionUtils.invokeMethod(method, target);
        }
    
        public static void doInvokes() {
            log.info("共{}个,初始化方法", invokers.size());
            invokers.forEach(CacheInvoker::doInvoke);
        }
    }
    

    3. spring bean 后处理器

    将所有被RefreshCache注解的方法,存入CacheInvoker容器中

    @Component
    public class RefreshCacheBeanProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            Class<?> clazz = bean.getClass();
            Method[] methods = clazz.getDeclaredMethods();
            List<CacheInvoker> invokers = Stream.of(methods)
                    .filter(method -> method.isAnnotationPresent(RefreshCache.class))
                    .map(method -> {
                        CacheInvoker invoker = new CacheInvoker(bean, method);
                        invoker.doInvoke();
                        return invoker;
                    })
                    .collect(Collectors.toList());
            CacheInvoker.addInvokers(invokers);
            return bean;
        }
    }
    

    使用实例:

    @Slf4j
    @Service
    public class RedisService {
    
        @RefreshCache
        public void initCache() {
            log.info("初始化redis 缓存");
        }
    }
    

    说明

    1. @RefreshCache注解,只有在spring 管理的实例中才生效,既类上有@Component注解。

    代码仓库

    https://gitee.com/thanksm/redis_learn/tree/master/refresh

    展开全文
  • 一.Redis缓存雪崩 热点数据基本都会去做缓存,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,定时任务刷新就有一个问题:缓存雪崩。 概念 大量的key设置了相同的过期时间,导致缓存在同一时刻全部失效,...

    一.Redis缓存雪崩

    热点数据基本都会去做缓存,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,定时任务刷新就有一个问题:缓存雪崩

    概念

    大量的key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

    此时,大数据量的请求直接到达数据库,如果没有做熔断策略,其他访问该数据库的接口都无法正常返回,会造成业务中断且短期内无法恢复。

    缓存正常获取时:
    在这里插入图片描述
    缓存同时失效时:
    在这里插入图片描述

    解决方案

    缓存雪崩有三种解决方案:使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间。

    (1)使用锁或队列

    一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,伪代码如下:

    public object GetProductListNew() {
        int cacheTime = 30;
        String cacheKey = "product_list";
        String lockKey = cacheKey;
    
        String cacheValue = CacheHelper.get(cacheKey);
        if (cacheValue != null) {
            return cacheValue;
        } else {
            synchronized(lockKey) {
                cacheValue = CacheHelper.get(cacheKey);
                if (cacheValue != null) {
                    return cacheValue;
                } else {
                    //这里一般是sql查询数据
                    cacheValue = GetProductListFromDB(); 
                    CacheHelper.Add(cacheKey, cacheValue, cacheTime);
                }
            }
            return cacheValue;
        }
    }
    

    加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。

    假设在高并发下,缓存重建期间key是锁着的,那么1000个请求999个都在阻塞,会导致用户等待超时,用户体验很差。

    而且在分布式环境还存在并发问题,可能还要解决分布式锁的问题;因此,在真正的高并发场景下很少使用。

    (2)设置过期标志更新缓存

    第二种解决方案是:给每一个缓存数据增加相应的缓存标记,记录缓存数据是否失效,如果缓存标记失效,则更新数据缓存,伪代码如下:

    //伪代码
    public object GetProductListNew() {
        int cacheTime = 30;
        String cacheKey = "product_list";
        //缓存标记
        String cacheSign = cacheKey + "_sign";
        String sign = CacheHelper.Get(cacheSign);
        //获取缓存值
        String cacheValue = CacheHelper.Get(cacheKey);
        if (sign != null) {
       	    //未过期,直接返回
            return cacheValue; 
        } else {
            CacheHelper.Add(cacheSign, "1", cacheTime);
            ThreadPool.QueueUserWorkItem((arg) -> {
                //这里一般是sql查询数据
                cacheValue = GetProductListFromDB(); 
                //日期设缓存时间的2倍,用于脏读
                CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);                 
            });
            return cacheValue;
        }
    }
    

    解释说明

    1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;

    2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。

    这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。

    (3)为key设置不同的缓存失效时间

    可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。或者设置热点数据永远不过期,有更新操作就直接更新缓存,不设置过期时间。

    二.Redis缓存击穿

    概念

    一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

    解决方案

    (1)设置热点数据永远不过期;

    (2)加互斥锁;

    多个线程同时去查询数据库,可以在第一个查询数据的线程里用一个互斥锁来上锁,其他线程走到这一步拿不到锁就等着,等第一个线程查询结束了,然后加入缓存,后面的线程进来发现有缓存了就直接走缓存。

    /**
         * 互斥锁 针对缓存击穿方案
         * @param key
         * @return
         * @throws InterruptedException
         */
        String mutex(String key) throws InterruptedException {
            String value = redisTemplate.opsForValue().get(key).toString();
            if (value  == null) {
                // 设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
                if (redisTemplate.opsForValue().setIfAbsent(key+"_mutex","1",3,TimeUnit.MINUTES)) {
                    // 数据库获取值
                    value = db.get(key);
                    redisTemplate.opsForValue().set(key, value);
                    redisTemplate.delete(key+"_mutex");
                } else {
                    //其他线程休息50毫秒后重试
                    Thread.sleep(50);
                    redisTemplate.opsForValue().get(key);
                }
                return value;
            } else {
                return value;
            }
        }
    

    三.Redis缓存穿透

    概念

    用户不断发起请求,请求的数据在缓存和数据库中都没有,比如数据库的 id 都是1开始自增上去的,发起为id值为 -1的数据请求或id为特别大不存在的数据时,该请求会直接访问数据库,从而导致数据库压力过大,严重时甚至会击垮数据库。

    解决方案
    • 接口层增加校验,如id做基础校验,id<=0的直接拦截;

    • 从缓存和数据库中都没有取到的数据,可以将key-value对写为key-null,设置较短的缓存有效时间,如30秒(设置太长会导致正常情况也没法使用),就可以防止攻击用户反复用同一个id进行暴力攻击的情况发生。

    参考博客:
    1.缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级、缓存热点 key
    2.缓存穿透,缓存击穿,缓存雪崩解决方案
    3.缓存穿透、缓存击穿、缓存雪崩概念及解决方案
    4.Redis-缓存雪崩、击穿、穿透

    展开全文
  • redis缓存穿透之setnx使用场景

    千次阅读 2018-06-30 11:13:34
    很多公司在业务场景中会增加缓存策略,而缓存用的最多的也就是redis了。 今天我们来说一下缓存穿透,我们缓存一般是有时效性的,一定的生命周期过去之后就会消失,一般的系统不会设置永久性的存储,这时候就会遇到...

        随着业务的增长,请求并发的增大。很多公司在业务场景中会增加缓存策略,而缓存用的最多的也就是redis了。

        今天我们来说一下缓存穿透,我们缓存一般是有时效性的,一定的生命周期过去之后就会消失,一般的系统不会设置永久性的存储,这时候就会遇到一个问题,要么就是主动刷新缓存,要么就是程序被动刷新缓存。

        事实证明很多程序当中很少主动刷新因为你要去写脚本,定时去刷新数据,这样的话代价比较大。所以较多的就选择了程序被动刷新缓存,就是在缓存失效之后读库在写入缓存。这里就会导致一个问题那就是:当缓存失效的那一刻大量的请求过来就会直接打到数据库上,如果并发量大的话读库操作肯定会有影响,连接超时,连接数过大这样的情况也会随时遇到。

        针对这种情况redis有一个进程锁也就是我们今天说的setnx

        下面直接贴代码:

        $ret = $this->redis->get('k' . $abbr);
        if (empty($ret)) {
        $ret1 = $this->redis->setnx('k'.$abbr,$ret);
            if($ret1){
            $sql = "select * from xxx where a=xx;";
            $query = $this->db->query($sql);
            $ret = $query->result_array();
            $this->redis->set('b' , json_encode($ret) , 6400);
            $this->redis->set('bakk', json_encode($ret) , 10000);
            }    else{
                $ret = $this->redis->get('bakk' . $abbr);
            }
    
        }

        这段程序的逻辑就是先用一个进程去请求数据如果没有了用setnx锁住这个键只有一个进程去读库操作然后重写两个键值,一个主要的查询键值,一个备份的键值当然缓存时间会长点。这样不管多少请求过来只有一个进程去做读库操作,其他的还是读redis备份数据。用空间来换取时间和效率还是比较划算的。

        这就是针对缓存穿透做的一套操作,下面上一下这个流程图:


    展开全文
  • 缓存之道Redis

    2020-11-20 23:49:14
    五、Redis缓存未命中问题 1、缓存穿透 2、缓存击穿 3、缓存雪崩 4、总结 六、缓存更新策略 1、查询时更新 2、修改时更新 3、定时任务更新 4、人工刷新缓存 一、Redis的主要使用场景 作为存储使用

    目录

    一、Redis的主要使用场景

    二、Redis和Memcached的主要区别

    三、Redis的5种数据结构类型

    1、Redis的5种数据类型分别是:String、list、hash、set、zset

    2、String和Hash的取舍

    四、Redis实现分布式锁

    五、Redis缓存未命中问题

    1、缓存穿透

    2、缓存击穿

    3、缓存雪崩

    4、总结

    六、缓存更新策略

    1、查询时更新

    2、修改时更新

    3、定时任务更新

    4、人工刷新缓存


    一、Redis的主要使用场景

    1. 作为存储使用,数据少的情况下,将数据存入Reids中,代替数据库的作用
    2. 作为配置中心使用,例如:花海仓wap
    3. 作为缓存使用,减轻数据库的压力
    4. 作为消息队列使用
    5. 作为唯一集合,抽奖时使用
    6. 热点数据排序
    7. 实现分布式
    8. 限流

    二、RedisMemcached的主要区别

    区别

    Redis Memcached
    支持的数据类型 支持5种数据类型 只支持String
    数据持久化 Redis断电重启后数据可恢复 mc断电重启后数据不可恢复

     

     

     

     

    三、Redis5种数据结构类型

    1、Redis5种数据类型分别是:Stringlisthashsetzset

    1. String(字符串):最简单的字符串存储类型。
    2. list(列表):类似于java中的LinkedList,是链表而不是数组,常用来作为异步队列使用。举例:分享助力领红包的时候,红包生成后,先放入Redis队列中,用户领取时就不会出现超领,而且速度很快。
    3. hash(字典):类似于java中的HashMap。举例:一个对象的不同信息可以使用hash存储。
    4. set(集合):类似于java中的HashSet,内部值是无序且唯一的,常用来存储唯一性的值;举例:可以存储抽奖的用户id,因为是唯一的,可以避免一个人同时中奖多次。
    5. zset(有序列表):类似于java中的SortedSetHashMap的集合,一方面key是唯一的,另一方面可以为每一个value赋予一个score,我们可以通过scorevalue值进行排序。举例:比如对Top100商品进行排序,key可以是Top10,value是商品Id,Score是商品销售件数,这样我们就可以根据Score对商品Id进行排序。

    2、StringHash的取舍

    场景:主要是要存储的值是一个对象的时候,需要考虑使用String还是Hash

    String的优点:占用储存空间小,但是操作命令简单

    String的缺点:

             1.存储对象时需要进行序列话,使用对象时需要进行反序列化,比较麻烦;

              2.不能针对对象的某个key进行操作,每次都是整个对象的读写,消耗带宽;

    Hash的优点:

    1.可以针对对象的某个key进行操作,不需要操作整个对象
    2.不需要进行序列化和反序列化

    Hash的缺点:占用存储空间大,操作命令多

    四、Redis实现分布式

    使用场景

    多线程并发操作,比如:领红包场景中,可能会出现用户同时点击几次的情况,

    这样可能会出现多个线程为用户领红包,就可能会出现一个人消耗掉了多个红包的场景。

    这种情况下,我们可以将用户id设为key,一个随机值作为value,注意要有超时时间。

    注意事项:

    1. 获取锁的时候,要有超时时间,避免获取不到锁时,线程一直等待
    2. 锁的value值最好设置一个随机值,当前线程执行完成之后,根据改value值释放锁
    3. 获取到锁,也要设置锁的超时时间,避免异常情况下锁一直不释放

    五、Redis缓存未命中问题

    主要是三种场景:缓存穿透、缓存击穿、缓存雪崩

    1、缓存穿透

    概念缓存穿透指去请求缓存中根本不存在的数据,这时候服务就会去请求数据库

    危害如果有人大量访问这样的数据,可能导致DB负载过重而挂调

    举例有人大量请求userId小于0,或者userId非常大的数据

    解决方案主要是两种:事前预防、事后预防

    事前预防:对入参进行校验,如将userId小于0的请求直接拦截掉

    事后预防:将不存在的数据的空结果也进行缓存,只是设置一个很短的过期时间,将压力转移到Redis去承担。

    总结事前预防可以拦截掉80%的非法请求,事后预防可以将剩下的20%非法请求转到到redis上。

     

    2、缓存击穿

    概念高并发的key,在key过期的那一瞬间,大量请求并发到了数据库,造成了数据库压力

    举例一个并发量很大的key在某一时间过期,这时候就会有很大量的请求同时去请求数据库

    解决方案主要是两种:永不过期、互斥锁

    永不过期:可以使用定时任务将数据库中数据定时更新到缓存中,并设置永不过期

    互斥锁:分布式锁实现,即同一时间只能有一个线程去查数据库,并更新缓存。

     

    3、缓存雪崩

    概念大量的key在同一时间过期,导致大量请求发到了数据库,造成数据库压力

    解决方案key过期时间设置为不一样的值,如过期时间再增加一个随机时间

     

    4、总结

    缓存穿透是业务层面的漏洞导致非法请求,与请求量、缓存失效没关系

    缓存击穿则只会出现在热点数据上,发生在缓存失效的瞬间,与业务没多大关系

    缓存雪崩则是由于多个key同时失效,导致数据库请求太多。非热点数据也会导致缓存雪崩,只要同时失效的key足够多。

     

    六、缓存更新策略

    1、查询时更新

    当查询数据时,发现缓存没有(也可能是过期了),查询数据库并更新缓存。

    2、修改时更新

    当后台更新数据时,要同时删除旧缓存,并更新缓存。

    3、定时任务更新

    为避免有缓存没有更新或异常情况下缓存未失效,使用定时任务进行更新,如每晚1点全量更新缓存数据。

    4、人工刷新缓存

    大促预热,或发现缓存未更新(或者担心缓存不更新),手动刷新缓存。

     

    【免责申明】本博客为本人工作学习总结整理,如有涉及侵权,请联系博主删除。本博客仅为学习交流使用,商业用途请勿使用。内容如有错误,欢迎留言交流学习。

    展开全文
  • 今天遇到了一个前同事挖的坑,刷新缓存中商品信息时先让key过期,然后从数据库里取最新数据然后再放到缓存中,他是这样写的 redisTemplate.expire(CacheConst.GOOGS_PREFIX,1,TimeUnit.MILLISECONDS); 设置key过期...
  • JWTToken超时刷新策略

    万次阅读 热门讨论 2018-09-18 20:55:50
    解决方案使用Cache做缓存(使用redis也可以,效果相同)。 /** * JWTToken刷新生命周期 * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样) * 2、当该用...
  • 缓存在应用中是必不可少的,经常用的如redis、memcache以及内存缓存等。Guava是Google出的一个工具包,它里面的cache即是对本地内存缓存的一种实现,支持多种缓存过期策略。 Guava cache的缓存加载方式有两种: ...
  • Redis V3.0 中文文档

    2018-01-13 22:37:31
    第 1 章Redis 介绍. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 第 2 章数据类型初探. . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...
  • 第 1 章Redis 介绍. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 第 2 章数据类型初探. . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...
  • 调用要求:一个用于刷新的refreshToken只能调用一次即失效,因此需要将每次来的token缓存起来,每次新来的token去缓存中查看是否存在,如果不存在(即首次调用)则返回新token,如果存在则不再返回; 2. 解决策略 ...
  • 先说明不是java代码的的问题,是redis缓存的问题 redis 在做缓存的时候序列化失败造成的,造成序列化失败。 解决策略: 一: 情况缓存数据库里面的缓存。刷新接口就好了 连接redis 选择库, flushdb 二: 在java ...
  • 问题记录

    2016-08-23 15:17:00
    实时刷新分布式缓存: 产品信息采用的分级存储的...当产品配置出错时,错误配置被redis缓存后,10分钟才会恢复,业务无法接受 所以需要提供一种策略,可以使本地缓存和redis立刻失效 解决方案: 1)分析业务...
  • Govern Service 是一个轻量级、低成本的...Govern Service 结合本地进程缓存策略 + Redis PubSub,实现实时进程缓存刷新,兼具无与伦比的 QPS 性能、进程缓存与 Redis 的实时一致性。 服务发现 maven 依赖,如下服
  • JetCache总结

    千次阅读 2020-06-01 22:08:45
    特性 ...分布式缓存自动刷新,分布式锁 (2.2+) 异步Cache API (2.2+,使用Redis的lettuce客户端时) Spring Boot支持 JetCache整合srpingboot配置 maven配置 <!--注意版本要对应,不然会报错-->
  • jeesite后台框架

    2018-09-07 19:09:56
    缓存EhCache统一管理,支持快速切换为Redis缓存,集群Session缓存共享。 安全方面优化 原有JeeSite1.2安全选项及安全考虑保留。 配置文件数据库密码及其它安全密钥自动加密。 所有请求参数获取,均通过XSS跨站脚本...
  • 回收策略和算法? 面试官:讲讲Redis的五大数据类型?如何使用?(内含完整测试源码) 今天又一名读者斩获蚂蚁金服Offer,就是这么简单!! 面试官:讲讲七层网络模型与TCP三次握手与四次断开? 面试官问我:如果让...
  • 3种常用的缓存读写策略! 系统设计 系统设计必备基础 RESTful API 我们在进行后端开发的时候,主要的工作就是为前端或者其他后端服务提供 API 比如查询用户数据的 API 。RESTful API 是一种基于 REST 构建的 API...
  • 缓存 Redisson : Redisson是架设在Redis基础上的一个 Java 驻内存数据网格(In-Memory Data Grid),支持超过 30 个对象和服务:Set,SortedSet, Map, List, Queue, Deque ......。更多介绍请看:《Redisson 项目...
  • 3种常用的缓存读写策略! 系统设计 系统设计必备基础 RESTful API 我们在进行后端开发的时候,主要的工作就是为前端或者其他后端服务提供 API 比如查询用户数据的 API 。RESTful API 是一种基于 REST 构建的 API...
  • 3种常用的缓存读写策略! 系统设计 系统设计必备基础 RESTful API 我们在进行后端开发的时候,主要的工作就是为前端或者其他后端服务提供 API 比如查询用户数据的 API 。RESTful API 是一种基于 REST 构建的 API...
  • spring.emily.redis.enable=false #限流组件开关,默认:false spring.emily.rate-limit.enable=true #防止重复提交组件开关,默认:false spring.emily.idempotent.enable=false #RestTemplate组件 #...

空空如也

空空如也

1 2
收藏数 23
精华内容 9
关键字:

redis缓存刷新策略

redis 订阅