68

Golang 工作笔记 go-cache

 4 years ago
source link: https://www.tuicool.com/articles/3EBra2y
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

后端技术基本都要和缓存打交道,最近刚好工作上碰到了这方法的需求,迷迷糊糊的用了一下golang的这个包现在打算写篇心得

首先开篇先稍微跑下题,什么是缓存(cache)?

缓存( cache ,原始意义是指访问速度比一般 随机存取存储器 (RAM)快的一种高速存储器,通常它不像系统主存那样使用 DRAM 技术,而使用昂贵但较快速的 SRAM 技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一。

67fiayJ.jpg!web

打个不恰当的比喻,当你想要做菜,你需要有原材料,在对它进行处理,然后在吃。

如果你每次切一次菜从冰箱里拿一次,如此往复非常浪费时间,你自己也会累。这时候你可以拿个篮子,一次将冰箱的你要用的材料(蔬菜)装起来,然后放到菜板旁边备用。

上面的例子就是: 冰箱:数据库  蔬菜:数据   篮子:缓存容器 

接下来就来介绍一下今天主要用的包 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相关的知识。未来加油!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK