0

0

详解Redis的LRU算法

藏色散人

藏色散人

发布时间:2020-08-27 13:33:39

|

4461人浏览过

|

来源于cnblogs

转载

下面由redis教程栏目给大家详解redis的lru算法,希望对需要的朋友有所帮助!

详解Redis的LRU算法

Redis的LRU算法

LRU算法背后的的思想在计算机科学中无处不在,它与程序的"局部性原理"很相似。在生产环境中,虽然有Redis内存使用告警,但是了解一下Redis的缓存使用策略还是很有好处的。下面是生产环境下Redis使用策略:最大可用内存限制为4GB,采用 allkeys-lru 删除策略。所谓删除策略:当redis使用已经达到了最大内存,比如4GB时,如果这时候再往redis里面添加新的Key,那么Redis将选择一个Key删除。那如何选择合适的Key删除呢?

CONFIG GET maxmemory"maxmemory""4294967296"CONFIG GET maxmemory-policy"maxmemory-policy""allkeys-lru"

在官方文档Using Redis as an LRU cache描述中,提供了好几种删除策略,比如 allkeys-lru、volatile-lru等。在我看来按选择时考虑三个因素:随机、Key最近被访问的时间 、Key的过期时间(TTL)

比如:allkeys-lru,"统计一下所有的"Key历史访问的时间,把"最老"的那个Key移除。注意:我这里加了引号,其实在redis的具体实现中,要统计所有的Key的最近访问时间代价是很大的。想想,如何做到呢?

evict keys by trying to remove the less recently used (LRU) keys first, in order to make space for the new data added.

再比如:allkeys-random,就是随机选择一个Key,将之移除。

evict keys randomly in order to make space for the new data added.

再比如:volatile-lru,它只移除那些使用 expire 命令设置了过期时间的Key,根据LRU算法来移除。

evict keys by trying to remove the less recently used (LRU) keys first, but only among keys that have an expire set, in order to make space for the new data added.

再比如:volatile-ttl,它只移除那些使用 expire 命令设置了过期时间的Key,哪个Key的 存活时间(TTL KEY 越小)越短,就优先移除。

evict keys with an expire set, and try to evict keys with a shorter time to live (TTL) first, in order to make space for the new data added.

volatile 策略(eviction methods) 作用的Key 是那些设置了过期时间的Key。在redisDb结构体中,定义了一个名为 expires 字典(dict)保存所有的那些用expire命令设置了过期时间的key,其中expires字典的键指向redis 数据库键空间(redisServer--->redisDb--->redisObject)中的某个键,而expires字典的值则是这个键的过期时间(long类型整数)。
额外提一下:redis 数据库键空间是指:在结构体redisDb中定义的一个名为"dict",类型为hash字典的一个指针,它用来保存该redis DB中的每一个键对象、以及相应的值对象。

既然有这么多策略,那我用哪个好呢?这就涉及到Redis中的Key的访问模式了(access-pattern),access-pattern与代码业务逻辑相关,比如说符合某种特征的Key经常被访问,而另一些Key却不怎么用到。如果所有的Key都可能机会均等地被我们的应用程序访问,那它的访问模式服从均匀分布;而大部分情况下,访问模式服从幂指分布(power-law distribution),另外Key的访问模式也有可能是随着时间变化的,因此需要一种合适的删除策略能够catch 住 (捕获住)各种情形。而在幂指分布下,LRU是一种很好的策略:

While caches can’t predict the future, they can reason in the following way: keys that are likely to be requested again are keys that were recently requested often. Since usually access patterns don’t change very suddenly, this is an effective strategy.

Redis中LRU策略的实现

最直观的想法:LRU啊,记录下每个key 最近一次的访问时间(比如unix timestamp),unix timestamp最小的Key,就是最近未使用的,把这个Key移除。看下来一个HashMap就能搞定啊。是的,但是首先需要存储每个Key和它的timestamp。其次,还要比较timestamp得出最小值。代价很大,不现实啊。

第二种方法:换个角度,不记录具体的访问时间点(unix timestamp),而是记录idle time:idle time越小,意味着是最近被访问的。

The LRU algorithm evicts the Least Recently Used key, which means the one with the greatest idle time.

比如A、B、C、D四个Key,A每5s访问一次,B每2s访问一次,C和D每10s访问一次。(一个波浪号代表1s),从上图中可看出:A的空闲时间是2s,B的idle time是1s,C的idle time是5s,D刚刚访问了所以idle time是0s

这里,用一个双向链表(linkedlist)把所有的Key链表起来,如果一个Key被访问了,将就这个Key移到链表的表头,而要移除Key时,直接从表尾移除。

It is simple to implement because all we need to do is to track the last time a given key was accessed, or sometimes this is not even needed: we may just have all the objects we want to evict linked in a linked list.

但是在redis中,并没有采用这种方式实现,它嫌LinkedList占用的空间太大了。Redis并不是直接基于字符串、链表、字典等数据结构来实现KV数据库,而是在这些数据结构上创建了一个对象系统Redis Object。在redisObject结构体中定义了一个长度24bit的unsigned类型的字段,用来存储对象最后一次被命令程序访问的时间:

By modifying a bit the Redis Object structure I was able to make 24 bits of space. There was no room for linking the objects in a linked list (fat pointers!)

亿众购物系统
亿众购物系统

一套设计完善、高效的web商城解决方案,独有SQL注入防范、对非法操作者锁定IP及记录功能,完整详细的记录了非法操作情况,管理员可以随时查看网站安全日志以及解除系统自动锁定的IP等前台简介:  1)系统为会员制购物,无限会员级别。  2)会员自动升级、相应级别所享有的折扣不同。  3)产品可在缺货时自动隐藏。  4)自动统计所有分类中商品数量,并在商品分类后面显示。  5)邮件列表功能,可在线订阅

下载

毕竟,并不需要一个完全准确的LRU算法,就算移除了一个最近访问过的Key,影响也不太。

To add another data structure to take this metadata was not an option, however since LRU is itself an approximation of what we want to achieve, what about approximating LRU itself?

最初Redis是这样实现的:

随机选三个Key,把idle time最大的那个Key移除。后来,把3改成可配置的一个参数,默认为N=5:maxmemory-samples 5

when there is to evict a key, select 3 random keys, and evict the one with the highest idle time

就是这么简单,简单得让人不敢相信了,而且十分有效。但它还是有缺点的:每次随机选择的时候,并没有利用历史信息。在每一轮移除(evict)一个Key时,随机从N个里面选一个Key,移除idle time最大的那个Key;下一轮又是随机从N个里面选一个Key...有没有想过:在上一轮移除Key的过程中,其实是知道了N个Key的idle time的情况的,那我能不能在下一轮移除Key时,利用好上一轮知晓的一些信息?

However if you think at this algorithm across its executions, you can see how we are trashing a lot of interesting data. Maybe when sampling the N keys, we encounter a lot of good candidates, but we then just evict the best, and start from scratch again the next cycle.

start from scratch太傻了。于是Redis又做出了改进:采用缓冲池(pooling)

当每一轮移除Key时,拿到了这个N个Key的idle time,如果它的idle time比 pool 里面的 Key的idle time还要大,就把它添加到pool里面去。这样一来,每次移除的Key并不仅仅是随机选择的N个Key里面最大的,而且还是pool里面idle time最大的,并且:pool 里面的Key是经过多轮比较筛选的,它的idle time 在概率上比随机获取的Key的idle time要大,可以这么理解:pool 里面的Key 保留了"历史经验信息"。

Basically when the N keys sampling was performed, it was used to populate a larger pool of keys (just 16 keys by default). This pool has the keys sorted by idle time, so new keys only enter the pool when they have an idle time greater than one key in the pool or when there is empty space in the pool.

采用"pool",把一个全局排序问题 转化成为了 局部的比较问题。(尽管排序本质上也是比较,囧)。要想知道idle time 最大的key,精确的LRU需要对全局的key的idle time排序,然后就能找出idle time最大的key了。但是可以采用一种近似的思想,即随机采样(samping)若干个key,这若干个key就代表着全局的key,把samping得到的key放到pool里面,每次采样之后更新pool,使得pool里面总是保存着随机选择过的key的idle time最大的那些key。需要evict key时,直接从pool里面取出idle time最大的key,将之evict掉。这种思想是很值得借鉴的。

至此,基于LRU的移除策略就分析完了。Redis里面还有一种基于LFU(访问频率)的移除策略,后面有时间再说。

JAVA 里面的LRU实现

JDK中的LinkedHashMap实现了LRU算法,使用如下构造方法,accessOrder 表示"最近最少未使用"的衡量标准。比如accessOrder=true,当执行java.util.LinkedHashMap#get元素时,就表示这个元素最近被访问了。

/**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

再重写:java.util.LinkedHashMap#removeEldestEntry方法即可。

The {@link #removeEldestEntry(Map.Entry)} method may be overridden to impose a policy for removing stale mappings automatically when new mappings are added to the map.

为了保证线程安全,用Collections.synchronizedMap将LinkedHashMap对象包装起来:

Note that this implementation is not synchronized. If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.  This is typically accomplished by synchronizing on some object that naturally encapsulates the map.

实现如下:(org.elasticsearch.transport.TransportService)

final Map<Long, TimeoutInfoHolder> timeoutInfoHandlers =
        Collections.synchronizedMap(new LinkedHashMap<Long, TimeoutInfoHolder>(100, .75F, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > 100;
            }
        });

当容量超过100时,开始执行LRU策略:将最近最少未使用的 TimeoutInfoHolder 对象  evict 掉。

参考链接:

  • Random notes on improving the Redis LRU algorithm
  • Using Redis as an LRU cache

最后,再扯一点与本文不相关的东西:关于数据处理的一些思考:
5G有能力让各种各样的节点都接入网络,就会产生大量的数据,如何高效地处理这些数据、挖掘数据(机器学习、深度学习)背后的隐藏的模式是一件非常有趣的事情,因为这些数据其实是人类行为的客观反映。举个例子,微信运动的计步功能,会让你发现:哎,原来每天我的活动量大概保持在1万步左右。再深入一步,以后各种工业设备、家用电器,都会产生各种各样的数据,这些数据是社会商业活动的体现,也是人类活动的体现。如何合理地利用这些数据呢?现有的存储系统能支撑这些数据的存储吗?有没有一套高效的计算系统(spark or tensorflow...)分析出数据中隐藏的价值?另外,基于这些数据训练出来的算法模型应用之后会不会带来偏见?有没有滥用数据?网上买东西有算法给你推荐,刷朋友圈会给你投针对性的广告,你需要借多少钱,贷多少款也会有模型评估,看新闻、看娱乐八卦也有算法推荐。你每天看到的东西、接触的东西,有很多很多都是这些"数据系统"做的决策,它们真的公平吗?在现实中如果你犯了错,有老师、朋友、家人给你反馈、纠正,你改了,一切还可以从头开始。可在这些数据系统之下呢?有纠错反馈机制吗?会不会刺激人越陷越深?就算你很正直、有良好的信用,算法模型也可能存在概率偏差,这是不是影响人类"公平竞争"的机会?这些都是数据开发人员值得思考的问题。

原文:https://www.cnblogs.com/hapjin/p/10933405.html

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

16

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

23

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

75

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

95

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

218

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

420

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

168

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

222

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

33

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

相关下载

更多

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 7.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号