【大话Java面试】-如何通俗易懂的理解Redis的回收算法LRU?

本文介绍了LRU(最近最少使用)算法的基本原理及其应用场景。通过一个业务案例,展示了如何运用LRU算法解决用户活跃度的问题,并提供了两种实现LRU缓存的方式:一种是通过继承LinkedHashMap,另一种则是结合双向链表和HashMap。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如何通俗易懂的理解LRU算法?

1.LRU是什么?

LRU全称Least Recently Used,也就是最近最少使用的意思,是一种内存管理算法,最早应用于Linux操作系统。

LRU算法基于一种假设:长期不被使用的数据,在未来被用到的几率也不大。因此,当数据所占内存达到一定阈值时,我们要移除掉最近最少被使用的数据。

LRU算法应用:可以在内存不够时,从哈希表移除一部分很少访问的用户。

LRU是什么?按照英文的直接原义就是Least Recently Used,最近最久未使用法,它是按照一个非常著名的计算机操作系统基础理论得来的:最近使用的页面数据会在未来一段时期内仍然被使用,已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。基于这个思想,会存在一种缓存淘汰机制,每次从内存中找到最久未使用的数据然后置换出来,从而存入新的数据!它的主要衡量指标是使用的时间,附加指标是使用的次数。在计算机中大量使用了这个机制,它的合理性在于优先筛选热点数据,所谓热点数据,就是最近最多使用的数据!因为,利用LRU我们可以解决很多实际开发中的问题,并且很符合业务场景。

2.LRU的需求

首先考虑这样的一个业务场景,小王在A公司上班,有一天产品提出了一个需求:“咱们系统的用户啊,每天活跃的就那么多,有太多的僵尸用户,根本不登录,你能不能考虑做一个筛选机制把这些用户刨出去,并且给活跃的用户做一个排名,我们可以设计出一些奖励活动,提升咱们的用户粘性,咱们只需要关注那些活跃的用户就行了“”。小王连忙点头,说可以啊,然而心里犯起嘀咕来了:这简单,按照常规思路,给用户添加一个最近活跃时间长度和登录次数,然后按照这两个数据计算他们的活跃度,最后直接排序就行了。嘿嘿,简直完美!不过!用户表字段已经很多了,又要加两个字段,然后还得遍历所有的数据排序?这样查询效率是不是会受影响啊?

3.LRU的实现方式

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存;
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

3.1 直接继承LinkedHashMap来实现

public class Code_LRU {
	class LRUCache extends LinkedHashMap<Integer,Integer>{
		private int capacity;
		public LRUCache(int capacity) {
			super(capacity,0.75F,true);
			this.capacity = capacity;
		}
		
		public int get(int key) {
			return super.getOrDefault(key,-1);
		}
		public void put(int key,int value) {
			super.put(key, value);
		}
		
		// 重写淘汰策略
		protected boolean removeEldestEntry(Map.Entry<Integer, Integer> edlest) {
			return size()>capacity;
		}
	}
}

3.2 用双向链表+hashMap来实现

// 解题思路:双向链表+hashmap来实现
	class LRUCache_2{
		// 双向链表
		class DLinkedNode{
			int key;
			int value;
			DLinkedNode prev;
			DLinkedNode next;
			public DLinkedNode() {
				
			}
			public DLinkedNode(int key,int value) {
				this.key = key;
				this.value = value;
			}
		}
		
		// hashmap
		private HashMap<Integer,DLinkedNode> cache = new HashMap<>();
		// 定义私有变量
		private int size;
		private int capacity;
		private DLinkedNode head,tail;
		
		public LRUCache_2(int capacity) {
			this.size = 0;
			this.capacity = capacity;
			// 生成伪头部和伪尾部结点
			head = new DLinkedNode();
			tail = new DLinkedNode();
			head.next = tail;
			tail.prev = head;
		}
		
		//获得值
		public int get(int key) {
			DLinkedNode node = cache.get(key);
			if(node==null) {
				return -1;
			}else {
				// 如果key存在,哈希表定位 结点移动到头部
				moveToHead(node);
				return node.value;
			}
		}
		
		// 放入值的操作
		public void put(int key,int value) {
			DLinkedNode node = cache.get(key);
			// 如果key不存在
			if(node==null) {
				// 新创建一个结点
				DLinkedNode newNode = new DLinkedNode(key,value);
				// 添加
				cache.put(key,newNode);
				// 添加到头部
				addToHead(newNode);
				++size;
				// 判断容量
				if(size>capacity) {
					// 称号出容量
					DLinkedNode tail = removeTail();
					// 删除hash表中对应的项
					cache.remove(tail.key);
					--size;
				}
				
			}else {
				node.value = value;
				// 移动
				moveToHead(node);
			}
		}
		
		// 添加结点的操作
		private void addToHead(DLinkedNode node) {
			node.prev = head;
			node.next = head.next;
			head.next.prev = node;
			head.next = node;
		}
		
		// 删除结点
		private void removeNode(DLinkedNode node) {
			node.prev.next = node.next;
			node.next.prev = node.prev;
		}
		
		// 移动到头结点 删结点 填充结点
		private void moveToHead(DLinkedNode node) {
			removeNode(node);
			addToHead(node);
		}
		// 删除尾部结点
		private DLinkedNode removeTail() {
			DLinkedNode res = tail.prev;
			removeNode(res);
			return res;
		}
	}

### RedisLRU算法实现 Redis中的LRU(Least Recently Used,最近最少使用)算法是一种缓存淘汰策略,用于在内存达到上限时选择性地删除一些不常用的键值对。为了提高性能和效率,Redis并没有采用严格的LRU算法,而是实现了一种**近似LRU机制**[^2]。 #### 近似LRU机制的核心要点 1. **抽样近似**:Redis不会对所有键进行全量排序,而是通过随机抽取一部分键(默认为5个)来近似判断哪些键是最近最少使用的。这种方式显著降低了计算开销。 2. **LRU时钟**:Redis维护一个全局的LRU时钟(`server.lruclock`),每个键在被访问或修改时都会更新其`lru`字段,记录最后一次访问的时间戳。当需要淘汰数据时,Redis会比较这些时间戳,选择最旧的键进行淘汰。 3. **性能优化**:通过结合抽样和全局LRU时钟,Redis能够在保证较高命中率的同时避免全量扫描带来的性能瓶颈。 #### LRU算法的具体实现 Redis中的LRU算法基于`redisObject`底层数据结构,其中包含一个`lru`字段,用于记录该键最近一次被访问的LRU时钟值[^4]。以下是其实现的关键点: - 每次键被访问或修改时,Redis会更新该键的`lru`字段为当前的全局LRU时钟值。 - 当内存达到`maxmemory`限制时,Redis会根据配置的`maxmemory-policy`策略执行淘汰操作。如果策略为`allkeys-lru`或`volatile-lru`,则会触发LRU淘汰逻辑。 - 在淘汰过程中,Redis会随机抽取一组键,并比较它们的`lru`字段值,选择最久未被访问的键进行删除。 以下是一个简单的伪代码示例,展示如何模拟RedisLRU淘汰过程: ```python import random def lru_eviction(keys, sample_size=5): if len(keys) <= sample_size: return min(keys, key=lambda k: k.lru) # 随机抽取样本 sample = random.sample(keys, sample_size) # 找到样本中最久未访问的键 least_recently_used = min(sample, key=lambda k: k.lru) return least_recently_used ``` ### 面试中常见的Redis LRU相关问题 面试中关于Redis LRU算法的问题通常围绕其实现细节、优化策略以及实际应用场景展开。以下是几个常见的面试问题及解答方向: 1. **Redis是如何实现LRU算法的?** - Redis采用了近似LRU机制,通过随机抽样和LRU时钟结合的方式,避免了全量扫描的高开销[^2]。 2. **Redis中的LRU与严格LRU的区别是什么?** - 严格LRU需要对所有键进行排序,而RedisLRU通过随机抽样近似实现,性能更高但可能不够精确。 3. **Redis的`maxmemory-policy`有哪些选项?** - 常见的淘汰策略包括`noeviction`、`allkeys-lru`、`volatile-lru`等,其中`allkeys-lru`适用于所有键,而`volatile-lru`仅适用于设置了过期时间的键[^3]。 4. **如何优化RedisLRU性能?** - 可以调整`maxmemory-samples`参数,控制每次抽样的键数量,默认为5。适当增加样本数量可以提高淘汰精度,但也可能增加计算开销。 5. **RedisLRU时钟是如何工作的?** - Redis维护了一个全局的LRU时钟(`server.lruclock`),每个键的`lru`字段记录了最后一次访问的时间戳,用于判断键的使用频率[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mind_programmonkey

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值