蒟蒻のgolang小窝(LRU)分布式缓存01

–第一版本被吞了 CAO。。。突然页面没了

快速介绍完吧
三种缓存淘汰算法

FIFO(First In First Out)
先进先出,也就是淘汰缓存中最老(最早添加)的记录。FIFO 认为,最早添加的记录,其不再被使用的可能性比刚添加的可能性大。这种算法的实现也非常简单,创建一个队列,新增记录添加到队尾,每次内存不够时,淘汰队首。但是很多场景下,部分记录虽然是最早添加但也最常被访问,而不得不因为呆的时间太长而被淘汰。这类数据会被频繁地添加进缓存,又被淘汰出去,导致缓存命中率降低
LFU(Least Frequently Used)
最少使用,也就是淘汰缓存中访问频率最低的记录。LFU 认为,如果数据过去被访问多次,那么将来被访问的频率也更高。LFU 的实现需要维护一个按照访问次数排序的队列,每次访问,访问次数加1,队列重新排序,淘汰时选择访问次数最少的即可。LFU 算法的命中率是比较高的,但缺点也非常明显,维护每个记录的访问次数,对内存的消耗是很高的;另外,如果数据的访问模式发生变化,LFU 需要较长的时间去适应,也就是说 LFU 算法受历史数据的影响比较大。例如某个数据历史上访问次数奇高,但在某个时间点之后几乎不再被访问,但因为历史访问次数过高,而迟迟不能被淘汰。
LRU(Least Recently Used)
最近最少使用,相对于仅考虑时间因素的 FIFO 和仅考虑访问频率的 LFU,LRU 算法可以认为是相对平衡的一种淘汰算法。LRU 认为,如果数据最近被访问过,那么将来被访问的概率也会更高。LRU 算法的实现非常简单,维护一个队列,如果某条记录被访问了,则移动到队尾,那么队首则是最近最少访问的数据,淘汰该条记录即可。

着重讲LRU,第一版被吞了好憔悴。。

package lru

import (
   "container/list"
   "reflect"
   "testing"
)

/*demo1*/
type Value interface {
   Len() int
}
type Cache struct {
   maxBytes int64
   nbytes int64
   ll *list.List
   cache map[string]*list.Element
   OnEvicted func(key string,value Value)

}
type entry struct {
   key string
   value Value
}

/*demo2*/
//Cache的Constructor
func New(maxBytes int64,onEvicted func(string,Value)) *Cache{
   return &Cache{
      maxBytes:  maxBytes,
      ll:        list.New(),
      cache:     make(map[string]*list.Element),
      OnEvicted: onEvicted,
   }
}
/*demo3*/
//List find function

func (c *Cache) Get(key string) (value Value,ok bool)  {
   if ele,ok:=c.cache[key];
   ok{
      c.ll.MoveToFront(ele)
      kv:=ele.Value.(*entry)
      return kv.value,true
   }
   return
}


/*demo4  delete*/
/*
c.ll.Back() 取到队首节点,从链表中删除。
delete(c.cache, kv.key),从字典中 c.cache 删除该节点的映射关系。
更新当前所用的内存 c.nbytes。
如果回调函数 OnEvicted 不为 nil,则调用回调函数。
2.4 新增/修改
*/
func (c *Cache) RemoveOldest() {
   ele := c.ll.Back()
   if ele != nil {
      c.ll.Remove(ele)
      kv := ele.Value.(*entry)
      delete(c.cache, kv.key)
      c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
      if c.OnEvicted != nil {
         c.OnEvicted(kv.key, kv.value)
      }
   }
}



/*demo5  add*/
/*
如果键存在,则更新对应节点的值,并将该节点移到队尾。
不存在则是新增场景,首先队尾添加新节点 &entry{key, value}, 并字典中添加 key 和节点的映射关系。
更新 c.nbytes,如果超过了设定的最大值 c.maxBytes,则移除最少访问的节点。
最后,为了方便测试,我们实现 Len() 用来获取添加了多少条数据。
*/
func (c *Cache) Add(key string, value Value) {
   if ele, ok := c.cache[key]; ok {
      c.ll.MoveToFront(ele)
      kv := ele.Value.(*entry)
      c.nbytes += int64(value.Len()) - int64(kv.value.Len())
      kv.value = value
   } else {
      ele := c.ll.PushFront(&entry{key, value})
      c.cache[key] = ele
      c.nbytes += int64(len(key)) + int64(value.Len())
   }
   for c.maxBytes != 0 && c.maxBytes < c.nbytes {
      c.RemoveOldest()
   }
}


/*demo6*/
func (c *Cache) Len() int {
   return c.ll.Len()
}


/*demo7*/
type String string

func (d String) Len() int {
   return len(d)
}

func TestGet(t *testing.T) {
   lru := New(int64(0), nil)
   lru.Add("key1", String("1234"))
   if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
      t.Fatalf("cache hit key1=1234 failed")
   }
   if _, ok := lru.Get("key2"); ok {
      t.Fatalf("cache miss key2 failed")
   }
}


/*demo8*/
func TestRemoveoldest(t *testing.T) {
   k1, k2, k3 := "key1", "key2", "k3"
   v1, v2, v3 := "value1", "value2", "v3"
   cap := len(k1 + k2 + v1 + v2)
   lru := New(int64(cap), nil)
   lru.Add(k1, String(v1))
   lru.Add(k2, String(v2))
   lru.Add(k3, String(v3))

   if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
      t.Fatalf("Removeoldest key1 failed")
   }
}

/*demo9   回调测试*/
func TestOnEvicted(t *testing.T) {
   keys := make([]string, 0)
   callback := func(key string, value Value) {
      keys = append(keys, key)
   }
   lru := New(int64(10), callback)
   lru.Add("key1", String("123456"))
   lru.Add("k2", String("k2"))
   lru.Add("k3", String("k3"))
   lru.Add("k4", String("k4"))

   expect := []string{"key1", "k2"}

   if !reflect.DeepEqual(expect, keys) {
      t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
   }
}

01 mark~

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值