Redis的高性能网络IO模型
Redis的高性能网络IO模型_Lucifer Zhao的博客-CSDN博客
Redis的持久化机制
Redis的持久化机制_Lucifer Zhao的博客-CSDN博客
Redis的过期策略
为了保证内存利用率,会把设置了过期时间并且过期的数据进行删除
惰性过期(被动过期): 通过key拿取数据时才判断是否过期
定期过期: 定期扫描,删除过期的数据
Redis的内存淘汰策略
数据全放在内存中,内存的容量是有限的,有可能存在一些无效的缓存(就是长时间不使用的缓存),如果没有设置过期时间,这些无效的缓存不光会占用内存,导致物理内存不够,还会降低IO性能,时间复杂度增加,所以针对这些问题,Redis一般通过以下方案解决:
- 设置过期时间
- 增加内存
- 设置内存淘汰策略
Redis安装好之后默认64位,内存没有最大限制,如果是32位系统,默认3GB
当内存达到上限时,设置值会报OOM错误,如下所示:
// 获取最大内存设置,如果返回0则代表没有限制,单位是byte
config get maxmemory
// 通过下面命令设置最大内存,如4G
config set maxmemory 4294967296
当内存达到上限时,可以设置LRU算法来释放无效的key
从设置了过期时间的key里扫描,以hash桶为维度扫描,最多扫20个key,一旦扫到某个hash桶必须扫完这个桶里的所有key,如果删除比例超过10%,会继续扫描并删除过期的key,但是最多扫描16次
Redis中提供了8种内存淘汰策略:
- volatile-lru:针对设置了过期时间的key,使用LRU算法进行淘汰
- allkeys-lru:针对所有key使用LRU算法进行淘汰
- volatile-lfu:针对设置了过期时间的key,使用LFU算法进行淘汰
- allkeys-lfu:针对所有key使用LFU算法进行淘汰
- volatile-random: 从设置了过期时间的key中随机删除
- allkeys-random: 从所有key中随机删除
- volatile-ttl:删除生存时间最近的一个键
- noeviction(默认策略):不删除键,返回错误OOM,只能读取不能写入
通过修改 maxmemory-policy 配置来设置淘汰策略,默认noeviction
核心本质:选择哪些数据进行清理,选择什么方式去清理数据
LRU算法
Least Recently Used(最近很少使用)
LRU算法简单逻辑图
双向链表存放很久没有使用的key,hashmap定位某个节点,用来做索引方便查询;随机采集5个key,将5个key加入到双向链表中,链表不停滚动,根据key的使用频率进行排序,最少使用的key就会被移动到队尾从而移除。
LRU算法简单实现
public class LRUCache {private Node head = new Node();;private Node tail = new Node();;private HashMap<String, Node> nodeHashMap = new HashMap<>();;private final int capacity;//容量public LRUCache(int capacity) {this.capacity = capacity;head.next = tail;tail.prev = head;}public String get(String key) {Node node = nodeHashMap.get(key);if (null == node) {return null;}// 刷新当前key的位置,尾部是将被移除的很少使用的,头部是热点访问数据moveNodeToHead(node);return node.value;}// 在链表中移动node节点,可以先删除节点,然后重新添加private void moveNodeToHead(Node node) {removeNode(node);addNodeToHead(node);}private void removeNode(Node node) {if (node == tail) {tail = tail.prev;tail.next = null;} else if (node == head) {head = head.next;head.prev = null;} else {node.prev.next = node.next;node.next.prev = node.prev;}}private void addNodeToHead(Node node) {head.prev = node;node.next = head;node.prev = null;head = node;}public void put(String key, String value) {Node node = nodeHashMap.get(key);if (null == node) { //如果node不存在,则添加到链表if (nodeHashMap.size() >= capacity) { //如果hashmap已存数据长度大于容量,则移除尾部不常访问数据removeNode(tail); //移除尾部节点nodeHashMap.remove(tail.key); //将key从hashmap中移除}// 添加新的节点到头部node = new Node(key, value);nodeHashMap.put(key, node);addNodeToHead(node);} else { //如果存在,更新数据,并将热点数据移动到链表头部node.value = value;moveNodeToHead(node);}}class Node {private String key;private String value;Node prev;Node next;public Node() {}public Node(String key, String value) {this.key = key;this.value = value;}}
}
Redis中的LRU算法
Redis3.0之后优化了LRU算法,有以下四种情况:
- 回收池已满,新采集的key与回收池中的key相比,空闲时间最小,则不做任何处理
- 回收池未满,新采集的key空闲时间比回收池中部分元素大,则插入到对应位置,后面元素往后移动一个位置
- 回收池未满,新采集的key空闲时间最小,则插入到尾部
- 回收池已满,新采集的key空闲时间比回收池中部分元素大,则移除头部元素,将新采集key插入到对应位置,前面的元素往前移动一个位置
LFU算法
Least Frequently Used(最少使用),与LRU算法相比,引入了访问次数,但是存在时效性问题
上面的都是Hashmap,用来定位节点位置;下面是一个横向+纵向的双向链表,横向是根据key访问次数排序的双向链表,每个横向节点也是一个双向链表,用来保存相同访问次数的元素