[ Github- geecache ](luckly-0/geecache (github.com))
收獲總結:
- 了解接口的使用場景,它和函數之間的差別和優(yōu)略勢
- 測試文件要以_test結尾
- 系統(tǒng)設計要嚴謹,要考慮后期的拓展性和維護 ,比如load函數考慮到了分布式場景
- 數據結構之間的封裝
sync.Mutex 互斥鎖
如果我們要是實現并發(fā)緩存,那么我們要引入sync.Mutex 互斥鎖來保證多個協(xié)程不沖突,確保同一時間只有一個協(xié)程運行,我們在使用的時候使用Lock() 和unLock()來實現阻塞
實現并發(fā)讀寫
實現ByteView表示緩存值 1.go
package geecache
type ByteView struct {
b []byte //緩存值,byte是為了通用性
}
func (v ByteView) Len() int {
return len(v.b)
}
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
func (v ByteView) String() string { // 返回一個字符串的拷貝
return string(v.b)
}
func cloneBytes(b []byte) []byte { // 返回一個byte的拷貝
c := make([]byte, len(b))
copy(c, b)
return c
}
- ByteView,b表示實際的緩存值
- Len()實現接口,表示所占的內存
- ByteSlice() 返回一個拷貝,因為ByteView不能修改
封裝lru.Cache ,添加并發(fā)屬性 2.go
實現cache數據結構
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
cache : 里面封裝了lru的Cache,控制并發(fā)的mutex鎖,和緩存大小
實現增加和get函數
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
}
add和get函數邏輯類似,首先使用mutex鎖保證不沖突,然后查看cache已經存在,如果不存在則初始化,然后添加/獲得數據
defer 的 使用
這里使用了defer,defer的作用就是令函數最后執(zhí)行,所以雖然 c.mu.Lock()
defer c.mu.Unlock()寫在一起,但是Unlock()是最后運行的,即保證協(xié)程不沖突,又提高代碼可讀性,不會忘記解鎖
實現Group ,負責控制緩存值的存取 group.go
實現回調函數,在緩存不存在時獲取數據
當我們在緩存中找不到數據時,此時我們需要從外界獲取數據,由于數據來源的多種,我們不應該考慮這么多,為了拓展性和可讀性,我們實現一個回調函數來通知用戶我們需要數據,用戶通過這個接口把數據傳入
package geecache
import (
"fmt"
"log"
"sync"
)
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)
}
這里定義了Getter接口,GetterFunc函數類型,并且為這個類型實現了Getter接口Get方法文章來源:http://www.zghlxwxcb.cn/news/detail-416948.html
在這里為什么使用接口而不用直接函數實現呢?
- 接口比函數更好的地方在于接口并不關心傳入的數據類型,所以接口可以實現多態(tài),更靈活也更節(jié)省代碼,面對其他情況也能處理
- 而函數需要形參,對傳入參數有要求,面對復雜場景無法處理
實現Group數據結構
type Group struct {
name string
getter Getter
mainCache cache
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
- name 表示緩存的名字
- getter 回調函數,用于不命中緩存時從用戶獲取數據
- mainCache 緩存,實現lru算法,add/get等操作
除此以外還有兩個全局變量 - mu RW鎖 允許多個同時讀,禁止讀寫和寫寫同時操作
- groups
實例化函數
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
return g
}
首先還是判斷回調函數是否正常(是否為空),正常則開啟mutex鎖,保證協(xié)程正常運行,然后實例化文章來源地址http://www.zghlxwxcb.cn/news/detail-416948.html
實現Get函數和GetGroup函數
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
- GetGroup函數通過緩存名字得到group
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, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
- 實現Get函數,首先判斷key是否存在實現過濾,然后查看緩存,找的到就返回,否則使用load函數去進一步查找
- load函數調用getLocally,在分布式場景會使用其他函數獲取
- getLocally 首先使用回調函數,如果成功得到數據則將源數據添加到緩存 mainCache 中(通過 populateCache 方法)
- populateCache 將數據添加main_Cache緩存
實現代碼和測試代碼
1.go
package geecache
type ByteView struct {
b []byte //緩存值,byte是為了通用性
}
func (v ByteView) Len() int {
return len(v.b)
}
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
func (v ByteView) String() string { // 返回一個字符串的拷貝
return string(v.b)
}
func cloneBytes(b []byte) []byte { // 返回一個byte的拷貝
c := make([]byte, len(b))
copy(c, b)
return c
}
2.go
package geecache
import (
"geecache/lru"
"sync"
)
type cache struct {
mu sync.Mutex
lru *lru.Cache
cacheBytes int64
}
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
}
group.go
package geecache
import (
"fmt"
"log"
"sync"
)
type Group struct {
name string
getter Getter
mainCache cache
}
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)
}
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
return g
}
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, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
group_test.go
package geecache
import (
"fmt"
"log"
"reflect"
"testing"
)
var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}
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.Fatal("callback failed")
}
}
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]++
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.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}
func TestGetGroup(t *testing.T) {
groupName := "scores"
NewGroup(groupName, 2<<10, GetterFunc(
func(key string) (bytes []byte, err error) { return }))
if group := GetGroup(groupName); group == nil || group.name != groupName {
t.Fatalf("group %s not exist", groupName)
}
if group := GetGroup(groupName + "111"); group != nil {
t.Fatalf("expect nil, but %s got", group.name)
}
}
到了這里,關于【go項目-geecache】動手寫分布式緩存 day2 - 單機并發(fā)緩存的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!