设计一个缓存策略,动态缓存热点数据

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 写在前面,因为我们最近的大作业项目需要用到热点排行这个功能,因为我们是要使用Elasticsearch来存储数据,然后最初设想是在ES中实现这个热点排行的功能,但是经过仔细思考,在我们这个项目中使用ES来做热点排行是一个很蠢的方式,因为我们这只是一个很小的排行,所以最终我们还是使用Redis来实现热点排行

使用LRU?


LRU是一种常见的算法,假如我们设定TOP10的热点数据,那么我们可以规定LRU容量为10,当容量没有满的时候,我们可以直接放入,当满了的时候我们就将最后一个排除然后引入最新的放在首部

241327197ae849d4a6dcd2a59b46dad6.png

这看似实现了热点排行但是没有,比如说2号数据访问100次而11号数据才访问一次,那么使用LRU就把100次访问的排除掉了,这就是不合理的,所以我们应该以每个数据的访问频率来选择排行


如何进行访问率排行


把所有数据都加入内存中,然后记录每个数据被访问的频率,这看起来就是很简单的,使用zset就可以实现,但是假如你的数据有100w条呢?你这样全部存入Redis,那么会导致大key的出现,同时引起Redis的效率降低,那么可以单独启动一台服务器来保存排行榜的数据?这其实是浪费的,因为一般我们的排行榜都是TOP10~TOP100,基本占用不了多少内存,而在我们的项目中我们的数据量是比较少的,而且有上传时间,一般上传时间越近更容易上TOP10,而且我们需要的只是TOP10,所有有两种方案


第一种:在数据库中挑选最近上传的10条数据,然后如果有人访问了这10条数据,那么对应的数据的访问频率就加一,不在这10条数据里面就不去管它,然后经过一段时间就去掉末尾几条访问频率较低的数据,再随机挑选几条假如TOP10,然后循环

第二种:第一种还是存在一点缺陷,就是有可能最开始TOP10就是访问最高的,那么可能会把真正的TOP10挤下去,所有在第二种方案中,我们缓存20条数据,每隔一段时间去掉访问频率最低的5-10条,然后随机挑选进来补充至20条但是我们只取前10,其它与方案一类似,只是缓存更多的数据

代码编写


理解思路过后,代码编写是最简单的一步,如何在项目中引入Redis以及操作Redis的依赖配置就不再赘述,因为那个与代码编写逻辑没有什么关联

选择最近20条数据

    public void getCur2MySQL(){
        Set<ZSetOperations.TypedTuple<String>> set = new HashSet<>();
        List<Integer> cur2Ids = baseMapper.getCur2Ids();
        cur2Ids.stream().forEach(e->{
            DefaultTypedTuple<String> tuple = new DefaultTypedTuple<String>(String.valueOf(e),0d);
            set.add(tuple);
        });
        redisTemplate.opsForZSet().removeRange(Constant.POLICY_TOP_10,0,-1);
        redisTemplate.opsForZSet().add(Constant.POLICY_TOP_10,set);
    }

有访问就加一

这里加一方法可以使用Lua脚本,感兴趣的大佬可以去优化

  //访问
    public PolicyEntity getByPolicyById(Integer id) {
        addVisited(id);
        Object o = redisTemplate.opsForHash().get(Constant.POLICY_HASH_OBJECT, Integer.toString(id));
        PolicyEntity policy = JSON.parseObject((String) o, PolicyEntity.class);
        return policy;
    }
  //加一
    public void addVisited(Integer id){
        if(redisTemplate.opsForZSet().score(Constant.POLICY_TOP_10, String.valueOf(id)) != null){
            redisTemplate.opsForZSet().incrementScore(Constant.POLICY_TOP_10,String.valueOf(id),1d);
        }
    }

获取Top10

    public List<PolicyEntity> getTop10() {
        Set<String> ids = redisTemplate.opsForZSet().reverseRange(Constant.POLICY_TOP_10, 0, 9);
        List<Object> list = new ArrayList<>();
        ids.stream().forEach(list::add);
        List<Object> multiGet = redisTemplate.opsForHash().multiGet(Constant.POLICY_HASH_OBJECT, list);
        List<PolicyEntity> res = new ArrayList<>();
        multiGet.stream().forEach((d)->{
           PolicyEntity policyEntity = JSON.parseObject((String) d, PolicyEntity.class);
           res.add(policyEntity);
       });
        return res;
    }

接下来就是实现定时任务的代码编写,我使用的是Quartz编写定时任务,这个实现定时任务还是有着其它的方法,如果有兴趣的大佬可以去尝试尝试

编写任务

删除最后五个然后在数据库中随机挑选五个加入其中

@Component
public class TopTenQuartzJob extends QuartzJobBean {
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    PolicyService policyService;
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        redisTemplate.opsForZSet().removeRange(Constant.POLICY_TOP_10,0,4);
        Set<String> ids = redisTemplate.opsForZSet().range(Constant.POLICY_TOP_10, 0, -1);
        List<Integer> list = ids.stream().map(e -> Integer.valueOf(e)).collect(Collectors.toList());
        List<Integer> id = policyService.listIdsAndNotIn(list);
        Set<ZSetOperations.TypedTuple<String>> set = new HashSet<>();
        id.stream().forEach(e->{
            DefaultTypedTuple<String> tuple = new DefaultTypedTuple<String>(String.valueOf(e),0d);
            set.add(tuple);
        });
        redisTemplate.opsForZSet().add(Constant.POLICY_TOP_10,set);
        System.out.println("删除了");
    }
}

其中的xml文件为

    <select id="listIdsAndNotIn" resultType="integer">
        select id from p_policy where id not in
        <foreach collection="list" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
        order by RAND() limit 5
    </select>


编写Trigger与JobDetail

@Configuration
public class QuarztConfig {
    @Value("${quartz.policy.top10.cron}")
    private String cron;
    @Bean
    public JobDetail topTenQuartzJobDetail(){
        JobDetail jobDetail = JobBuilder.newJob(TopTenQuartzJob.class)
                .storeDurably()
                .build();
        return jobDetail;
    }
    @Bean
    public Trigger topTenQuartzTrigger(){
        CronScheduleBuilder schedule = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .forJob(topTenQuartzJobDetail())
                .withSchedule(schedule)
                .build();
        return trigger;
    }
}

这种方式实现排行榜还是存在着问题,假如存在一个经常访问的数据但是一直随机没有随机进Redis,那么它就一直上不了排行榜,但是对于我这种项目的实现已经够用了,因为在我这个项目中我们保存的是最新的文件而且比较少,一般对于文件这种上热榜一般都是新发布的,所以该影响对此项目的影响较小

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
缓存 算法 数据挖掘
深入理解缓存更新策略:从LRU到LFU
【10月更文挑战第7天】 在本文中,我们将探讨计算机系统中缓存机制的核心——缓存更新策略。缓存是提高数据检索速度的关键技术之一,无论是在硬件还是软件层面都扮演着重要角色。我们会详细介绍最常用的两种缓存算法:最近最少使用(LRU)和最少使用频率(LFU),并讨论它们的优缺点及适用场景。通过对比分析,旨在帮助读者更好地理解如何选择和实现适合自己需求的缓存策略,从而优化系统性能。
48 3
|
22天前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
56 10
|
18天前
|
Web App开发 缓存 UED
如何设置浏览器的缓存策略?
【10月更文挑战第23天】通过合理地设置浏览器的缓存策略,可以在提高网页性能、减少网络流量的同时,确保用户能够获取到最新的内容,从而提升用户体验和网站的性能优化效果。
54 4
|
19天前
|
存储 消息中间件 缓存
缓存策略
【10月更文挑战第25天】在实际应用中,还需要不断地监控和调整缓存策略,以适应系统的变化和发展。
|
19天前
|
存储 缓存 算法
分布式缓存有哪些常用的数据分片算法?
【10月更文挑战第25天】在实际应用中,需要根据具体的业务需求、数据特征以及系统的可扩展性要求等因素综合考虑,选择合适的数据分片算法,以实现分布式缓存的高效运行和数据的合理分布。
|
22天前
|
缓存 监控 NoSQL
Redis 缓存穿透及其应对策略
【10月更文挑战第23天】通过以上对 Redis 缓存穿透的详细阐述,我们对这一问题有了更深入的理解。在实际应用中,我们需要根据具体情况综合运用多种方法来解决缓存穿透问题,以保障系统的稳定运行和高效性能。同时,要不断关注技术的发展和变化,及时调整策略,以应对不断出现的新挑战。
42 4
|
26天前
|
存储 缓存 NoSQL
保持HTTP会话状态:缓存策略与实践
保持HTTP会话状态:缓存策略与实践
|
1月前
|
存储 缓存 监控
|
1月前
|
缓存 监控 前端开发
处理页面缓存中数据不一致的问题
【10月更文挑战第9天】
42 2