Golang GroupCache源码分析

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 中。