默认情况maxmemory配置项并未启用,Redis官方介绍64位操作系统默认无内存限制,32位操作系统默认3GB隐式内存配置,如果maxmemory 为0,代表内存不受限。
因此我们在做缓存架构时,要根据硬件资源+业务需求做合适的maxmemory配置。
3、内存达到maxmemory怎么办
很显然配置了最大内存,当maxmemory达到了最大上限之后Redis不可能不干活了,那么Redis是怎么来处理这个问题的呢?这就是本文的重点,Redis 提供了maxmemory-policy淘汰策略(本文只讲述LRU不涉及LFU,LFU在下一篇文章讲述),对满足条件的key进行删除,辞旧迎新。
maxmemory-policy淘汰策略:
-
**noeviction:**当达到内存限制并且客户端尝试执行可能导致使用更多内存的命令时返回错误,简单来说读操作仍然允许,但是不准写入新的数据,del(删除)请求可以。
-
**allkeys-lru:**从全体key中,通过lru(Least Recently Used - 最近最少使用)算法进行淘汰
-
**allkeys-random:**从全体key中,随机进行淘汰
-
**volatile-lru:**从设置了过期时间的全部key中,通过lru(Least Recently Used - 最近最少使用)算法进行淘汰,这样可以保证未设置过期时间需要被持久化的数据,不会被选中淘汰
-
**volatile-random:**从设置了过期时间的全部key中,随机进行淘汰
-
**volatile-ttl:**从设置了过期时间的全部key中,通过比较key的剩余过期时间TTL的值,TTL越小越先被淘汰
还有volatile-lfu/allkeys-lfu这个留到下文一起探讨,两个算法不一样!
random随机淘汰只需要随机取一些key进行删除,释放内存空间即可;ttl过期时间小先淘汰也可以通过比较ttl的大小,将ttl值小的key进行删除,释放内存空间即可。
那么LRU是怎么实现的呢?Redis又是如何知道哪个key最近被使用了,哪个key最近没有被使用呢?
4、LRU算法实现
我们先用Java的容器实现一个简单的LRU算法,我们使用ConcurrentHashMap做key-value结果存储元素的映射关系,使用ConcurrentLinkedDeque来维持key的访问顺序。
LRU实现代码:
package com.lizba.redis.lru;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
/**
*
* LRU简单实现
*
* @Author: Liziba
* @Date: 2021/9/17 23:47
*/
public class SimpleLru {
/** 数据缓存 */
private ConcurrentHashMap<String, Object> cacheData;
/** 访问顺序记录 */
private ConcurrentLinkedDeque sequence;
/** 缓存容量 */
private int capacity;
public SimpleLru(int capacity) {
this.capacity = capacity;
cacheData = new ConcurrentHashMap(capacity);
sequence = new ConcurrentLinkedDeque();
}
/**
* 设置值
* @param key
* @param value
* @return
*/
public Object setValue(String key, Object value) {
// 判断是否需要进行LRU淘汰
this.maxMemoryHandle();
// 包含则移除元素,新访问的元素一直保存在队列最前面
if (sequence.contains(key)) {
sequence.remove();
}
sequence.addFirst(key);
cacheData.put(key, value);
return value;
}
/**
* 达到最大内存,淘汰最近最少使用的key
*/
private void maxMemoryHandle() {
while (sequence.size() >= capacity) {
String lruKey = sequence.removeLast();
cacheData.remove(lruKey);
System.out.println("key: " + lruKey + “被淘汰!”);
}
}
/**
* 获取访问LRU顺序
* @return
*/
public List getAll() {
return Arrays.asList(sequence.toArray(new String[] {}));
}
}
测试代码:
package com.lizba.redis.lru;
/**
*
* 测试最近最少使用
*
* @Author: Liziba
* @Date: 2021/9/18 0:00
*/
public class TestSimpleLru {
public static void main(String[] args) {
SimpleLru lru = new SimpleLru(8);
for (int i = 0; i < 10; i++) {
lru.setValue(i+“”, i);
}
System.out.println(lru.getAll());
}
}
测试结果:
从上数的测试结果可以看出,先加入的key0,key1被淘汰了,最后加入的key也是最新的key保存在sequence的队头。
通过这种方案,可以很简单的实现LRU算法;但缺点也十分明显,方案需要使用额外的数据结构来保存key的访问顺序,这样会使Redis内存消耗增加,本身用来优化内存的方案,却要消耗不少内存,显然是不行的。
5、Redis的近似LRU
最后
我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。
其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。
不断奔跑,你就知道学习的意义所在!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!