NewGroup 接口的第三个参数是 Getter 类型的接口,其用于当缓存丢失之后,或者从缓存里面获取不到数据的时候,调用该 Getter 接口从数据源加载数据,Getter 接口的具体定义在 groupcache.go 文件中,具体代码如下:
// A Getter loads data for a key.
type Getter interface {
// Get returns the value identified by key, populating dest.
//
// The returned data must be unversioned. That is, key must
// uniquely describe the loaded data, without an implicit
// current time, and without relying on cache expiration
// mechanisms.
Get(ctx context.Context, key string, dest Sink) error
}
该接口只有一个方法,具体参数解释如下:
字段 | 说明 |
---|---|
ctx | 上下文,可以用于额外传参 |
key | 要从缓存获取的数据的键 |
dest | 获取的数据可以用来填充 dest,其是一个 Sink 接口类型的参数 |
接下来,我们来看 Sink 接口,具体代码如下:
// A Sink receives data from a Get call.
//
// Implementation of Getter must call exactly one of the Set methods
// on success.
type Sink interface {
// SetString sets the value to s.
SetString(s string) error
// SetBytes sets the value to the contents of v.
// The caller retains ownership of v.
SetBytes(v []byte) error
// SetProto sets the value to the encoded version of m.
// The caller retains ownership of m.
SetProto(m proto.Message) error
// view returns a frozen view of the bytes for caching.
view() (ByteView, error)
}
Sink 接口用于从 Get 接口接受数据,也就是 Get 接口获取到的数据会存储到 Sink 接口中,该接口定义了四个方法,具体解释如下:
函数 | 说明 |
---|---|
SetString | 设置字符串类型的数据 |
SetBytes | 设置字节数组类型的数据 |
SetProto | 设置 Protobuf 类型的数据 |
view | 返回当前需要缓存的数据 |
接下来,我们看下我们 NewGroup 的写法,具体代码如下:
stringGroup := groupcache.NewGroup(name, 1<<20, groupcache.GetterFunc(getterFunc))
我们使用了 GetterFunc 并且传入了 getterFunc 函数,其中 GetterFunc 是一个自定义类型,具体定义如下:
// A GetterFunc implements Getter with a function.
type GetterFunc func(ctx context.Context, key string, dest Sink) error
func (f GetterFunc) Get(ctx context.Context, key string, dest Sink) error {
return f(ctx, key, dest)
}
我们看到,GetterFunc 实现了 Get 方法,也就是说 GetterFunc 实现了 Getter 接口,这里需要注意的是,GetterFunc 也是一个函数类型,并且函数的原型与 Getter 接口里面的 Get 方法一样,这个使用方法就非常的巧妙,
如果我们按照一般的接口定义方法来实现这个接口的话,大体代码应该如下:
//定义结构体,并实现 Getter 接口
type MyGetter struct{}
func (MyGetter) Get(ctx context.Context, key string, dest Sink) error {
//具体函数实现
return
}
var myGetter MyGetter
stringGroup := groupcache.NewGroup(name, 1<<20, myGetter)
我们可以看到,这样我们就需要定义一个 MyGetter 结构体,并且使用 MyGetter 结构体定义一个实体,再传入到 NewGroup 函数中,这样写起来代码非常的多且麻烦,而我们将类型 GetterFunc 定义成与 Getter 接口里面的 Get 方法一致,就非常的方便,我们在调用的时候不再需要定义结构体,也不再需要定义结构体的实体,直接传入需要实现的具体函数即可。
现在,我们来看下,我们的 getterFunc 函数的具体实现,代码如下:
func getterFunc(ctx context.Context, key string, dest groupcache.Sink) (err error){
//当cache miss之后,用来执行的load data方法
fp, err := os.Open("groupcache.conf")
if err != nil {
fmt.Println("read groupcache.conf Err =", err)
return err
}
defer fp.Close()
fmt.Printf("look up for %s from config_file\n", key)
//按行读取配置文件
buf := bufio.NewReader(fp)
for {
line, err := buf.ReadString('\n')
if err != nil {
if err == io.EOF {
dest.SetBytes([]byte{})
return nil
} else {
return err
}
}
line = strings.TrimSpace(line)
parts := strings.Split(line, "=")
if len(parts) > 2 {
continue
} else if parts[0] == key {
dest.SetBytes([]byte(parts[1]))
return nil
} else {
continue
}
}
}
这个代码其实就是当缓存里面没有数据事,读取文件,从配置文件里面获取对应的键的数据,并放入到 dest 中。