go-cache是基于内存的k/v缓存,类似于mencached,适用于单机应用程序。go-cache是线程安全的,支持并发地存取。虽然go-cache是保存在内存,但可以通过SaveFile()方法将缓存中的数据保存到文件。
go-cache的逻辑代码主要是在cache.go文件中,首先来看它的数据结构
// 整体缓存
type Cache struct {
*cache
}
type cache struct {
defaultExpiration time.Duration // 默认的过期时间
items map[string]Item // 保存的k/v对
mu sync.RWMutex // 读写锁
onEvicted func(string, interface{}) // delete操作后执行的回调函数
janitor *janitor
}
// 具体缓存的值
type Item struct {
Object interface{}
Expiration int64 // 过期时间
}
// 传入默认过期时间和清理时间
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
c := newCache(de, m)
C := &Cache{c}
// 如果传入的清理时间>0,则开启定期清理逻辑
if ci > 0 {
runJanitor(c, ci)
runtime.SetFinalizer(C, stopJanitor)
}
return C
}
// 设定默认过期时间,初始化一个cache并返回
func newCache(de time.Duration, m map[string]Item) *cache {
if de == 0 {
de = -1
}
c := &cache{
defaultExpiration: de,
items: m,
}
return c
}
// 存储一个键值对
func (c *cache) Set(k string, x interface{}, d time.Duration) {
var e int64
// 设定过期时间:
// 1. d = DefaultExpiration (即0),为默认过期时间
// 2. d < 0,永不过期
// 3. d > 0,采用传入的过期时间
if d == DefaultExpiration {
d = c.defaultExpiration
}
if d > 0 {
e = time.Now().Add(d).UnixNano()
}
// 在将键值对写入map前先加锁
c.mu.Lock()
c.items[k] = Item{
Object: x,
Expiration: e,
}
c.mu.Unlock()
}
与Set类似,都是写入一个键值对,区别是当要存入的键已存在时,Add返回error,而Set覆盖原来的值
func (c *cache) Add(k string, x interface{}, d time.Duration) error {
c.mu.Lock()
_, found := c.get(k)
// 如果键已存在则返回error
if found {
c.mu.Unlock()
return fmt.Errorf("Item %s already exists", k)
}
c.set(k, x, d)
c.mu.Unlock()
return nil
}
替换已存在的键值,若键不存在则返回error
func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
c.mu.Lock()
_, found := c.get(k)
// 键不存在,返回error
if !found {
c.mu.Unlock()
return fmt.Errorf("Item %s doesn't exist", k)
}
c.set(k, x, d)
c.mu.Unlock()
return nil
}
func (c *cache) Get(k string) (interface{}, bool) {
// 加读锁
c.mu.RLock()
item, found := c.items[k]
// 没有找到则直接返回
if !found {
c.mu.RUnlock()
return nil, false
}
// 如果设置了过期时间则检查是否过期,若过期了则直接返回
if item.Expiration > 0 {
if time.Now().UnixNano() > item.Expiration {
c.mu.RUnlock()
return nil, false
}
}
c.mu.RUnlock()
return item.Object, true
}
func (c *cache) Delete(k string) {
c.mu.Lock()
v, evicted := c.delete(k)
c.mu.Unlock()
// 如果注册了回调函数,则调用
if evicted {
c.onEvicted(k, v)
}
}
// 执行删除键值对操作,并判断是否注册回调函数
func (c *cache) delete(k string) (interface{}, bool) {
if c.onEvicted != nil {
if v, found := c.items[k]; found {
delete(c.items, k)
return v.Object, true
}
}
delete(c.items, k)
return nil, false
}
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
}
c.janitor = j
go j.Run(c) // 新的协程做缓存清理逻辑
}
func (j *janitor) Run(c *cache) {
// 定时器
ticker := time.NewTicker(j.Interval)
for {
select {
// 定时清理
case <-ticker.C:
c.DeleteExpired()
// 终止清理
case <-j.stop:
ticker.Stop()
return
}
}
}
// 与Delete类似,遍历所有键值对,找到过期的键值对执行删除操作,若该键值对有回调函数则一一调用
func (c *cache) DeleteExpired() {
var evictedItems []keyAndValue
now := time.Now().UnixNano()
c.mu.Lock()
for k, v := range c.items {
if v.Expiration > 0 && now > v.Expiration {
ov, evicted := c.delete(k)
if evicted {
evictedItems = append(evictedItems, keyAndValue{k, ov})
}
}
}
c.mu.Unlock()
for _, v := range evictedItems {
c.onEvicted(v.key, v.value)
}
}
package main
import (
"fmt"
"time"
"github.com/patrickmn/go-cache"
)
func main() {
c := cache.New(5 * time.Second, 10 * time.Second)
c.OnEvicted(func(s string, i interface{}) {
fmt.Printf("delete key: %s, value: %+v\n", s, i)
})
// 永不过期
c.Set("test1", "never expired", cache.NoExpiration)
// 默认过期时间
c.Set("test2", "default expiration time", cache.DefaultExpiration)
// 自定义过期时间
c.Set("test3", "custom expiration time", 2 * time.Second)
time.Sleep(3*time.Second)
v1, found := c.Get("test1")
fmt.Println("after 3 seconds")
fmt.Printf("test1's value: %+v, found? %t\n", v1, found)
v2, found := c.Get("test2")
fmt.Printf("test2's value: %+v, found? %t\n", v2, found)
v3, found := c.Get("test3")
fmt.Printf("test3's value: %+v, found? %t\n", v3, found)
time.Sleep(3*time.Second)
v1, found = c.Get("test1")
fmt.Println("after 6 seconds")
fmt.Printf("test1's value: %+v, found? %t\n", v1, found)
v2, found = c.Get("test2")
fmt.Printf("test2's value: %+v, found? %t\n", v2, found)
v3, found = c.Get("test3")
fmt.Printf("test3's value: %+v, found? %t\n", v3, found)
c.Delete("test1")
v1, found = c.Get("test1")
fmt.Printf("test1's value: %+v, found? %t\n", v1, found)
}
// 输出
// after 3 seconds
// test1's value: never expired, found? true
// test2's value: default expiration time, found? true
// test3's value: <nil>, found? false
// after 6 seconds
// test1's value: never expired, found? true
// test2's value: <nil>, found? false
// test3's value: <nil>, found? false
// delete key: test1, value: never expired
// test1's value: <nil>, found? false