From c53c30ece9c3d727b138774875b1b734a29a2393 Mon Sep 17 00:00:00 2001 From: Petrichor <390983386@qq.com> Date: Wed, 30 Mar 2022 19:53:09 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Day2=20=E5=8D=95=E6=9C=BA?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ byteview.go | 26 ++++++++++++++ cache.go | 38 ++++++++++++++++++++ geecache.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ geecache_test.go | 60 +++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 byteview.go create mode 100644 cache.go create mode 100644 geecache.go create mode 100644 geecache_test.go diff --git a/.gitignore b/.gitignore index 0a70cd3..53f77fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.draft/ + *.jpg *.png \ No newline at end of file diff --git a/byteview.go b/byteview.go new file mode 100644 index 0000000..0813e65 --- /dev/null +++ b/byteview.go @@ -0,0 +1,26 @@ +package geecache + +type ByteView struct { + b []byte +} + +func (v ByteView) Len() int { + return len(v.b) +} + +// ByteSlice 返回字节数组形式的拷贝 +// +// b 是只读的,返回拷贝防止缓存值被外部程序修改 +func (v ByteView) ByteSlice() []byte { + return cloneBytes(v.b) +} + +func (v ByteView) String() string { + return string(v.b) +} + +func cloneBytes(b []byte) []byte { + c := make([]byte, len(b)) + copy(c, b) + return c +} diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..5952808 --- /dev/null +++ b/cache.go @@ -0,0 +1,38 @@ +package geecache + +import ( + "geecache/lru" + "sync" +) + +type cache struct { + mu sync.Mutex + lru *lru.Cache + cacheBytes int64 +} + +// add +// +// 使用延迟初始化 +func (c *cache) add(key string, value ByteView) { + c.mu.Lock() + defer c.mu.Unlock() + if c.lru == nil { + c.lru = lru.New(c.cacheBytes, nil) + } + c.lru.Add(key, value) +} + +func (c *cache) get(key string) (value ByteView, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + if c.lru == nil { + return + } + + if v, ok := c.lru.Get(key); ok { + return v.(ByteView), ok + } + + return +} diff --git a/geecache.go b/geecache.go new file mode 100644 index 0000000..09058e9 --- /dev/null +++ b/geecache.go @@ -0,0 +1,91 @@ +package geecache + +import ( + "fmt" + "log" + "sync" +) + +// 接口型函数 +// +// 参考 Go 语言标准库 net/http/server.go +type Getter interface { + Get(key string) ([]byte, error) +} + +type GetterFunc func(key string) ([]byte, error) + +func (f GetterFunc) Get(key string) ([]byte, error) { + return f(key) +} + +// Group 缓存命名空间 +// +// getter Getter 缓存未命中时获取源数据的回调 +// +// A Group is a cache namespace and associated data loaded spread over +type Group struct { + name string + getter Getter + mainCache cache +} + +var ( + mu sync.RWMutex + groups = make(map[string]*Group) +) + +func NewGroup(name string, cacheBytes int64, getter Getter) *Group { + if getter == nil { + panic("nil Getter") + } + mu.Lock() + defer mu.Unlock() + g := &Group{ + name: name, + getter: getter, + mainCache: cache{cacheBytes: cacheBytes}, + } + groups[name] = g // 其实只需要对 map 加锁 + return g +} + +// GetGroup returns the named group previously created with NewGroup, or +// nil if there's no such group. +func GetGroup(name string) *Group { + mu.RLock() + g := groups[name] + mu.RUnlock() + return g +} + +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("[GeeCache] hit") + return v, nil + } + + return g.load(key) +} + +func (g *Group) load(key string) (value ByteView, err error) { + return g.getLocally(key) +} + +func (g *Group) getLocally(key string) (ByteView, error) { + bytes, err := g.getter.Get(key) + if err != nil { + return ByteView{}, err + } + value := ByteView{b: cloneBytes(bytes)} + g.populateCache(key, value) + return value, err +} + +func (g *Group) populateCache(key string, value ByteView) { + g.mainCache.add(key, value) +} diff --git a/geecache_test.go b/geecache_test.go new file mode 100644 index 0000000..b34a840 --- /dev/null +++ b/geecache_test.go @@ -0,0 +1,60 @@ +package geecache + +import ( + "fmt" + "log" + "reflect" + "testing" +) + +func TestGetter(t *testing.T) { + var f Getter = GetterFunc(func(key string) ([]byte, error) { + return []byte(key), nil + }) + + expect := []byte("key") + if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) { + t.Fatalf("callback failed") + } +} + +var db = map[string]string{ + "Tom": "630", + "Jack": "589", + "Sam": "567", +} + +// 1、在缓存为空的情况下,能够通过回调函数获取到源数据。 +// 2、在缓存已经存在的情况下,是否直接从缓存中获取, +// 为了实现这一点,使用 loadCounts 统计某个键调用回调函数的次数, +// 如果次数大于1,则表示调用了多次回调函数,没有缓存。 +// go test -run TestGet +func TestGet(t *testing.T) { + loadCounts := make(map[string]int, len(db)) + gee := NewGroup("scores", 2<<10, GetterFunc( + func(key string) ([]byte, error) { + log.Println("[SlowDB] search key", key) + if v, ok := db[key]; ok { + if _, ok := loadCounts[key]; !ok { + loadCounts[key] = 0 + } + loadCounts[key] += 1 + return []byte(v), nil + } + return nil, fmt.Errorf("%s not exist", key) + }, + )) + + for k, v := range db { + if view, err := gee.Get(k); err != nil || view.String() != v { + t.Fatalf("failed to get value of Tom") + } // loaded from callback function + if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 { + t.Fatalf("cache %s miss", k) + } // cache hit + } + + if view, err := gee.Get("unknown"); err == nil { + t.Fatalf("the value of unknown should be empty, but %s got", view) + } +}