Skip to content

Commit

Permalink
✨ feat: Day2 单机并发缓存
Browse files Browse the repository at this point in the history
  • Loading branch information
p3ddd committed Mar 30, 2022
1 parent 5e8c73c commit c53c30e
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.draft/

*.jpg
*.png
26 changes: 26 additions & 0 deletions byteview.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
@@ -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
}
91 changes: 91 additions & 0 deletions geecache.go
Original file line number Diff line number Diff line change
@@ -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)
}
60 changes: 60 additions & 0 deletions geecache_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit c53c30e

Please sign in to comment.