一、LRU算法介绍
LRU(Least Recently Used)算法是一种常见的页面置换算法,主要用于缓存淘汰策略。其核心思想是基于时间局部性原理:如果数据最近被访问过,那么将来被访问的概率也会更高。因此,LRU算法会优先淘汰最近最少使用的数据。
二、mysql和redis中的使用
ySQL 和 Redis 都采用了 LRU 算法来管理内存中的缓存数据,以提高性能并防止内存溢出。下面是它们如何使用 LRU 算法的:
MySQL 中的 LRU
在 MySQL 中,尤其是在使用 InnoDB 存储引擎时,LRU 算法主要用于管理缓冲池(Buffer Pool)中的数据页。
-
缓冲池: 缓冲池是 InnoDB 用于缓存数据页的内存区域。当查询需要访问某个数据页时,如果该页已经在缓冲池中,则直接读取;否则,需要从磁盘加载该页到缓冲池中。
-
LRU 列表: 缓冲池中的数据页按访问时间组织在一个 LRU 列表中。最新访问的数据页会被移到列表的头部,最久未被访问的数据页位于尾部。
-
淘汰策略: 当缓冲池满了,需要腾出空间以加载新的数据页时,InnoDB 会从 LRU 列表的尾部淘汰最近最少使用的页面。
此外,InnoDB 对传统的 LRU 算法进行了优化,引入了一个叫做“midpoint insertion strategy”的策略,这样能更好地适应访问模式,使得频繁访问的数据页在缓冲池中保留更长时间。
Redis 中的 LRU
Redis 是一个内存数据库,LRU 算法用于管理键值对的内存使用。当 Redis 的内存使用达到配置的最大限制时,LRU 算法决定哪些键值对需要被淘汰。
-
内存淘汰策略: Redis 提供多种内存淘汰策略,其中
volatile-lru
和allkeys-lru
是基于 LRU 算法的:- volatile-lru: 仅对设置了过期时间的键应用 LRU 算法。
- allkeys-lru: 对所有键应用 LRU 算法。
-
近似 LRU 算法: Redis 实现的 LRU 是一种近似 LRU,它并不严格记录每个键的精确访问顺序,而是基于随机采样的方式来近似判断哪些键最近最少使用。这种方法在保证性能的同时,能够有效减少计算资源的消耗。
总结而言,MySQL 和 Redis 都通过 LRU 算法来管理内存中缓存的数据,有效提高了系统的性能并避免了内存溢出
三、leecode代码思路与代码实现
为什么使用map+双向链表
使用 map
(哈希表)和双向链表来实现 LRU 算法的主要原因是为了在 O(1) 时间复杂度内完成缓存的 查找、插入和删除 操作。这种组合数据结构使得 LRU 缓存既高效又便于操作
go代码
package main
import "fmt"
//思路: 实现双向链表的 addToHead 、Move以及ConstructDoublyList
type Node struct {
Key int
Val int
Prev *Node
Next *Node
}
type DoublyList struct {
head *Node
tail *Node
}
func ConstructDoublyList() *DoublyList {
head := &Node{}
tail := &Node{}
head.Next = tail
tail.Prev = head
return &DoublyList{head, tail}
}
func (this *DoublyList) AddToHead(node *Node) {
if node == nil {
return
}
node.Next = this.head.Next
node.Prev = this.head
this.head.Next.Prev = node
this.head.Next = node
}
func (this *DoublyList) Move(node *Node) {
node.Prev.Next = node.Next
node.Next.Prev = node.Prev
}
type LRUCache struct {
doublyList *DoublyList
cache map[int]*Node
capacity int
}
func Constrctor(capacity int) LRUCache {
return LRUCache{
doublyList: ConstructDoublyList(),
cache: map[int]*Node{},
capacity: capacity,
}
}
func (this *LRUCache) Get(key int) int {
var res int
if node, ok := this.cache[key]; ok {
res = node.Val
this.doublyList.Move(node)
this.doublyList.AddToHead(node)
} else {
res = -1
}
return res
}
func (this *LRUCache) Put(key int, value int) {
if node, ok := this.cache[key]; ok {
node.Val = value
} else {
tail := this.doublyList.tail.Prev
if len(this.cache) == this.capacity {
delete(this.cache, tail.Key)
this.doublyList.Move(tail)
}
//新加
newNode := &Node{Key: key, Val: value}
this.doublyList.AddToHead(newNode)
this.cache[key] = newNode
}
}
// 测试
func main() {
lru := Constrctor(2)
fmt.Println(lru.Get(1))
lru.Put(1, 1)
lru.Put(2, 2)
lru.Get(1)
lru.Put(3, 3)
fmt.Println(lru.Get(1))
}