首页 > 数据库 >Redis中惰性策略的启发和流量包应用设计

Redis中惰性策略的启发和流量包应用设计

时间:2024-04-05 23:58:33浏览次数:24  
标签:删除 过期 Redis 流量 惰性 key 更新 启发

引言

        在技术领域,许多中间件之所以获得巨大成功,部分原因在于它们所采用的思想之先进。这些思想解决了一个个世纪难题,接下来我将讲述一个我学习到的思想,并将其应用至工作中的案例。

        惰性策略在日常编码中随处可见,但究竟什么是惰性策略呢?简而言之,惰性策略是一种优化方法,即在不需要进行计算或操作时,不会真正进行相关的处理,而是仅仅记录相关信息或轨迹。只有在需要执行行动操作时,才会触发从头到尾的真正计算。这种机制大大减少了不必要的资源消耗,提高了程序的效率。惰性策略的使用有很多,其中比较常见的便是Redis了,从中学习这些思想可以在我们日后遇到难题时得到帮助。

中间件设计思想:Redis过期Key淘汰策略

        在早些年作为编程小白的我,在使用Redis时常会想一些问题,例如:Redis的Key配置了过期时间,这个是怎么被删除的?Redis数据明明过期了,怎么还占用着内存?

主动策略和惰性策略

        对于这些问题,曾设想过他们的设计思路,例如对于如何清除过期的 Key ,很自然的可以想到就是可以给每个 key 加一个定时器,这样当时间到达过期时间的时候就自动删除 key,这种定时策略也叫主动策略。

        但从辩证角度来看这种方式使之有过期时间的 Key都需要一个定时器,那么这对 CPU 是极不友好的,会占用较多的CPU资源。后来在不断探究过程中,Redis同样也使用了惰性策略,即不用定时器,采取被动的方式,在访问一个 key 的时候去判断这个 key 是否到达过期时间了,过期了则删除掉。

        这种定期删除+惰性删除的Key过期策略,使得不会立即从内存中删除,当过期key未被客户端调用且未达到执行主动策略的时间,此Key依旧存在内存中。通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。如果定期删除漏掉了很多过期 Key的同时也没及时去查,没走惰性删除,就是造成大量过期 key 堆积在内存里,最终会导致 redis 内存块耗尽,那么Redis此时会走内存淘汰机制。

如何淘汰过期的keys

       通过redis命令行运行set name xdclass 3600后,每个设置了过期时间的Key都会放入到一个独立的容器中。

定期删除

        隔一段时间,就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除,这种定期删除的方式可能会导致很多过期 Key 到了时间并没有被删除掉。

      摘自官方文档:EXPIRE | Redis

Redis 会每秒进行10次过期扫描,过期扫描不会遍历容器中所有的 key,而是采用一种特殊策略

        1)从容器中随机 20 个 key;
        2)删除这 20 个 key 中已经过期的 key;
        3)如果过期的 key 比率超过 1/4,那就重复步骤 1;

惰性删除

        当某个客户端试图访问key时,发现该key已超时会把此key从内存中删除。

主从架构Key删除策略

       从节点不会让key过期,而是主节点的key过期删除后,成为del命令传输到从节点进行删除
主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在。

架构中的启发

        类似于Redis的这种思想其实在主流的中间架构中几乎随处可见,例如Spring中bean创建懒加载(延迟加载)、设计模式中单例创建的懒汉式、Mybatis的懒加载,借助于这种思想在工作中解决了许多数据更新问题,也延伸出了许多方案。例如我再在实际工作中流量包更新维护需求,免费流量包:业务为了拉新,鼓励新用户注册,赠送一个免费流量包,每天允许有一定次免费创建短链的次数。

        采用惰性策略解决方案,不用每天更新全部流量包,用的时候再更新即可。这样使得只要用户有使用,流量包都是可以得到更新,没使用的用户流量包不会去更新,避免了海量数据下更新维护的问题,如果采用定时更新,几千万用户更新记录都是会有不少时间的延迟。
整体步骤如下:

        1)查询用户全部可用流量包
        2)遍历用户可用流量包,判断是否更新-用日期判断(要么都更新过,要么都没更新,根据gmt_modified)。没更新的流量包后加入【待更新集合】中,增加【今天剩余可用总次数】;已经更新的判断是否超过当天使用次数,如果没超过则增加【今天剩余可用总次数】,超过则忽略;
        3)更新用户今日流量包相关数据;
        4)扣减使用的某个流量包使用次数;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UseTrafficVO {

    /**
     * 天剩余可用总次数 = 总次数-已用
     */
  private   Integer dayTotalLeftTimes;

    /**
     * 当前使用流量包
     */
    private   TrafficDO currentTrafficDO ;

    /**
     * 没过期,且今天没更新的流量包
     */
    private  List<Long> unUpdatedTrafficIds = new ArrayList<>();

}



@Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData useTraffic(UseTrafficRequest trafficRequest) {

        Long accountNo = trafficRequest.getAccountNo();

        //处理流量包,筛选未更新流量包、当前使用流量包
        UseTrafficVO useTrafficVO = processTrafficList(accountNo);

        log.info("今天可用总次数:{}, 当前使用的流量包:{}",useTrafficVO.getDayTotalLeftTimes(),useTrafficVO.getCurrentTrafficDO());
        //如果当前流量包为空,则没有可用流量包
        if(useTrafficVO.getCurrentTrafficDO() == null){
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        log.info("待更新流量包列表:{}",useTrafficVO.getUnUpdatedTrafficIds());
        if(useTrafficVO.getUnUpdatedTrafficIds().size() >0) {
            //更新今日流量包
            trafficManager.batchUpdateUsedTimes(accountNo, useTrafficVO.getUnUpdatedTrafficIds());
        }

        //先更新,再增加此次流量包扣减
        int rows = trafficManager.addDayUsedTimes( accountNo,  useTrafficVO.getCurrentTrafficDO().getId(),1);
        if(rows !=1){
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        return JsonData.buildSuccess();
    }



/**
     * 处理流量包,筛选未更新流量包、当前使用流量包
     * @param accountNo
     */
    private  UseTrafficVO processTrafficList(Long accountNo){


        //全部流量包
        List<TrafficDO> list = trafficManager.selectAvailableTraffics(accountNo);
        if (list == null || list.size() == 0) { throw new BizException(BizCodeEnum.TRAFFIC_EXCEPTION);}


        //天剩余可用总次数 = 总次数-已用
         Integer dayTotalLeftTimes = 0;

        //当前使用流量包
         TrafficDO currentTrafficDO = null;

        //没过期,且今天没更新的流量包
        List<Long> unUpdatedTrafficIds = new ArrayList<>();

        //今天日期
        String todayStr = TimeUtil.format(new Date(),"yyyy-MM-dd");

        for(TrafficDO trafficDO : list){
            //判断是否更新,用日期判断,不能用时间
            String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(),"yyyy-MM-dd");
            if(todayStr.equalsIgnoreCase(trafficUpdateDate)){
                //已经更新   剩余可用 = 天总次数-已用次数
                int dayLeftTimes = trafficDO.getDayLimit()-trafficDO.getDayUsed();
                dayTotalLeftTimes = dayTotalLeftTimes + dayLeftTimes;

                //选取 当次流量包
                if(dayLeftTimes>0 && currentTrafficDO == null){
                    currentTrafficDO = trafficDO;
                }
            }else {
                //未更新
                dayTotalLeftTimes = dayTotalLeftTimes + trafficDO.getDayLimit();
                //记录未更新流量包  剩余可用 = 天总次数
                unUpdatedTrafficIds.add(trafficDO.getId());

                //选取 当次流量包
                if(currentTrafficDO == null){
                    currentTrafficDO = trafficDO;
                }

            }

        }

        UseTrafficVO useTrafficVO =
                new UseTrafficVO(dayTotalLeftTimes,currentTrafficDO,unUpdatedTrafficIds);

        return useTrafficVO;

    }

标签:删除,过期,Redis,流量,惰性,key,更新,启发
From: https://blog.csdn.net/qq_30294911/article/details/137248172

相关文章

  • Redis各个方面入门详解
    目录一、Redis介绍二、分布式缓存常见的技术选型方案三、Redis和Memcached的区别和共同点四、缓存数据的处理流程五、Redis作为缓存的好处六、Redis常见数据结构以及使用场景七、Redis单线程模型八、Redis给缓存数据设置过期时间九、Redis判断数据过期的原理十......
  • 【爬虫】项目篇-爬取豆瓣电影周榜Top10,保存至Redis
    写法一:编写两个爬虫程序文件:爬虫1将豆瓣一周口碑榜的电影url添加到redis中名为movie_url的列表中(注意避免多次运行导致重复的问题);爬虫2从movie_url中读出网址,爬取每一部电影的导演、主演、类型、制片国家/地区、语言、上映日期、片长,并将它们保存到redis的hash表(自行命名)中。d......
  • Redis数据库——群集(主从、哨兵)
    目录前言 一、主从复制1.基本原理2.作用3.流程4.搭建主动复制4.1环境准备4.2修改主服务器配置4.3从服务器配置(Slave1和Slave2)4.4查看主从复制信息4.5验证主从复制二、哨兵模式——Sentinel1.定义2.原理3.作用4.组成5.工作机制6.搭建哨兵模式6.1环境准备6.......
  • Redis缓存穿透和缓存雪崩
    一、缓存穿透1什么是缓存穿透        缓存穿透说简单点就是大量请求的key根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的key发起大量请求,导致大量请求落到数据库。2处理流程如下图所示,用户......
  • redis6.2.6配置文件说明
     导游Redis版本配置文件说明###UNIT(单位)###(了解)###INCLUDES(包含)###(了解)###MODULES(模块)###(了解)###NETWORK(网络)###(需记)###TLS/SSL(安全套接字)###(了解)###GENERAL(通用)###(精通)###SNAPSHOTTING(快照)###(需记)###REPLICATION(主从)###(必会)###KEYSTRACKING(键跟踪)###(了......
  • 【Redis系列】Redis安装与使用
    ......
  • Redis 的主从复制、哨兵
    目录一.Redis主从复制1.介绍2.作用3.流程4.搭建Redis主从复制 安装redis修改master的Redis配置文件修改slave的Redis配置文件验证主从效果二.Redis哨兵模式1.介绍2.原理3.哨兵模式的作用4.工作流程4.1故障转移机制4.2主节点的选举5.搭......
  • redis持久化
        redis是一个基于内存的数据库,如果没有持久化的话,那么一旦服务器重启或者断电,之前的数据都会丢失(RAM)        redis的持久化有两种方式:    一种是RDB(RedisDatabase),另一种是AOF(AppendOnlyFile)RDB    是将内存中的数据快照写入快照,它......
  • 【智能排班系统】基于Redis的increment命令和lua脚本实现IP限流
    文章目录什么是IP限流?为什么需要IP限流?保护服务器资源防范恶意攻击与滥用提升用户体验IP限流方式:实现实现原理代码结构lua脚本为什么要使用lua脚本Ip限流工具类对所有接口进行限流IP限流请求过滤器SpringSecurity中配置过滤器对不经过SpringSecurity的过滤器的请求进......
  • Online_install_mem_redis.sh
    Online_install_mem_redis.sh......