-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
.draft/ | ||
|
||
*.jpg | ||
*.png |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |