7-链表:(下)LRU缓存

1. 原理

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,如果数据最近被访问过,那么将来被访问的几率也更高。

依据此原理,可以设计微服务的二级缓存结构,用于存储经常访问的热数据,实现避免频繁访问后端数据库的相同数据逻辑。当使用该缓存当做二级缓存时,需要保证缓存中数据和后端数据的一致性。

2. 实现

lrucache实现是使用一个链表保存缓存数据,详细算法实现如下:

  1. 新数据插入到链表尾部;

  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表尾部;

  3. 当链表满的时候,将链表头部的数据丢弃。

3. 分析

【命中率】 当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

【复杂度】

O(1)-O(n)

【代价】

命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。

逻辑流程:

github相关资源:

https://github.com/yiekue/lru4go

https://github.com/wonderivan/lrucache

https://github.com/wonderivan/craw

实现一:

package main;
 
import (
    "container/list"
    "errors"
    "sync"
    "fmt"
    "encoding/json"
)
 
//LRU(Least recently used)最近最少使用,算法根据数据的历史访问记录来进行淘汰数据
//核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高"
//常见的实现方式是用一个链表保存数据
//1. 新数据插入到链表头部
//2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
//3. 当链表满的时候,将链表尾部的数据丢弃
 
type cacheItem struct {
    Key string;
    Val interface{};
}
 
type LRU struct {
    //最大存储数量
    maxNum int;
    //当前存储数量
    curNum int;
    //锁,保证数据一致性
    mutex  sync.Mutex;
    //链表
    data   *list.List;
}
 
//添加数据
func (l *LRU) add(key string, value interface{}) error {
    //判断key是否存在
    if e, _ := l.exist(key); e {
        return errors.New(key + "已存在");
    }
    //判断当前存储数量与最大存储数量
    if l.maxNum == l.curNum {
        //链表已满,则删除链表尾部元素
        l.clear();
    }
    l.mutex.Lock();
    l.curNum++;
    //json序列化数据
    data, _ := json.Marshal(cacheItem{key, value});
    //把数据保存到链表头部
    l.data.PushFront(data);
    l.mutex.Unlock();
    return nil;
}
 
//设置数据
func (l *LRU) set(key string, value interface{}) error {
    e, item := l.exist(key);
    if !e {
        return l.add(key, value);
    }
    l.mutex.Lock();
    data, _ := json.Marshal(cacheItem{key, value});
    //设置链表元素数据
    item.Value = data;
    l.mutex.Unlock();
    return nil;
}
 
//清理数据
func (l *LRU) clear() interface{} {
    l.mutex.Lock();
    l.curNum--;
    //删除链表尾部元素
    v := l.data.Remove(l.data.Back());
    l.mutex.Unlock();
    return v;
}
 
//获取数据
func (l *LRU) get(key string) interface{} {
    e, item := l.exist(key);
    if !e {
        return nil;
    }
    l.mutex.Lock();
    //数据被访问,则把元素移动到链表头部
    l.data.MoveToFront(item);
    l.mutex.Unlock();
    var data cacheItem;
    json.Unmarshal(item.Value.([]byte), &data);
    return data.Val;
}
 
//删除数据
func (l *LRU) del(key string) error {
    e, item := l.exist(key);
    if !e {
        return errors.New(key + "不存在");
    }
    l.mutex.Lock();
    l.curNum--;
    //删除链表元素
    l.data.Remove(item);
    l.mutex.Unlock();
    return nil;
}
 
//判断是否存在
func (l *LRU) exist(key string) (bool, *list.Element) {
    var data cacheItem;
    //循环链表,判断key是否存在
    for v := l.data.Front(); v != nil; v = v.Next() {
        json.Unmarshal(v.Value.([]byte), &data);
        if key == data.Key {
            return true, v;
        }
    }
    return false, nil;
}
 
//返回长度
func (l *LRU) len() int {
    return l.curNum;
}
 
//打印链表
func (l *LRU) print() {
    var data cacheItem;
    for v := l.data.Front(); v != nil; v = v.Next() {
        json.Unmarshal(v.Value.([]byte), &data);
        fmt.Println("key:", data.Key, " value:", data.Val);
    }
}
 
//创建一个新的LRU
func LRUNew(maxNum int) *LRU {
    return &LRU{
        maxNum: maxNum,
        curNum: 0,
        mutex:  sync.Mutex{},
        data:   list.New(),
    };
}
 
func main() {
    lru := LRUNew(5);
    lru.add("1111", 1111);
    lru.add("2222", 2222);
    lru.add("3333", 3333);
    lru.add("4444", 4444);
    lru.add("5555", 5555);
    lru.print();
    //get成功后,可以看到3333元素移动到了链表头
    fmt.Println(lru.get("3333"));
    lru.print();
    //再次添加元素,如果超过最大数量,则删除链表尾部元素,将新元素添加到链表头
    lru.add("6666", 6666);
    lru.print();
    lru.del("4444");
    lru.print();
    lru.set("2222", "242424");
    lru.print();
}

转自:https://www.cnblogs.com/jkko123/p/6971133.html

资料链接2:

https://www.jianshu.com/p/2aff66fac48a

发布了127 篇原创文章 · 获赞 24 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/Linzhongyilisha/article/details/99355436