当前位置: 首页 > news >正文

Redis 中 key 的过期策略 和 定时器的两种实现方式

目录

1. 定期删除 

2. 惰性删除 

3. 补充

4. 定时器

4.1 基于优先级队列/堆

4.2 基于时间轮实现的定时器


一个 redis 中可能同时存在很多的 key, 这些 key 中可能有很大一部分都有过期时间, 此时 redis 服务器咋知道哪些 key 已经过期要被删除, 哪些 key 还没过期 ? 

如果要直接遍历所有的 key, 显然是行不通的, 效率非常的低

这里 redis 有两个策略 :

1. 定期删除 

每次抽取一部分, 进行验证过期时间, 保证这个抽取检查的过程, 足够快!

为啥这里对于定期删除的时间, 有明确的要求呢? 

因为 redis 是单线程的程序, 主要任务是处理每个命令的任务, 和扫描过期的 key. 如果扫描的过期 key 消耗的时间太多了, 就可能导致正常处理请求的命令就被阻塞了. (产生了类似于执行 keys * 这样的效果)

2. 惰性删除 

假设这个 key 已经到过期的时间了, 但是暂时还没有删除它, key 还在. 紧接着, 后面有一次访问, 正好用到了这个 key, 于是这次访问就会让 redis 服务器触发删除 key 的操作, 同时再返回一个 nil

举个例子 : 我小学的时候, 我去买方便面, 我想吃的那种方便面, 就只剩下一包了~~, 正要付钱的时候, 我就看了一眼方便面的生产日期, 发现过期了!! 老板就说得了, 这个面我不卖了~~

虽然有了上述两种策略结合, 整体的效果还是一般~~~

3. 补充

redis 为了对上述进行补充, 还提供了一系列的 内存淘汰策略~~

定时器

        在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临定时删除时,对key进行删除

优点:

        保证内存被尽快释放

缺点

        若过期 key 很多,删除这些 key 会占用很多的 CPU 时间,在 CPU 时间紧张的情况下, CPU不能把所有的时间用来做要紧的事儿,还要去花时间删除这些 key

        定时器的创建耗时,若为每一个设置过期时间的 key 创建一个定时器(将会有大量的定时器产生),性能影响严重

普通的定时器不能解决这个问题我们可以优化定时器

如果有多个 key 过期, 也可以同过一个定时器来高效/节省 cpu 的前提下来处理多个 key (基于优先级队列或者时间轮的都可以实现比较高级的定时器)

但是 redis 是没有采取 这种定时器的方式的.

很难考证为啥.

个人的猜测:基于定时器实现,势必就要引I入多线程了.redis早期版本就是奠定了单线程的基调~引入多线程就打破了作者的初衷~~

4. 定时器

在某个时间达到之后, 执行指定的任务

4.1 基于优先级队列/堆

在 redis 过期的 key 场景中, 我们就可以定义 ""过期时间越早, 就是优先级越高"

现在假设有很多 key 设置了过期时间, 就可以把这些 key 加入到一个优先级队列中, 指定优先级规则是过期时间早的, 先出队列 (队首元素)

此时, 定时器中只要分配一个线程, 让这个线程取检查队首元素, 看是否过期即可! 如果队首元素还没有过期, 后续的元素一定没有过期 (此时扫描线程不需要遍历所有 key 只盯住这一个队首元素即可)

另外在扫描线程检查队首元素过期时间的时候, 也不能检查的太频繁~~

此时做法就是可以根据当前时刻和队首元素的过期时间, 设置一个等待~~, 当时间差不多到了,系统再唤醒这个线程.

此时扫描线程不需要高频扫描队首元素, 把cpu的开销也节省下来了.

在有新任务添加的时候, 唤醒一下刚才的线程, 重新检查一下队首元素, 再根据时间差距重新调整阻塞时间即可.

4.2 基于时间轮实现的定时器

这种方法实现的定时器, 不一定保证任务准时执行, 但一定会执行 (可能会推迟)

加入任务的时间是 100 ms, 就加入 1 这个格子后面的链表中, 200 ms 就加入 2 这个格子后面的链表, 1500 ms 的任务, 就会 轮一圈回来, 加入 1 这个格子后面的链表, 101 ms 的话会加入 1 这个格子.

还会有一个疑问, 就是但我们的指针指向 1 这个格子, 时间是 100 ms, 然后遍历后面的链表, 遍历到 101 ms 的任务的时候, 时间还没有到, 就会继续向后面遍历, 直到链表遍历结束, 然后这个 101 ms 的任务虽然在开始遍历的时候没有到时间, 但结束的时候已经到时间了, 这个时候这个任务就会推迟执行 (等下一圈遍历到这个节点的时候在执行任务)

还有每当指针指向间隔的时候, 都是固定的一个线程去遍历和完成任务, 当时间已经到了指针指向下一个任务的时候,  这个线程是排队一个一个执行任务和遍历链表还是直接从下一个链表开始遍历任务要看具体的设计 (这个里面到了时间的任务, 要么没有遍历到, 要么就会被执行)

http://www.dtcms.com/a/306532.html

相关文章:

  • 基于逻辑回归、随机森林、梯度提升树、XGBoost的广告点击预测模型的研究实现
  • 超宽带测距+测角+无线通信一体化跟随模组:机械狗、无人车、无人机等跟随
  • Dify-15: 开发指南
  • DIY循迹模块多路改造指南
  • 【WRF-Chem第三期】输入数据概览
  • 随笔之TDengine基准测试示例
  • LeetCode 25:K 个一组翻转链表
  • MCU中的CAN总线是什么?
  • WebRTC核心组件技术解析:架构、作用与协同机制
  • 一文掌握最新版本Monocle3单细胞轨迹(拟时序)分析
  • 如何将JPG、PNG、GIF图像转换成PDF、SVG、EPS矢量图像
  • Rust基础[part9]_返回值和错误处理、模块化
  • [特殊字符] 征服CPU的艺术:Rust多进程编程实战指南
  • Cortex-M处理器的优势?
  • STM32CubeIDE新建项目过程记录备忘(二)
  • FFmpeg:因码流采集与封装不同步导致录制出来的MP4文件会出现黑屏、绿屏的问题
  • Zynq SoC 中断控制系统设计与实现:基于 GPIO 的中断驱动开发
  • LocalDateTime vs Instant vs ZonedDateTime:到底该用哪个?
  • .net6的webapi项目统一封装返回值
  • 剧本杀系统 App 开发:科技赋能,重塑剧本杀游戏体验
  • 光伏气象监测系统:当阳光遇见科技
  • Javascript 基础总结
  • 做题笔记:某大讯飞真题28道
  • 浅拷贝和深拷贝
  • uni-app,uni.navigateTo
  • 【LangChain4j 详解】Java生态大语言模型框架设计哲学与架构原理
  • Node.js以及异步编程
  • vue模块化导入
  • 网络安全学习第16集(cdn知识点)
  • Android调用python库和方法的实现