如何制作一个php网站源码,请人制作软件的网站,万网域名管理登录,宠物网站模板Redis 的过期策略以及内存淘汰机制详解1. Redis 的过期策略1.1 如何设置 key 的过期时间#xff1f;1.2 key 设置且到了过期时间后#xff0c;该 key 保存的数据还占据内存么#xff1f;1.3 Redis 如何删除过期的数据1.3.1 定期删除1.3.2 惰性删除2. Redis 的内存淘汰机制2.…
Redis 的过期策略以及内存淘汰机制详解1. Redis 的过期策略1.1 如何设置 key 的过期时间1.2 key 设置且到了过期时间后该 key 保存的数据还占据内存么1.3 Redis 如何删除过期的数据1.3.1 定期删除1.3.2 惰性删除2. Redis 的内存淘汰机制2.1 Redis 最大内存淘汰机制有哪些2.2 如何设置 Redis 最大内存2.3 如何设置 Redis 内存淘汰机制3. LRU 和 LFU 算法3.1 概念3.1.1 LRULeast Recently Used算法3.1.2 LFULeast Frequently Used算法3.2 区别3.3 实现3.3.1 LRU3.3.2 LFU4. 其他场景对过期 key 的处理4.1 快照生成 RDB 文件时4.2 服务重启载入 RDB 文件时4.3 AOF 文件写入时4.4 重写 AOF 文件时4.5 主从同步时1. Redis 的过期策略
Redis 在存储数据时如果指定了过期时间缓存数据到了过期时间就会失效那么 Redis 是如何处理这些失效的缓存数据呢这就用到了 Redis 的过期策略 - 定期删除 惰性删除。下面我们带着问题一起来学习 Redis 的过期策略。
1.1 如何设置 key 的过期时间
Redis 在指定 expire 时间后自动移除给定的键
设置 key 秒级精度的过期时间
EXPIRE key seconds
# 设置 key 毫秒级精度的过期时间
PEXPIRE key millisecondsRedis 在指定 unix 时间来临之后自动移除给定的键
设置 key 秒级精度的过期时间戳(unix timestamp)
EXPIREAT key seconds_timestamp
# 设置 key 毫秒级精度的过期时间戳(unix timestamp) 以毫秒计
PEXPIREAT key milliseconds_timestamp1.2 key 设置且到了过期时间后该 key 保存的数据还占据内存么
当 key 过期后该 key 保存的数据还是会占据内存的。
因为每当我们设置一个 key 的过期时间时Redis 会将该键带上过期时间存放到一个过期字典中。当 key 过期后如果没有触发 Redis 的删除策略的话过期后的数据依然会保存在内存中的这时候即便这个 key 已经过期我们还是能够获取到这个 key 的数据。
1.3 Redis 如何删除过期的数据
Redis 使用“定期删除 惰性删除” 策略删除过期数据。
1.3.1 定期删除
Redis 默认每隔 100ms 就随机抽取部分设置了过期时间的 key检测这些 key 是否过期如果过期了就将其删除。
100ms 是怎么来的
定期任务是 Redis 服务正常运行的保障它的执行频率由 hz 参数的值指定默认为10即每秒执行10次。
hz 105.0 之前的 Redis 版本hz 参数一旦设定之后就是固定的了。hz 默认是 10。这也是官方建议的配置。如果改大表示在 Redis 空闲时会用更多的 CPU 去执行这些任务。官方并不建议这样做。但是如果连接数特别多在这种情况下应该给与更多的 CPU 时间执行后台任务。
Redis 5.0之后有了 dynamic-hz 参数默认就是打开。当连接数很多时自动加倍 hz以便处理更多的连接。
dynamic-hz yes为什么是随机抽取部分 key而不是全部 key
因为如果 Redis 里面有大量 key 都设置了过期时间全部都去检测一遍的话 CPU 负载就会很高会浪费大量的时间在检测上面甚至直接导致 Redis 挂掉。所有只会抽取一部分而不会全部检查。
随机抽取部分检测部分是多少是由 redis.conf 文件中的 maxmemory-samples 属性决定的默认为 5。
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
#
# maxmemory-samples 5正因为定期删除只是随机抽取部分 key 来检测这样的话就会出现大量已经过期的 key 并没有被删除这就是为什么有时候大量的 key 明明已经过了失效时间但是 Redis 的内存还是被大量占用的原因 为了解决这个问题Redis 又引入了惰性删除策略。
1.3.2 惰性删除
惰性删除不是去主动删除而是在你要获取某个 key 的时候Redis 会先去检测一下这个 key 是否已经过期如果没有过期则返回给你如果已经过期了那么 Redis 会删除这个 key不会返回给你。
“定期删除 惰性删除” 就能保证过期的 key 最终一定会被删掉 但是只能保证最终一定会被删除要是定期删除遗漏的大量过期 key我们在很长的一段时间内也没有再访问这些 key那么这些过期 key 不就一直会存在于内存中吗不就会一直占着我们的内存吗这样不还是会导致 Redis 内存耗尽吗由于存在这样的问题所以 Redis 又引入了内存淘汰机制来解决。
2. Redis 的内存淘汰机制
2.1 Redis 最大内存淘汰机制有哪些 volatile-lru当内存不足执行写入操作时在设置了过期时间的键空间中移除最近最少最长时间使用的 key。 allkeys-lru当内存不足执行写入操作时在整个键空间中移除最近最少最长时间使用的 key。这个是最常用的 volatile-lfu当内存不足执行写入操作时在设置了过期时间的键空间中移除最不经常最少次使用的key。 allkeys-lfu当内存不足执行写入操作时在整个键空间中移除最不经常最少次使用的key。 volatile-random - 当内存不足执行写入操作时在设置了过期时间的键空间中随机移除某个 key。 allkeys-random - 当内存不足执行写入操作时在整个键空间中随机移除某个 key。 volatile-ttl - 当内存不足执行写入操作时在设置了过期时间的键空间中优先移除过期时间最早剩余存活时间最短的 key。 noeviction不删除任何 key, 只是在内存不足写操作时返回一个错误。默认选项一般不会选用
2.2 如何设置 Redis 最大内存
redis.conf 配置文件中的 maxmemory 属性限定了 Redis 最大内存使用量当占用内存大于 maxmemory 的配置值时会执行内存淘汰机制。
# maxmemory bytes当达到设置的内存使用限制时Redis 将根据选择的内存淘汰机制maxmemory-policy删除 key。
2.3 如何设置 Redis 内存淘汰机制
内存淘汰机制由 redis.conf 配置文件中的 maxmemory-policy 属性设置没有配置时默认为 noeviction不删除任何 策略
# maxmemory-policy noeviction3. LRU 和 LFU 算法
3.1 概念
3.1.1 LRULeast Recently Used算法
百度百科
LRU最近最少使用淘汰算法Least Recently Used。LRU 是淘汰最近最久未使用的数据。
3.1.2 LFULeast Frequently Used算法
百度百科
LFU最不经常使用淘汰算法Least Frequently Used。LFU是淘汰一段时间内使用次数最少的数据。
3.2 区别
LRU 关键是看最后一次被使用到发生替换的时间长短时间越长就会被淘汰而 LFU 关键是看一定时间段内被使用的频率次数使用频率越低就会被淘汰。
LRU 算法适合较大的文件比如游戏客户端最近加载的地图文件
LFU 算法适合较小的文件和教零碎的文件比如系统文件、应用程序文件。
LRU 消耗 CPU 资源较少LFU 消耗 CPU 资源较多。
3.3 实现
3.3.1 LRU
这里用 leetcode 中一道面试题LRU 缓存 来设计和构建一个 “最近最少使用” 缓存。 关键实现 LRUCache(int capacity)容量大于容量选择最久未使用资源淘汰。 get(key)如果密钥 (key) 存在于缓存中则获取密钥的值总是正数否则返回 -1。 put(key, value)如果密钥不存在则写入其数据值。当缓存容量达到上限时它应该在写入新数据之前删除最近最少使用的数据值从而为新的数据值留出空间。
public class LRUCache {private class Node{Node prev;Node next;int key;int value;public Node(int key, int value) {this.key key;this.value value;this.prev null;this.next null;}}private int capacity;private HashMapInteger, Node hs new HashMapInteger, Node();private Node head new Node(-1, -1);private Node tail new Node(-1, -1);// param capacity, an integerpublic LRUCache(int capacity) {// write your code herethis.capacity capacity;tail.prev head;head.next tail;}// return an integerpublic int get(int key) {// write your code hereif( !hs.containsKey(key)) {return -1;}// remove currentNode current hs.get(key);current.prev.next current.next;current.next.prev current.prev;// move current to tailmove_to_tail(current);return hs.get(key).value;}// param key, an integer// param value, an integer// return nothingpublic void set(int key, int value) {// write your code hereif( get(key) ! -1) {hs.get(key).value value;return;}if (hs.size() capacity) {hs.remove(head.next.key);head.next head.next.next;head.next.prev head;}Node insert new Node(key, value);hs.put(key, insert);move_to_tail(insert);}private void move_to_tail(Node current) {current.prev tail.prev;tail.prev current;current.prev.next current;current.next tail;}public static void main(String[] as) throws Exception {LRUCache cache new LRUCache(3);cache.set(2, 2);cache.set(1, 1);System.out.println(cache.get(2));System.out.println(cache.get(1));System.out.println(cache.get(2));cache.set(3, 3);cache.set(4, 4);System.out.println(cache.get(3));System.out.println(cache.get(2));System.out.println(cache.get(1));System.out.println(cache.get(4));}
}3.3.2 LFU
这里用 leetcode 中一道面试题最不经常使用 LFU 缓存 算法设计并实现数据结构。 关键实现 LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象。 int get(int key) - 如果键 key 存在于缓存中则获取键的值否则返回 -1 。 void put(int key, int value) - 如果键 key 已存在则变更其值如果键不存在请插入键值对。当缓存达到其容量 capacity 时则应该在插入新项之前移除最不经常使用的项。在此问题中当存在平局即两个或更多个键具有相同使用频率时应该去除 最近最久未使用 的键。
public class LFUCache {private static final int DEFAULT_MAX_SIZE 3;private int capacity DEFAULT_MAX_SIZE;//保存缓存的访问频率和时间private final MapInteger, HitRate cache new HashMapInteger, HitRate();//保存缓存的KVprivate final MapInteger, Integer KV new HashMapInteger, Integer();// param capacity, an integerpublic LFUCache(int capacity) {this.capacity capacity;}// param key, an integer// param value, an integer// return nothingpublic void set(int key, int value) {Integer v KV.get(key);if (v null) {if (cache.size() capacity) {Integer k getKickedKey();KV.remove(k);cache.remove(k);}cache.put(key, new HitRate(key, 1, System.nanoTime()));} else { //若是key相同只增加频率,更新时间,并不进行置换HitRate hitRate cache.get(key);hitRate.hitCount 1;hitRate.lastTime System.nanoTime();}KV.put(key, value);}public int get(int key) {Integer v KV.get(key);if (v ! null) {HitRate hitRate cache.get(key);hitRate.hitCount 1;hitRate.lastTime System.nanoTime();return v;}return -1;}// return 要被置换的keyprivate Integer getKickedKey() {HitRate min Collections.min(cache.values());return min.key;}class HitRate implements ComparableHitRate {Integer key;Integer hitCount; // 命中次数Long lastTime; // 上次命中时间public HitRate(Integer key, Integer hitCount, Long lastTime) {this.key key;this.hitCount hitCount;this.lastTime lastTime;}public int compareTo(HitRate o) {int hr hitCount.compareTo(o.hitCount);return hr ! 0 ? hr : lastTime.compareTo(o.lastTime);}}public static void main(String[] as) throws Exception {LFUCache cache new LFUCache(3);cache.set(2, 2);cache.set(1, 1);System.out.println(cache.get(2));System.out.println(cache.get(1));System.out.println(cache.get(2));cache.set(3, 3);cache.set(4, 4);System.out.println(cache.get(3));System.out.println(cache.get(2));System.out.println(cache.get(1));System.out.println(cache.get(4));}
}4. 其他场景对过期 key 的处理
4.1 快照生成 RDB 文件时
过期的 key 不会被保存在 RDB 文件中。
4.2 服务重启载入 RDB 文件时
Master 载入 RDB 时文件中的未过期的键会被正常载入过期键则会被忽略。Slave 载入 RDB 时文件中的所有键都会被载入当主从同步时再和 Master 保持一致。
4.3 AOF 文件写入时
因为 AOF 保存的是执行过的 Redis 命令所以如果 Redis 还没有执行 delAOF 文件中也不会保存 del 操作当过期 key 被删除时DEL 命令也会被同步到 AOF 文件中去。
4.4 重写 AOF 文件时
执行 BGREWRITEAOF 时 过期的 key 不会被记录到 AOF 文件中。
4.5 主从同步时
Master 删除 过期 Key 之后会向所有 Slave 服务器发送一个 DEL 命令Slave 收到通知之后会删除这些 Key。
Slave 在读取过期键时不会做判断删除操作而是继续返回该键对应的值只有当 Master 发送 DEL 通知Slave 才会删除过期键这是统一、中心化的键删除策略保证主从服务器的数据一致性。