您的位置:首页 > 其它

groupcache分析(一) -单机环境下的groupcache分析

2017-04-05 22:30 253 查看

Groupcache简介

以包的形式,提供了缓存服务. 可以内嵌到项目中,无需单独部署.

Groupcache组成部分

consistenthash

groupcachepb

lru

singleflight

byteview

groupcache

http

peer

sinks

理解上述9个要点,就可以清晰的了解groupcache究竟是什么样子?在什么场合下适合使用groupcache.

上述9个部分,又可以按照单点cache和集群cache进行划分, 在单点cache中,上述的9个部分中,有很多就用不上了.

单机环境groupcache需要部分

3.lru

4.singleflight

5.byteview

6.groupcache

9.sinks

在单机环境中, 远程节点cache查询这个功能就用不上,groupcache在单机环境下,根本就不会用到其他的函数,只会用到上述5个文件或包中的函数.

下边来介绍下单机环境中,上边5个包的功能

3. lru

这个是存储cache的一种方式,lru算法,将访问到的cache放置到链表的头部,当有新增cache到cache池中后,如果cache池超过设定的最大值,则链表最后那个元素将会被删除. lru算法,保证最近访问的元素置于头部,访问频率最低的cache元素被放到链表尾部,这样,当cache池满了之后,将访问频率最低的元素删除.

单机环境中,只会用到maincache,而不用用到hotcache,因为hotcache是本节点从远程节点获取到的key值对应的cache数据后,保存在本节点上的一个副本.

4.singleflight

这个包类似于系统提供的Once包,只是与系统提供的Once包有点区别,系统中的Once包,是Do中提供的函数形式的参数只被执行一次, 而singleflight包中提供的方法是,key值对应的函数,只被执行一次.

groupcache缓存模块中,如果在获取key值对应的cache值时,如果在maincache中没有找到cache时(单机环境,不考虑远程节点获取cache,也就不用考虑hotcache), 会调用key对应的Getter方法,这个方法会将获取数据,并存储key值对应的缓存到maincache中.

当系统初始化后,如果同时有多个goroutine读取同一个key值的缓存时,将会出现goroutine阻塞, 只需要第一个goroutine调用Getter中的方法,处理好key值的缓存,将cache存储到maincache中,后边的goroutine就不需要调用Getter中的方法来获取数据,而是直接从第一个goroutine处理好的maincache中获取缓存. 这种处理的好处是,保证了每一个key值注册的Getter方法只被调用一次,而不是重复调用.

5.Byteview

整个groupcache包中,都是采用到了Byteview这个类型存储数据,也就是在lru的链表中,存储的数据类型是Byteview.尽管Byteview中又分为[]byte和string,但是对于groupcache这个包中,Byteview类型,是最细粒度的非系统类型.下边,我们将称Byteview为groupcache为最小存储单元.

所以对于Byteview这个类,可以理解成groupcache的存储单元.

6.groupcache

这是groupcache整个cache模块的核心部分.groupcache缓存的策略是,先分组,再找key. 也就是说, 不同的组中,相同的key对应的值可以不一样.这种分组缓存的策略,可以方便我们在不同的业务场景中,简洁的设置缓存.

groupcache有一个很好的开始,即groupcache.NewGroup这个函数.再创建Group时,需要提供一个Getter接口类型的变量.咋一看去,个那就Getter是从groupcache获取key的value信息.其实不然,这个Getter的含义是,从别的地方获取key的value,并将key的value存入到groupcache中.

所以,Getter,其实就是写入缓存groupcache的方法,类似于其他缓存系统中的Put,Set等等.

package hcache

import (
"errors"
"github.com/golang/groupcache"
"io/ioutil"
"sync"
)

// 这个包提供了缓存静态文件的方法
// 将系统中使用到的静态html文件缓存到内存中,
// 下次在打开这个页面时,就不需要从磁盘中读取,而是直接冲缓存中加载.
// example:
// hcache.Register(key,value)
// 每一个key只能注册一次,否则会panic.

var groupctx groupcache.Context
var lock = new(sync.RWMutex)

// 存储所有的静态文件
// key是索引
// vaule 是key的文件存储地址
var staticFile map[string]string = make(map[string]string)

// 注册静态文件信息
func Register(key, value string) {
lock.RLock()
defer lock.RUnlock()
if _, ok := staticFile[key]; ok {
panic(key + value + " 这个静态页面已经被注册了.")
}
staticFile[key] = value
}

func GetStaticFile(key string) ([]byte, error) {

gp := groupcache.GetGroup("ASOFDATEHAUTH")
if gp == nil {
gp = groupcache.NewGroup("ASOFDATEHAUTH", 1<<28, groupcache.GetterFunc(func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
if filepath, ok := staticFile[key]; ok {
fmt.Println("get " + key + " html data from disk.")
rst, _ := ioutil.ReadFile(filepath)
return dest.SetBytes(rst)
}
return errors.New("filepath is not exists." + key)
}))
}

var rst groupcache.ByteView
err := gp.Get(groupctx, key, groupcache.ByteViewSink(&rst))
if err != nil {
goto DISK
}
return rst.ByteSlice(), err

DISK:
if filepath, ok := staticFile[key]; ok {
fmt.Println("get authority html data from disk.")
rst, _ := ioutil.ReadFile(filepath)
return rst, nil
}
return nil, errors.New("filepath is not exists." + key)
}


上述是groupcache的一段示例代码, 这段代码,是对静态文件的一个缓存. 每一个key对应着一个静态文件.这些cache全部分布在ASOFDATEHAUTH这个组中,分组的好处是,在多人协作过程中,不用担心其他组中的key值与你自己开辟的组中的key值冲突.

9.sinks

上边我们提到Byteview是groupcache的最小存储单元. 但是groupcache并不能直接将byteview类型的数据存入到cache中.在sinks文件中,提供了几个数据类型,用户只能将数据转换成sinks文件中定义好的数据类型,然后groupcache会自动将这些存入进来的数据转换成Byteview类型.

sinks的作用,就是丰富了groupcache的适用性,不强迫用户将存入进去的数据一定搞成Byteview,虽然groupcache的最小存储单元是Byteview.

Sink就像一个水槽,用户只需要把需要缓存的数据注入到这个水槽中,groupcache将会把水槽中的数据缓存起来.

上述是单机环境中groupcache使用到的组件. 那么对于用户而言,在单机环境中使用groupcache时,又需要怎么来使用groupcache呢?

上边那段示例代码,已经展示了在单机环境中,使用groupcache的非常方便.主要有下边两个过程:

1. 创建分组

2. 提供Getter接口类型的变量

第一点很好处理,就是直接调用NewGroup方法,就可以创建分组,在创建分组时,需要先判断一下这个分组存在与否,重复创建分组,会panic.

GetGroup函数,就实现了获取某个分组的功能.

分组定义被创建时,需要提供一个groupcache.Getter接口类型的变量,这个接口中有一个方法

Get(ctx Context, key string, dest Sink) error


这个方法比较特殊,之前我们提到过singleflight部件.这个Get方法就是在singleflight中执行的.singleflight部件,保证了在一个指定的分组中,同一个key对应的Get方法只被执行一次.

那么这个Getter接口中的Get方法需要完成什么了?答案是:将key对应的缓存存入到groupcache的Sink中.后边就可以通过key,在groupcache中查找key对应的value.

下边来看看怎么从groupcache中获取key对应的value代码示例:

var rst groupcache.ByteView
err := gp.Get(groupctx, key, groupcache.ByteViewSink(&rst))


gp变量是通过NewGroup或者GetGroup来获取.groupcache将会判断key值对应的value是否在groupcache中,如果key对应的value在缓存中,则将key的value值返回到Sink中.即rst变量中,将会是key在cache中的value值.

如果在调用上述函数时,发现groupcache中没有发现key对应的value, 则此时,会执行Getter接口变量中Get方法,这个Get方法是个lasy function. 只有当用户在查找key值对应的value时,在没有找到的情况下才执行.

假如同一时点,有100个goroutine在groupcache中查找key的value值,而此时key对应的value又恰巧不在groupcache的cache中,那么是不是这个Getter接口变量的Get方法会被执行100次呢? 答案是:不是! singleflight很好的解决了这个问题, singleflight规定,在一个分组中,一个key只能执行一次Getter变量中的Get方法. 当100个goroutine同时查找key时,第一个goroutine去执行Getter变量中的Get方法,执行完后,这个key的value将会被放入到groupcache的cache中,后边排队的99个goroutine将会直接读取cache中key对应的value.

Getter变量提供的Get方法,用于给不在groupcache中的key设置cache. 那么Getter是怎么来的?

在创建分组时,必须提供Getter接口类型的变量,NewGroup函数如下:

func NewGroup(name string, cacheBytes int64, getter Getter) *Group


上述方法,第一个是分组的名字,这个名字不能重复,第二个是这个分组在内存中最大占用空间,第三个就是Getter接口类型变量了.这个变量很关键,每一个不在groupcache中的key,都会触发这个Getter接口类型变量的Get方法.这个Get方法如果不将key对应的value存入到groupcache中,则下次用户再次查询key的缓存时,任然会触发Getter接口类型变量的Get方法. 如果在Getter接口变量的Get方法中不去存储key的value到groupcache的lru中,那么整个groupcache将失去意义.并且将会浪费资源,降低系统性能.

通过上述的分析,来总结下groupcache的特点:

按分组来存储管理缓存.

每一个组提供了一个写入key的value到cache的处理函数,即Getter变量的Get方法,就算key值不同,但是处理函数确都一样

没有提供delete group操作.

由于groupcache没有提供delete操作,所有,在动态数据处理中,groupcache将很难发挥作用,在静态文件缓存中,就比较方便,由于没有delete group操作,在singleflight的规定下,也不同担心并发下缓存不一致等等问题.

每一个分组中,不同的key都只能对应一个Get处理方法.要想实现同一个分组中,不同的key采用不同的Get方法,需要引入很多预判处理,代码量将会大大增加, 且很难做到对key的全包含预判,所以,groupcache很难做到对不同的key,采用不同的Get方法.

对于上述几个特点,将groupcache缓存静态文件时,就不会有什么问题,但是在处理动态数据时,由于key的value是变化的,而groupcache又不提供delete操作,所以,在动态数据处理过程中,groupcache就存在一定的不足之处.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  groupcache