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

news2024/10/6 1:45:50

👨‍💻个人主页: 才疏学浅的木子
🙇‍♂️ 本人也在学习阶段如若发现问题,请告知非常感谢 🙇‍♂️
📒 本文来自专栏: 常见场景解决方案
🌈 每日一语努力不一定成功,但不努力一定不会成功 🌈
❤️ 支持我:👍点赞 🌹收藏 🤟关注

写在前面,因为我们最近的大作业项目需要用到热点排行这个功能,因为我们是要使用Elasticsearch来存储数据,然后最初设想是在ES中实现这个热点排行的功能,但是经过仔细思考,在我们这个项目中使用ES来做热点排行是一个很蠢的方式,因为我们这只是一个很小的排行,所以最终我们还是使用Redis来实现热点排行

使用LRU?

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

这看似实现了热点排行但是没有,比如说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,那么它就一直上不了排行榜,但是对于我这种项目的实现已经够用了,因为在我这个项目中我们保存的是最新的文件而且比较少,一般对于文件这种上热榜一般都是新发布的,所以该影响对此项目的影响较小

如果有其它大佬有设计更好的排行榜希望可以评论区留言或者私信,感激不尽!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/25726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 0 —— 信息在计算机中的表示

计算机的电路 由 逻辑门电路组成。一个逻辑门电路可以看成为一个开关&#xff0c;每个开关的状态是“开” 则 高电位 对应 1 或者 “关” 则 低电位 对应 0 &#xff0c; 那么1和0 刚刚好用二进制数来表示&#xff1a; 每个位只能取1和0 &#xff0c;称为 one 个 bit &#…

谈一谈关于Linux内核编译详解原理

前言&#xff1a;为什么要做这个启动盘&#xff0c;因为内核编译是很危险的&#xff0c;中间出了错系统则直接崩溃&#xff0c;然后就无法开机了&#xff0c;你将看到一个_在你的左上角闪烁。知道启动盘可以帮你从外置设备启动系统&#xff0c;能启动系统才能恢复系统。1.编译前…

Java 线程和反射---尚硅谷Java视频学习

1.Java程序在运行得时候默认就会产生一个进程2.这个进程会有一个主线程3.代码都在主线程中执行 线程的生命周期 线程的执行方式 public class Java02_Thread {public static void main(String[] args) throws Exception {// TODO 线程 - 执行方式&#xff08;串行&#xff0c…

ASP.NET Core 3.1系列(14)——分布式缓存Redis的使用

1、前言 前一篇博客介绍了ASP.NET Core中本地缓存MemoryCache的使用方法。相较于本地缓存&#xff0c;分布式缓存更加适合大多数项目应用场景&#xff0c;下面就来介绍一下如何在ASP.NET Core中对Redis缓存进行相关操作。 2、分布式缓存接口——IDistributedCache 对于分布式…

pytorch深度学习实战24

第二十四课 VGG网络 VGG是Oxford的Visual Geometry Group的组提出的&#xff08;大家应该能看出VGG名字的由来了&#xff09;。该网络是在ILSVRC 2014上的相关工作&#xff0c;主要工作是证明了增加网络的深度能够在一定程度上影响网络最终的性能。VGG有两种结构&#xff0c;分…

远程重启电脑

一、设置电脑允许自动启动 在远程计算机上编辑设置远程注册表 步骤1. 按“WindowsR”键调用运行对话框。输入“services.msc”并点击“确定”。 步骤2. 在“服务”窗口中&#xff0c;双击“RemoteRegistry”以检查其状态。 步骤3. 将启动类型更改为“自动”。 二、查找远程计…

SPARKSQL3.0-各阶段自定义扩展规则源码剖析

一、前言 这一节主要介绍如何自定义扩展各阶段规则 虽然spark内部提供了很多优化规则&#xff0c;但在实际工作中&#xff0c;经常因为业务需求需要自定义扩展优化器或解析器&#xff0c;故自己实现一个优化器才对sparksql有更深的理解 二、扩展范围 spark在扩展方便做的很…

vue.js毕业设计,基于vue.js前后端分离教室预约小程序系统设计与实现

功能介绍 【后台管理功能模块】 系统设置&#xff1a;设置关于我们、联系我们、加入我们、法律声明 广告管理&#xff1a;设置小程序首页轮播图广告和链接 留言列表&#xff1a;所有用户留言信息列表&#xff0c;支持删除 会员列表&#xff1a;查看所有注册会员信息&#xff0…

从零开始学前端:DOM、BOM、焦点事件 --- 今天你学习了吗?(JS:Day20)

从零开始学前端&#xff1a;程序猿小白也可以完全掌握&#xff01;—今天你学习了吗&#xff1f;&#xff08;JS&#xff09; 复习&#xff1a;从零开始学前端&#xff1a;CSSOM视图模式 — 今天你学习了吗&#xff1f;&#xff08;JS&#xff1a;Day19&#xff09; 文章目录从…

java8 (jdk 1.8) 新特性——Stream ApI

在java8 中&#xff0c;有两个最重要的改变&#xff0c;一个就是之前了解的Lmbda java8 (jdk 1.8) 新特性——Lambda ,还有一个就是Stream Api 1. 什么是Stream API 简单来说就是一个类库&#xff0c;里边有一些方法方便我们对集合数据进行操作&#xff0c;就好像使用 SQL 语…

Windows cmd 命令及Linux 环境下导入导入mysql 数据库

文章目录一、背景二、Windows cmd 导入导出mysql 数据库1.导出数据库三种方式&#xff08;导出数据库时不需要连接数据库&#xff09;2. 操作步骤2.导入数据库三、linux 环境下导入导出数据库一、背景 最近在本机上安装了一个WMware 虚拟机&#xff0c;需要从本机&#xff08;…

从三层架构说起,谈谈对历史项目的小改造

项目背景说明 最近接手一个 “老” 项目的需求修改&#xff0c;项目整体基于 .net core 3.1 平台&#xff0c;以传统的三层架构为基础构建。了解需求后&#xff0c;逐步对原有项目框架进行大概的了解&#xff0c;主要是熟悉一些框架的开发规范&#xff0c;基本工具类库的使用&…

寒亭5.8万亩盐碱稻 国稻种芯·中国水稻节:山东潍坊插秧期

寒亭5.8万亩盐碱稻 国稻种芯中国水稻节&#xff1a;山东潍坊插秧期 新京报讯&#xff08;记者赵利新&#xff09;新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#xff…

MMRotate 全面升级,新增 BoxType 设计

引言&#xff1a;大大降低水平框检测器改旋转框检测器的难度 MMRotate 是一个基于 PyTorch 和 MMDetection 的开源旋转框目标检测工具箱。它将目标检测从水平框扩展到旋转框&#xff0c;为场景文字、遥感影像、自动驾驶等领域的应用打下了基础&#xff0c;为学术界和产业界提供…

瞄准镜-第12届蓝桥杯Scratch选拔赛真题精选

[导读]&#xff1a;超平老师计划推出Scratch蓝桥杯真题解析100讲&#xff0c;这是超平老师解读Scratch蓝桥真题系列的第82讲。 蓝桥杯选拔赛每一届都要举行4~5次&#xff0c;和省赛、国赛相比&#xff0c;题目要简单不少&#xff0c;再加上篇幅有限&#xff0c;因此我精挑细选…

数据结构——单链表

一.简介 上一篇文章&#xff0c;我们介绍了线性表中的顺序表。 而顺序表拥有一些缺陷 1.空间不够时需要增容&#xff0c;增容需要付出代价 2.为避免重复扩容&#xff0c;我们进行指数扩容&#xff0c;可能会造成空间浪费 3.顺序表从开始位置连续存储&#xff0c;插入删除数…

卡特尔世界杯来了,只喝精酿啤酒不玩望京扑克,其实也是一种缺失

北京时间2022年11月20日&#xff0c;卡特尔世界杯正式拉开了序幕&#xff0c;全球都进入了世界杯时间。世界杯的开幕&#xff0c;最高兴的还是球迷朋友&#xff0c;大家可以欢聚一堂&#xff0c;喝着精酿啤酒看着足球&#xff0c;那滋味别提多舒服了。 世界杯对于广大球迷来说&…

表的增删查改

目录 插入数据 基本查询 更新数据 清空数据 聚合函数 group by子句 内置函数 基本查询练习 多表查询 子查询 合并查询 表的内外连接 插入数据 单行—全列插入 如下图&#xff0c;全列插入可以省略要在哪些列插入&#xff01; 多行—指定列插入 如下图&#xff0…

安装 Red Hat Enterprise Linux 9.1 虚拟机

目录1. 官方下载链接与新闻2. 安装提示3. 系统安装步骤&#xff08;1&#xff09;进入系统引导界面&#xff08;2&#xff09;进入【系统语言选择】界面&#xff08;3&#xff09;进入【安装信息摘要】界面① 设置【root密码】② 设置【安装目的地】&#xff08;4&#xff09;进…

【python】使用python将多个视频合并、延长视频的时间

今天做知识分享的时候&#xff0c;最后保存的视频时长是58分钟&#xff0c;那么如何改变视频的时长&#xff0c;将视频时长改为一个小时呢&#xff1f; 下面提供3个方案&#xff1a; 方案1&#xff0c;重新录&#xff0c;很显然&#xff0c;不合理&#xff1b; 方案2&#xf…