后端技术基本都要和缓存打交道,最近刚好工作上碰到了这方法的需求,迷迷糊糊的用了一下golang的这个包现在打算写篇心得
首先开篇先稍微跑下题,什么是缓存(cache)?
缓存(cache),原始意义是指访问速度比一般随机存取存储器(RAM)快的一种高速存储器,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一。
打个不恰当的比喻,当你想要做菜,你需要有原材料,在对它进行处理,然后在吃。
如果你每次切一次菜从冰箱里拿一次,如此往复非常浪费时间,你自己也会累。这时候你可以拿个篮子,一次将冰箱的你要用的材料(蔬菜)装起来,然后放到菜板旁边备用。
上面的例子就是:冰箱:数据库 蔬菜:数据 篮子:缓存容器
接下来就来介绍一下今天主要用的包 go-cache
go-cache 是一个基于内存的、高速的,存储k-v格式的缓存工具。它适用于运行在单台机器上的应用程序,可以存储任何数据类型的值,并可以被多个goroutine安全地使用。 虽然go-cache 不打算用作持久数据存储,但是可以将整个缓存数据保存到文件(或任何io.Reader/Writer)中,并且能快速从中指定数据源加载,快速恢复状态。
go-cache核心代码
type cache struct {
defaultExpiration time.Duration //默认的通用key实效时长
items map[string]Item //底层的map存储
mu sync.RWMutex //由于map是非线程安全的,增加的全局锁
onEvicted func(string, interface{})//失效key时,回触发,我自己命名为回收函数
janitor *janitor //监视器,Goroutine,定时轮询用于失效key
}复制代码
demo
import (
"fmt"
"github.com/patrickmn/go-cache"
"time"
)
func main() {
// 默认过期时间为5min,每10min清理一次过期缓存
c := cache.New(5*time.Minute, 10*time.Minute)
// 设置key-value,并设置为默认过期时间
c.Set("foo", "bar", cache.DefaultExpiration)
// 设置一个不会过期的key,该key不会自动删除,重新更新key或者使用c.Delete("baz")
c.Set("baz", 42, cache.NoExpiration)
// 从缓存获取对应key的值
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}
// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, found := c.Get("foo")
if found {
MyFunction(foo.(string))
}
// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, found := c.Get("foo"); found {
foo := x.(string)
// ...
}
// or
var foo string
if x, found := c.Get("foo"); found {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string
// Want performance? Store pointers!
c.Set("foo", &MyStruct, cache.DefaultExpiration)
if x, found := c.Get("foo"); found {
foo := x.(*MyStruct)
// ...
}
}复制代码
前面的例子对应到程序里面就是我这次面对的一个小问题
我在网页上需要导出一个报表,每行每个单元格都需要从对应的model中取出相应的数据但是因为这项数据肯定不可能只存在一个model里,所以需要去查相关联的表。如果每次用哪个model就去库里去查相应的数据,速度就会巨慢无比(原来的人就是这么写的所以我们需要去优化它)
首先我们需要把数据从数据库取出,这里我用的是mongodb,也就是将里面collection的数据全部取出来(collection你可以理解为mysql中的表)
for _, collection := range collections {
switch collection {
case "product":
products, err := gql.FindProduct(ctx, mongoplus.M{})
if err != nil {
logrus.Warn(err)
}
for _, product := range products {
temp := product
err := coll.Set("product_"+product.ID, &temp)
if err != nil {
logrus.Warn(err)
}
}
case "user":
users, err := gql.FindUser(ctx, mongoplus.M{})
if err != nil {
logrus.Warn(err)
}
for _, user := range users {
temp := user
err := coll.Set("user_"+user.ID, &temp)
if err != nil {
logrus.Warn(err)
}
}
case "company":
companys, err := gql.FindCompanyCache(ctx, mongoplus.M{})
if err != nil {
logrus.Warn(err)
}
for _, com := range companys {
temp := com
err := coll.Set("com_"+com.ID, &temp)
if err != nil {
logrus.Warn(err)
}
}
case "region":
Regions, err := gql.FindRegion(ctx, mongoplus.M{})
if err != nil {
logrus.Warn(err)
}
for _, Region := range Regions {
temp := Region
err := coll.Set("region_"+Region.ID, &temp)
if err != nil {
logrus.Warn(err)
}
}
case "industry":
industrys, err := gql.FindIndustry(ctx, mongoplus.M{})
if err != nil {
logrus.Warn(err)
}
for _, industry := range industrys {
temp := industry
err := coll.Set("industry_"+industry.ID, &temp)
if err != nil {
logrus.Warn(err)
}
}
}
}
return coll
}复制代码
上面的代码我把去出的数据都放在了容器里面coll.Set("product_"+product.ID, &temp)
我采用的方式是字段id_+model的形式
然后要用的时候直接从数据库中读取数据就能优化一部分时间
总结:
gocache相对简单,用了map[string]Item来进行存储,没有限制大小,只要内存允许可以一直存,没有上限,这个在实际生产中需要注意。
gocache很简单,但是也有不少问题没有做,简单列一些自己想到的,可以一起优化下:
1. cache数量没有上限,这个在线上使用的时候还是容易出问题
2. 调用get获取对象的时候,如果对象不存在,get方法会直接返回nil,其实这里可以优化一下如果没有命中可以从数据库load一下。
3、一些命中无法跟踪。
结语:
其实对于go-cache还有其他地方可以分析,比如它锁的粒度,结合系统的垃圾回收等等,但是由于我学习golang语言时间不长,很多地方没有学通就不写出来丢人啦。等以后系统的学习go的多线程和其他算法后,我会更新go-cache相关的知识。未来加油!
有疑问加站长微信联系(非本文作者)