六、Redis内存
1、内存配置
(1)、内存设置
①、如果不设置内存大小或者设置内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存。
②、Redis一般推荐设置内存为最大物理内存的四分之三。
(2)、修改文件配置
修改maxmemory大小,单位字节
(3)、通过命令修改
#修改内存大小
config set maxmemory 1024 # 设置内存大小为1KB,成功返回OK
config get maxmemory # 获取内存大小
info memory # 查看内存使用情况
(4)、OOM
#修改内存大小
config set maxmemory 1 # 设置内存大小为1Byte,成功返回OK
set k1 v1 # 添加k1,v1
(error) OOM command not allowed when used memory > 'maxmemory' # 报错:提示OOM,内存不够
2、淘汰策略
(1)、过期键删除策略
①、定时删除
数据到了过期时间会立即删除,Redis不可能时刻遍历所有被设置了过期时间的key,来查看该key是否到达过期时间,然后进行删除操作。立即删除能保证内存中数据最大的准确性,但这样对CPU不友好,用CPU性能换取存储空间(拿时间换空间)。
②、惰性删除
数据到了过期时间,不作处理,等下次访问该数据时,如果未过期,返回数据;发现已过期,则删除,返回不存在。如果一个键已过期,而这个键又保存在Redis中,那么它所占用的内存就不会释放。对内存不友好,用存储空间获取CPU性能(拿空间换时间)。
③、定期删除
定期删除策略是以上两种策略的折中,就是每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。
该策略难点是如果执行的时间太长,或者执行太频繁,定期删除策略就会退化成定时删除。如果执行太少,或者执行时间太短就会退化成惰性删除策略。所以执行时间和执行频率需要合理设置。
(2)、缓存淘汰策略
针对过期键删除策略的弊端,Redis提供了缓存淘汰策略,当Redis缓存达到maxmemory时,会触发缓存淘汰策略。
LRU算法:Least Recently Used 最近最少使用。LFU算法:Least Frequently Uesd 最少使用频率。
①、noeviction:不会驱逐任何key,Redis默认的淘汰策略,内存满后报OOM。
②、allkeys-lru:对所有key使用LRU(Least Recently Used)算法进行删除。
③、volatile-lru:对所有设置了过期时间的key使用LRU算法进行删除。
④、allkeys-random:对所有key随机删除。
⑤、volatile-random:对所有设置了过期时间的key随机删除。
⑥、volatile-ttl:删除马上要过期的key。
⑦、allkeys-lfu:对所有key使用LFU算法进行删除。
⑧、volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除。
⑨、淘汰策略设置
生产中多数使用allkeys-lru淘汰策略。
a、修改文件配置
b、通过命令修改
config set maxmemory- policy allkeys- lru # 设置缓存淘汰策略为allkeys- lru,设置成功返回OK
config get maxmemory- policy # 获取缓存淘汰策略配置
(3)、LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的数据予以淘汰。
①、利用LinkedHashMap实现LRU算法
写操作 + 读操作时间复杂都为O(1)。
public class LRUCacheDemo<K,V> extends LinkedHashMap<K,V> {
private int capacity;
public LRUCacheDemo(int capacity){
//accessOrder设置为false按照插入顺序排序(默认);为true按照访问顺序排序;
super(capacity, 0.75f, true);
this.capacity = capacity;
}
/**
* 如果HashMap里的元素个数大于初始化容量capacity,则移除最近最少使用的元素,即LinkedHashMap的双向链表中最前面的元素
* 重写LinkedHashMap的removeEldestEntry方法,因为LinkedHashMap是protected,所以此处不能大于protected
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest){
return super.size() > capacity;
}
public static void main(String[] args) {
LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
lruCacheDemo.put(1, "a");
lruCacheDemo.put(2, "b");
lruCacheDemo.put(3, "c");
//输出:[1, 2, 3]
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(2, "b");
//输出:[1, 3, 2]
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(4, "e");
//输出:[3, 2, 4]
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.get(3);
//输出:[2, 4, 3]
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(5, "f");
//输出:[4, 3, 5]
System.out.println(lruCacheDemo.keySet());
}
}
②、手写实现LRU算法
public class LRUCacheDemo {
//Map负责查找,需要构建一个虚拟双向链表,它里面放的是一个个Node节点,作为数据载体。
/**
* 1、构建一个Node节点,作为数据载体
*/
class Node<K,V> {
K key;
V value;
Node<K,V> prev;
Node<K,V> next;
public Node(){
this.prev = this.next = null;
}
public Node(K key, V value){
this.key = key;
this.value = value;
this.prev = this.next = null;
}
}
/**
* 2、构建一个虚拟的双向链表,里面放置的是自定义的Node节点
*/
class DoubleLinkedList<K,V>{
Node<K,V> head;
Node<K,V> tail;
/**
* 双向链表的构造方法
*/
public DoubleLinkedList(){
head = new Node<>();
tail = new Node<>();
head.next = tail;
tail.prev = head;
}
private void addFirst(Node<K,V> node){
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void addLast(Node<K,V> node){
node.prev = tail.prev;
node.next = tail;
tail.prev.next = node;
tail.prev = node;
}
private void removeNode(Node<K,V> node){
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = null;
node.next = null;
}
private void removeFirst(){
Node<K,V> node = head.next;
head.next = node.next;
node.next.prev = head;
node.prev = null;
node.next = null;
}
private Node getFirst(){
return head.next;
}
private Node getLast(){
return tail.prev;
}
}
private int capacity;
Map<Integer, Node<Integer, Integer>> map;
DoubleLinkedList<Integer, Integer> doubleLinkedList;
public LRUCacheDemo(int capacity){
this.capacity = capacity;
map = new HashMap<>();
doubleLinkedList = new DoubleLinkedList<>();
}
public Integer get(int key){
if(!map.containsKey(key)){
return -1;
}
Node<Integer, Integer> node = map.get(key);
doubleLinkedList.removeNode(node);
doubleLinkedList.addLast(node);
return node.value;
}
public void put(int key, int value){
if(map.containsKey(key)){
Node<Integer, Integer> updateNode = map.get(key);
updateNode.value = value;
map.put(key, updateNode);
doubleLinkedList.removeNode(updateNode);
doubleLinkedList.addLast(updateNode);
}else{
if(map.size() == capacity){
Node<Integer, Integer> removeNode = doubleLinkedList.getFirst();
map.remove(removeNode.key, removeNode);
doubleLinkedList.removeNode(removeNode);
}
Node<Integer,Integer> addNode = new Node<>(key, value);
map.put(key, addNode);
doubleLinkedList.addLast(addNode);
}
}
public static void main(String[] args) {
LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
lruCacheDemo.put(1, 1);
lruCacheDemo.put(2, 2);
lruCacheDemo.put(3, 3);
//输出:[1, 2, 3]
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.get(1);
lruCacheDemo.put(5, 5);
//输出:[1, 3, 5] 不知道为啥map的顺序错误,但是doubleLinkedList的顺序是正确的
System.out.println(lruCacheDemo.map.keySet());
}
}