网站开发详细流程图,做qa和helpful的网站,众包 wordpress,借助网络1 介绍
Redis无论是惰性删除还是定期删除#xff0c;都可能存在删除不尽的情况#xff0c;无法删除完全#xff0c;比如每次删除完过期的 key 还是超过 25%#xff0c;且这些 key 再也不会被客户端访问。 这样的话#xff0c;定期删除和堕性删除可能都彻底的清理掉。如果…1 介绍
Redis无论是惰性删除还是定期删除都可能存在删除不尽的情况无法删除完全比如每次删除完过期的 key 还是超过 25%且这些 key 再也不会被客户端访问。 这样的话定期删除和堕性删除可能都彻底的清理掉。如果这种情况长时间持续下去可能会导致内存耗尽所以Redis必须有一个完善的内存淘汰机制来保障。这就是我们这一篇的重点Redis内存自动淘汰机制。
2 Redis内存淘汰策略
在 redis 中总共由8种淘汰策略默认的淘汰策略是 noeviction。
noeviction不淘汰策略(默认)淘汰数据策略设置过期时间的淘汰策略valatile-random随机淘汰算法volatile-ttl淘汰失效时间最短的keyvolatile-lru删除最近最少使用的keyvolatile-lfu删除访问次数最少的key所有数据的淘汰策略allkeys-lru删除最近最少使用的key全部allkeys-lfu删除访问次数最少的key全部allkey-random随机淘汰算法全部
2.1 设置过期时间的淘汰策略
volatile-ttl、volatile-random、volatile-lru、volatile-lfu 这4种策略淘汰的数据范围为设置了过期时间的数据。
2.2 所有 key 的淘汰策略
allkeys-lru、allkeys-random、allkeys-lfu 这3种淘汰策略无论是否设置了过期时间内存不足时都会进行淘汰。 也就是说无论它的过期时间到没到都有可能被删除。
3 LRU淘汰策略执行过程
这边以LRU算法为例子讲解它的全称是 Least Rencently Used即将最近最久未使用的算法进行数据淘汰。 我们这边以图例来讲解整个过程如下
首先设置一个淘汰池一个链表假设默认大小是16里面的数据采用末尾淘汰制。如图中 MRU表示链表的表头代表着最近最常被访问的数据LRU表示链表的表尾代表最近最不常使用的数据。如果淘汰池中的数据被访问则会被移动到 MRU 端其他位置的数据则相应往后移动一位每次指令操作的时候自旋会判断当前内存是否满足指令所需要的内存如果当前内存不能满足会从淘汰池中的尾部拿取一个最适合淘汰的数据 取样模式配置 maxmemory-samples属性从Redis中获取随机的取样数据避免一次性读取All Key性能慢在取样的数据中根据淘汰算法 找到最适合淘汰的数据将需要淘汰的数据从Redis删除并且从淘汰池移除 这边注意LRU 更新和新增数据都发生在链表首删除数据都发生在链表尾。 水果 Orange 跟 Pitaya 被访问被移动到MRU端新增的Mango也被插入到MRU端。而最末端的Olive则被删除。
4 算法实现
以下是使用Go语言实现Redis LRU淘汰过程的示例代码代码注释很清楚
package main import ( container/list fmt time
) type Redis struct { data map[string]*list.Element // 存储缓存项的键和值以及它们在LRU链表中的位置 lru *list.List // LRU链表
} type cacheItem struct { key string value string // 记录该缓存项最后一次被访问的时间 lastAccess time.Time
} func NewRedis() *Redis { return Redis{ data: make(map[string]*list.Element), lru: list.New(), }
} func (r *Redis) Get(key string) (string, bool) { // 从LRU链表中查找缓存项 if elem, ok : r.data[key]; ok { // 将该缓存项移动到链表头部表示最近被访问过 r.lru.MoveToFront(elem) // 更新缓存项的最后访问时间 item : elem.Value.(*cacheItem) item.lastAccess time.Now() return item.value, true } return , false
} func (r *Redis) Set(key string, value string) { // 从LRU链表中查找缓存项 if elem, ok : r.data[key]; ok { // 如果缓存项存在更新其值和最后访问时间并将其移动到链表头部 item : elem.Value.(*cacheItem) item.value value item.lastAccess time.Now() r.lru.MoveToFront(elem) return } // 如果缓存项不存在创建新的缓存项并将其添加到LRU链表头部 item : cacheItem{ key: key, value: value, lastAccess: time.Now(), } elem : r.lru.PushFront(item) r.data[key] elem // 如果缓存空间已满执行LRU淘汰操作 for r.lru.Len() maxItems { // 从链表尾部查找最久未被访问的缓存项 elem : r.lru.Back() item : elem.Value.(*cacheItem) // 如果该缓存项的过期时间已到达则从链表中删除该缓存项 if item.lastAccess.Add(expireTime).Before(time.Now()) { r.lru.Remove(elem) delete(r.data, item.key) } else { // 否则只从链表中删除该缓存项 r.lru.Remove(elem) } }
}在这个示例中我们使用了一个map来存储缓存项的键和值以及它们在LRU链表中的位置。我们使用了一个LRU链表来存储缓存项并按照访问时间将它们排序。在Get方法中我们从LRU链表中查找缓存项并将其移动到链表头部表示最近被访问过。在Set方法中如果缓存项已存在我们更新其值和最后访问时间并将其移动到链表头部如果缓存项不存在我们创建新的缓存项并将其添加到LRU链表头部。如果缓存空间已满我们执行LRU淘汰操作从链表尾部查找最久未被访问的缓存项并从链表中删除它。注意我们还检查了缓存项的过期时间如果该缓存项已过期则也会从链表中删除它。