1.淘汰算法模块
GOCache(随便取的名字)主要参考GeeCache实现https://geektutu.com/post/geecache.html,同时借鉴了
Redis的部分特性,力求以最简单的方式实现最关键的功能,正在完善中。
|--Cache
|----CacheAlgorithm
|----DoubleList.go //lru算法使用的双向链表
|----ICacheAlgorithm.go //淘汰算法抽象层 后期可实现多个淘汰算法
|----lru.go //淘汰算法实现类
首先,实现最底层的淘汰算法抽象层:
实现ICacheAlgorithm接口需要实现Put、Get、Remove、RemoveOldest(移除最‘老’的元素)四个方法
GOCache\Cache\CacheAlgorithm\ICacheAlgorithm.go
package CacheAlgorithm
type ICacheAlgorithm interface { //淘汰算法抽象层 后期可实现多个淘汰算法
Put(key string, val Value)
Get(key string) (Value, bool)
Remove(key string) error
RemoveOldest() //淘汰
}
//包装string类型实现LEN()便可以作为缓存值value
type String string
func (d String) Len() int { //实现了LEN方法
return len(d)
}
//缓存值value可以是所有实现了Len()方法的type
type Value interface {
Len() int
}
接下来完成一个淘汰算法实现类:LRU算法,如果此处无法理解可以找力扣上的LRU算法题解看看,也可使用其他算法,如LFU、FIFO等。
GOCache\Cache\CacheAlgorithm\lru.go
package CacheAlgorithm
import (
"errors"
)
type LRUCache struct {//去掉了钩子函数,我觉得放到业务层解决比较好
maxBytes uint64//最大可存储的总比特大小
nBytes uint64//现在存储的总比特大小
doubleList *DoubleList//单独实现的双向链表
cacheMap map[string]*ListNode
}
type Entry struct {//包装键值对放到链表节点上
Key string
val Value
}
func NewEntry(key string, val Value) *Entry {
return &Entry{Key: key, val: val}
}
func NewLRUCache(maxBytes uint64) ICacheAlgorithm {
var a ICacheAlgorithm = &LRUCache{
maxBytes: maxBytes,
doubleList: NewDoubleList(),
cacheMap: make(map[string]*ListNode),
}
return a
}
//1.检查是否存在key
func (c *LRUCache) Put(key string, val Value) {
if oldValListNode, ok := c.cacheMap[key]; ok { //插入过(更新)
oldValListNode.Entry.val = val
c.doubleList.MoveToLast(oldValListNode)
} else { //未插入过
dataLen := uint64(len(key) + val.Len())
for c.nBytes+dataLen > c.maxBytes {
c.RemoveOldest()
}
c.nBytes += dataLen
NewNode := NewListNodeWithEntry(NewEntry(key, val))
c.cacheMap[key] = NewNode
c.doubleList.AddToLast(NewNode)
}
}
func (c *LRUCache) Get(key string) (Value, bool) {
if listNode, ok := c.cacheMap[key]; ok { //插入过
c.doubleList.MoveToLast(listNode)
return listNode.Entry.val, true
} else { //未插入过
return nil, false
}
}
func (c *LRUCache) Remove(key string) error { //没有该元素怎么办
if delListNode, ok := c.cacheMap[key]; ok { //插入过
delKey, delLen := c.doubleList.DeleteNode(delListNode)
delete(c.cacheMap, delKey)
c.nBytes -= uint64(len(delKey) + delLen)
return nil
} else { //未插入过
return errors.New("KEY[" + key + "]NOT FOUND!")
}
}
func (c *LRUCache) RemoveOldest() {
delKey, delLen := c.doubleList.DeleteOldest()
delete(c.cacheMap, delKey)
c.nBytes -= uint64(len(delKey) + delLen)
}
DoubleList为单独实现的双向链表,支持LRU算法的使用,也可使用原生库的双向链表。
GOCache\Cache\CacheAlgorithm\DoubleList.go
package CacheAlgorithm
type DoubleList struct {
head *ListNode
tail *ListNode
}
type IDoubleList interface {
DeleteOldest() (string, int) //删除最久未使用
MoveToLast(node *ListNode) //移动到链表尾部 最近使用
AddToLast(node *ListNode)
DeleteNode(node *ListNode) (string, int)
}
type ListNode struct {
next *ListNode
pre *ListNode
Entry *Entry
}
func NewListNode() *ListNode {
return &ListNode{
next: nil,
pre: nil,
Entry: nil,
}
}
func NewListNodeWithEntry(entry *Entry) *ListNode {
return &ListNode{
next: nil,
pre: nil,
Entry: entry,
}
}
func (d *DoubleList) DeleteOldest() (string, int) {
DelNode := d.head.next
d.head.next = d.head.next.next
d.head.next.pre = d.head //取消对头部元素的引用
return DelNode.Entry.Key, DelNode.Entry.val.Len()
}
func (d *DoubleList) DeleteNode(node *ListNode) (string, int) {
node.pre.next = node.next
node.next.pre = node.pre
return node.Entry.Key, node.Entry.val.Len()
}
func (d *DoubleList) MoveToLast(node *ListNode) {
node.pre.next = node.next
node.next.pre = node.pre
d.AddToLast(node)
}
func (d *DoubleList) AddToLast(node *ListNode) {
d.tail.pre.next = node
node.pre = d.tail.pre
node.next = d.tail
d.tail.pre = node
}
func NewDoubleList() *DoubleList {
d := &DoubleList{
head: NewListNode(),
tail: NewListNode(),
}
d.head.next = d.tail
d.tail.pre = d.head
return d
}
测试类(待完善)
func TestLRU(t *testing.T) {
cache := CacheAlgorithm.NewLRUCache(5)
cache.Put("1", CacheAlgorithm.String("a"))
cache.Put("2", CacheAlgorithm.String("b"))
cache.Put("3", CacheAlgorithm.String("c"))
cache.Put("4", CacheAlgorithm.String("d"))
cache.Put("5", CacheAlgorithm.String("e"))
fmt.Println(cache.Get("4"))
fmt.Println(cache.Get("2"))
fmt.Println(cache.Get("3"))
fmt.Println(cache.Get("4"))
}
结果:
d true
<nil> false
<nil> false
d true
2.缓存主模块
上一节中实现了LRU淘汰算法,这里完成淘汰算法的抽象层:
GOCache\Cache\Cache.go
package Cache
import (
"GOCache/Cache/CacheAlgorithm"
"sync"
)
//cache.go的实现非常简单,实例化 lru(或者其他算法),封装 get 和 put 方法,并添加互斥锁 mu。
type Cache struct {
lock sync.Mutex //互斥锁
CacheImpl CacheAlgorithm.ICacheAlgorithm //实现了ICacheAlgorithm的算法,现在只有LRU
algorithm string //使用的底层算法 例如"LRU"
cacheBytes uint64 //最大可存储的比特大小
}
func (c *Cache) newCache(algorithm string) CacheAlgorithm.ICacheAlgorithm {
switch c.algorithm {
case "lru": //执行实例化 可添加其他算法
return CacheAlgorithm.NewLRUCache(c.cacheBytes)
default:
return CacheAlgorithm.NewLRUCache(c.cacheBytes)
}
}
func (c *Cache) put(key string, value ByteView) {
c.lock.Lock()
defer c.lock.Unlock()
if c.CacheImpl == nil { //延迟初始化
c.CacheImpl = c.newCache(c.algorithm)
}
c.CacheImpl.Put(key, value)
}
func (c *Cache) get(key string) (value ByteView, ok bool) {
c.lock.Lock()
defer c.lock.Unlock()
if c.CacheImpl == nil { //延迟初始化
c.CacheImpl = c.newCache(c.algorithm)
}
if v, ok := c.CacheImpl.Get(key); ok {
return v.(ByteView), ok
}
return
}
抽象了一个只读数据结构 ByteView 用来表示缓存值,ByteView是byte数组的包装类,可以存储真实字节,图片等
GOCache\Cache\byteview.go
package Cache
//抽象了一个只读数据结构 ByteView 用来表示缓存值
type ByteView struct {
Bytes []byte //存储真实字节 可存图片等
}
// 实现了Len方法,ByteView是value的实现类,可以作为缓存的val
func (v ByteView) Len() int {
return len(v.Bytes)
}
// ByteSlice 返回一个比特切片的拷贝
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.Bytes)
}
// byte[]转为string的方法
func (v ByteView) String() string {
return string(v.Bytes)
}
func cloneBytes(b []byte) []byte {
c := make([]byte, len(b))
copy(c, b)
return c
}
接下来完成Group结构,类似于一个数据库,目前是一个数据库对应一个cache结构,每个数据库有唯一id,默认实例化了一个id为1的group。
GOCache\Cache\group.go
package Cache
import (
"GOCache/conf"
"fmt"
"log"
"sync"
)
type Group struct { //一个group可类似于一个数据库
GroupID int
mainCache Cache //实现的并发缓存
}
var ( //全局变量
mu sync.RWMutex
groups = make(map[int]*Group)
group1 = NewGroup(0) //实例化一个group全局可用
)
func NewGroup(id int) *Group {
mu.Lock()
defer mu.Unlock()
g := &Group{
GroupID: id,
//getter: getter,
mainCache: Cache{cacheBytes: conf.Config.MaxBytes, algorithm: conf.Config.CacheAlgorithm},
}
groups[id] = g
return g
}
//根据id返回实例指针
func GetGroup(groupID int) *Group {
mu.RLock()
g := groups[groupID]
mu.RUnlock()
return g
}
//包装get方法
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GOCache] hit")
return v, nil
}
return ByteView{}, fmt.Errorf("[" + key + "]key Not Found!")
}
//包装put方法
func (g *Group) Put(key string, value ByteView) {
g.mainCache.put(key, value)
}
至此,GoCache的最核心部分已完成,接下来完成客户端和服务端。