Golang 工作筆記 go-cache

xjlgxlgx發表於2019-10-15
後端技術基本都要和快取打交道,最近剛好工作上碰到了這方法的需求,迷迷糊糊的用了一下golang的這個包現在打算寫篇心得

首先開篇先稍微跑下題,什麼是快取(cache)?

快取(cache,原始意義是指訪問速度比一般隨機存取儲存器(RAM)快的一種高速儲存器,通常它不像系統主存那樣使用DRAM技術,而使用昂貴但較快速的SRAM技術。快取的設定是所有現代計算機系統發揮高效能的重要因素之一。

Golang 工作筆記  go-cache

打個不恰當的比喻,當你想要做菜,你需要有原材料,在對它進行處理,然後在吃。

如果你每次切一次菜從冰箱裡拿一次,如此往復非常浪費時間,你自己也會累。這時候你可以拿個籃子,一次將冰箱的你要用的材料(蔬菜)裝起來,然後放到菜板旁邊備用。

上面的例子就是:冰箱:資料庫  蔬菜:資料   籃子:快取容器  

接下來就來介紹一下今天主要用的包 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相關的知識。未來加油!


相關文章