go-cache

https://github.com/patrickmn/go-cache

一句话描述

基于内存的 K/V 存储/缓存 : (类似于Memcached),适用于单机应用程序

简介

go-cache是什么?

基于内存的 K/V 存储/缓存 : (类似于Memcached),适用于单机应用程序 ,支持删除,过期,默认Cache共享锁,

大量key的情况下会造成锁竞争严重

为什么选择go-cache?

可以存储任何对象(在给定的持续时间内或永久存储),并且可以由多个goroutine安全地使用缓存。

Example

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/patrickmn/go-cache"
  6. )
  7. type MyStruct struct {
  8. Name string
  9. }
  10. func main() {
  11. // 设置超时时间和清理时间
  12. c := cache.New(5*time.Minute, 10*time.Minute)
  13. // 设置缓存值并带上过期时间
  14. c.Set("foo", "bar", cache.DefaultExpiration)
  15. // 设置没有过期时间的KEY,这个KEY不会被自动清除,想清除使用:c.Delete("baz")
  16. c.Set("baz", 42, cache.NoExpiration)
  17. var foo interface{}
  18. var found bool
  19. // 获取值
  20. foo, found = c.Get("foo")
  21. if found {
  22. fmt.Println(foo)
  23. }
  24. var foos string
  25. // 获取值, 并断言
  26. if x, found := c.Get("foo"); found {
  27. foos = x.(string)
  28. fmt.Println(foos)
  29. }
  30. // 对结构体指针进行操作
  31. var my *MyStruct
  32. c.Set("foo", &MyStruct{Name: "NameName"}, cache.DefaultExpiration)
  33. if x, found := c.Get("foo"); found {
  34. my = x.(*MyStruct)
  35. // ...
  36. }
  37. fmt.Println(my)
  38. }

源码分析

源码分析主要针对核心的存储结构、Set、Get、Delete、定时清理逻辑进行分析。包含整体的逻辑架构

核心的存储结构

  1. package cache
  2. // Item 每一个具体缓存值
  3. type Item struct {
  4. Object interface{}
  5. Expiration int64 // 过期时间:设置时间+缓存时长
  6. }
  7. // Cache 整体缓存
  8. type Cache struct {
  9. *cache
  10. }
  11. // cache 整体缓存
  12. type cache struct {
  13. defaultExpiration time.Duration // 默认超时时间
  14. items map[string]Item // KV对
  15. mu sync.RWMutex // 读写锁,在操作(增加,删除)缓存时使用
  16. onEvicted func(string, interface{}) // 删除KEY时的CallBack函数
  17. janitor *janitor // 定时清空缓存的结构
  18. }
  19. // janitor 定时清空缓存的结构
  20. type janitor struct {
  21. Interval time.Duration // 多长时间扫描一次缓存
  22. stop chan bool // 是否需要停止
  23. }

Set、Get、Delete

Set
  1. package cache
  2. func (c *cache) Set(k string, x interface{}, d time.Duration) {
  3. // "Inlining" of set
  4. var e int64
  5. if d == DefaultExpiration {
  6. d = c.defaultExpiration
  7. }
  8. if d > 0 {
  9. e = time.Now().Add(d).UnixNano()
  10. }
  11. c.mu.Lock() // 这里可以使用defer?
  12. c.items[k] = Item{
  13. Object: x, // 实际的数据
  14. Expiration: e, // 下次过期时间
  15. }
  16. c.mu.Unlock()
  17. }

Get
  1. package cache
  2. func (c *cache) Get(k string) (interface{}, bool) {
  3. c.mu.RLock() // 加锁,限制并发读写
  4. item, found := c.items[k] // 在 items 这个 map[string]Item 查找数据
  5. if !found {
  6. c.mu.RUnlock()
  7. return nil, false
  8. }
  9. if item.Expiration > 0 {
  10. if time.Now().UnixNano() > item.Expiration { // 已经过期,直接返回nil,为什么在这里不直接就删除了呢?
  11. c.mu.RUnlock()
  12. return nil, false
  13. }
  14. }
  15. c.mu.RUnlock()
  16. return item.Object, true
  17. }

Delete
  1. package cache
  2. // Delete an item from the cache. Does nothing if the key is not in the cache.
  3. func (c *cache) Delete(k string) {
  4. c.mu.Lock()
  5. v, evicted := c.delete(k)
  6. c.mu.Unlock()
  7. if evicted {
  8. c.onEvicted(k, v) // 删除KEY时的CallBack
  9. }
  10. }
  11. func (c *cache) delete(k string) (interface{}, bool) {
  12. if c.onEvicted != nil {
  13. if v, found := c.items[k]; found {
  14. delete(c.items, k)
  15. return v.Object, true
  16. }
  17. }
  18. delete(c.items, k)
  19. return nil, false
  20. }

定时清理逻辑

  1. package cache
  2. func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
  3. c := newCache(de, m)
  4. C := &Cache{c}
  5. if ci > 0 {
  6. runJanitor(c, ci) // 定时运行清除过期KEY
  7. runtime.SetFinalizer(C, stopJanitor) // 当C被GC回收时,会停止runJanitor 中的协程
  8. }
  9. return C
  10. }
  11. func runJanitor(c *cache, ci time.Duration) {
  12. j := &janitor{
  13. Interval: ci,
  14. stop: make(chan bool),
  15. }
  16. c.janitor = j
  17. go j.Run(c) // 新的协程做过期删除逻辑
  18. }
  19. func (j *janitor) Run(c *cache) {
  20. ticker := time.NewTicker(j.Interval)
  21. for {
  22. select {
  23. case <-ticker.C: // 每到一个周期就全部遍历一次
  24. c.DeleteExpired() // 实际的删除逻辑
  25. case <-j.stop:
  26. ticker.Stop()
  27. return
  28. }
  29. }
  30. }
  31. // Delete all expired items from the cache.
  32. func (c *cache) DeleteExpired() {
  33. var evictedItems []keyAndValue
  34. now := time.Now().UnixNano()
  35. c.mu.Lock()
  36. for k, v := range c.items { // 加锁遍历整个列表
  37. // "Inlining" of expired
  38. if v.Expiration > 0 && now > v.Expiration {
  39. ov, evicted := c.delete(k)
  40. if evicted {
  41. evictedItems = append(evictedItems, keyAndValue{k, ov})
  42. }
  43. }
  44. }
  45. c.mu.Unlock()
  46. for _, v := range evictedItems {
  47. c.onEvicted(v.key, v.value)
  48. }
  49. }

思考

Lock 的使用

  • 在go-cache中,涉及到读写cache,基本上都用到了锁,而且在遍历的时候也用到锁,当cache的数量非常多时,读写频繁时,
    会有严重的锁冲突。

使用读写锁?
  • sync.RWMutex, 在读的时候加RLock, 可以允许多个读。在写的时候加Lock,不允许其他读和写。

锁的粒度是否可以变更小?
  • 根据KEY HASH 到不同的map中

使用sync.map?
  • 减少锁的使用

runtime.SetFinalizer

在实际的编程中,我们都希望每个对象释放时执行一个方法,在该方法内执行一些计数、释放或特定的要求,
以往都是在对象指针置nil前调用一个特定的方法,
golang提供了runtime.SetFinalizer函数,当GC准备释放对象时,会回调该函数指定的方法,非常方便和有效。

对象可以关联一个SetFinalizer函数, 当gc检测到unreachable对象有关联的SetFinalizer函数时,
会执行关联的SetFinalizer函数, 同时取消关联。 这样当下一次gc的时候, 对象重新处于unreachable状态
并且没有SetFinalizer关联, 就会被回收。

Doc

http://godoc.org/github.com/patrickmn/go-cache

比较

相似的库