beego 框架 cache 目录是 Go 实现的一个缓存管理器,是beego框架自带工具之一,当然,如果你只想使用 cache 而不是整个 beego 框架,可以选择性安装:
go get github.com/astaxie/beego/cache
在我写这篇博文时, beego 版本是 v1.11.1 , cache 支持内存、文件、 memcache 、 redis、ssdb 。
使用例子:
package main
import (
// 导入cache包
"github.com/astaxie/beego/cache"
"time"
)
func main() {
/*
bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)//memcache
bm, err := cache.NewCache("redis", `{"conn":":6039"}`)//redis
bm, err := cache.NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
*/
// 初始化一个内存的缓存管理器
bm, err := cache.NewCache("memory", `{"interval":60}`)
bm.Put("user", "张三", 10 * time.Second) //
bm.Get("user")
bm.IsExist("user")
bm.Delete("user")
}
cache 包定义了Cache接口,如下:
// Cache interface contains all behaviors for cache adapter.
// usage:
// cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go.
// c,err := cache.NewCache("file","{....}")
// c.Put("key",value, 3600 * time.Second)
// v := c.Get("key")
//
// c.Incr("counter") // now is 1
// c.Incr("counter") // now is 2
// count := c.Get("counter").(int)
type Cache interface {
// Get 函数通过键获取值。
Get(key string) interface{}
// GetMulti 是 Get 的一个批处理版本。
GetMulti(keys []string) []interface{}
// Put 函数设置存入一对键值和到期时间。
Put(key string, val interface{}, timeout time.Duration) error
// 通过键删除值.
Delete(key string) error
// 让键对应的值加1.
Incr(key string) error
// 让键对应的值减1.
Decr(key string) error
// 检查一个键是否存在.
IsExist(key string) bool
// 清空所有缓存.
ClearAll() error
// 根据配置启动一个gc协程。
StartAndGC(config string) error
}
cache.go文件定义了 Instance , Instance 是一个 function ,功能是创建一个Cache接口的实例。adapters是一个 map 用于注册实现了 Cache 的适配器。Register注册可用的适配器,如果被调用两次或者驱动为空,将 panic 。代码如下:
type Instance func() Cache
var adapters = make(map[string]Instance)
func Register(name string, adapter Instance) {
if adapter == nil {
panic("cache: Register adapter is nil")
}
if _, ok := adapters[name]; ok {
panic("cache: Register called twice for adapter " + name)
}
adapters[name] = adapter
}
我们来先看内存缓存的实现方式:
全局变量及结构
DefaultEvery 表示在内存中回收过期缓存项的时间,默认为 1 分钟。
var DefaultEvery = 60
MemoryItem 用于保存基本内存缓存元素.
type MemoryItem struct {
val interface{}
createdTime time.Time
lifespan time.Duration
}
MemoryCache 是一个内存管理适配器,它包含了一个读写锁(sync.RWMutex),使得 MemoryCache 具备并发安全性。
type MemoryCache struct {
sync.RWMutex
dur time.Duration
items map[string]*MemoryItem
Every int // run an expiration check Every clock time
}
方法及函数
init function 注册驱动名,其实就是一个map存取操作,见上面的 Register 函数。
func init() {
Register("memory", NewMemoryCache)
}
NewMemoryCache 初始化一个内存缓存适配器,返回 Cache 接口,NewMemoryCache 实现了 Cache 接口。
func NewMemoryCache() Cache {
cache := MemoryCache{items: make(map[string]*MemoryItem)}
return &cache
}
Get 返回一个键在 MemoryCache 中的值, 如果该键不存在或到期,返回nil。
func (bc *MemoryCache) Get(name string) interface{} {
bc.RLock()
defer bc.RUnlock()
if itm, ok := bc.items[name]; ok {
if itm.isExpire() {
return nil
}
return itm.val
}
return nil
}
GetMulti 传入一个键的 slice,返回slice中存在于缓存且没有过期的值.
func (bc *MemoryCache) GetMulti(names []string) []interface{} {
var rc []interface{}
for _, name := range names {
rc = append(rc, bc.Get(name))
}
return rc
}
Put 向 MemoryCache 中存入一个键名位name ,值为 item 的缓存键值对并设置时间为 lifespan ,如果时间为 0 ,那么 item 将永远存在,除非用户主动删除或。
func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
bc.Lock()
defer bc.Unlock()
bc.items[name] = &MemoryItem{
val: value,
createdTime: time.Now(),
lifespan: lifespan,
}
return nil
}
Delete 删除 MemoryCache 中键名为 name 的缓存.
func (bc *MemoryCache) Delete(name string) error {
bc.Lock()
defer bc.Unlock()
if _, ok := bc.items[name]; !ok {
return errors.New("key not exist")
}
delete(bc.items, name)
if _, ok := bc.items[name]; ok {
return errors.New("delete key error")
}
return nil
}
Incr 增加键名为 name 缓存的值,支持类型:int,int32,int64,uint,uint32,uint64.
func (bc *MemoryCache) Incr(key string) error {
bc.RLock()
defer bc.RUnlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
case int:
itm.val = itm.val.(int) + 1
case int32:
itm.val = itm.val.(int32) + 1
case int64:
itm.val = itm.val.(int64) + 1
case uint:
itm.val = itm.val.(uint) + 1
case uint32:
itm.val = itm.val.(uint32) + 1
case uint64:
itm.val = itm.val.(uint64) + 1
default:
return errors.New("item val is not (u)int (u)int32 (u)int64")
}
return nil
}
Decr 减少键名为 name 缓存的值,支持类型:int,int32,int64,uint,uint32,uint64,如果类型为 uint,uint32,uint64 且值为 0 时,会返回值小于0错误。
func (bc *MemoryCache) Decr(key string) error {
bc.RLock()
defer bc.RUnlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
case int:
itm.val = itm.val.(int) - 1
case int64:
itm.val = itm.val.(int64) - 1
case int32:
itm.val = itm.val.(int32) - 1
case uint:
if itm.val.(uint) > 0 {
itm.val = itm.val.(uint) - 1
} else {
return errors.New("item val is less than 0")
}
case uint32:
if itm.val.(uint32) > 0 {
itm.val = itm.val.(uint32) - 1
} else {
return errors.New("item val is less than 0")
}
case uint64:
if itm.val.(uint64) > 0 {
itm.val = itm.val.(uint64) - 1
} else {
return errors.New("item val is less than 0")
}
default:
return errors.New("item val is not int int64 int32")
}
return nil
}
IsExist 检查键为 name 的缓存是否存在.
func (bc *MemoryCache) IsExist(name string) bool {
bc.RLock()
defer bc.RUnlock()
if v, ok := bc.items[name]; ok {
return !v.isExpire()
}
return false
}
ClearAll 会清除所有缓存.
func (bc *MemoryCache) ClearAll() error {
bc.Lock()
defer bc.Unlock()
bc.items = make(map[string]*MemoryItem)
return nil
}
StartAndGC 开始周期性对缓存进行检查,如果缓存键值对过期,会被删除。
func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["interval"]; !ok {
cf = make(map[string]int)
cf["interval"] = DefaultEvery
}
dur := time.Duration(cf["interval"]) * time.Second
bc.Every = cf["interval"]
bc.dur = dur
go bc.vacuum()
return nil
}
未导出函数及方法
// 检查是否超时.
func (bc *MemoryCache) vacuum() {
bc.RLock()
every := bc.Every
bc.RUnlock()
if every < 1 {
return
}
for {
<-time.After(bc.dur)
if bc.items == nil {
return
}
if keys := bc.expiredKeys(); len(keys) != 0 {
bc.clearItems(keys)
}
}
}
// expiredKeys 返回到期的键名 slice.
func (bc *MemoryCache) expiredKeys() (keys []string) {
bc.RLock()
defer bc.RUnlock()
for key, itm := range bc.items {
if itm.isExpire() {
keys = append(keys, key)
}
}
return
}
// clearItems 清空键名在 keys 内的缓存.
func (bc *MemoryCache) clearItems(keys []string) {
bc.Lock()
defer bc.Unlock()
for _, key := range keys {
delete(bc.items, key)
}
}
后记
file、ssdb、memcache和redis实现都相似,file以文件方式储存每个键对应一个文件,文件内容为值的数据,用gob编码持久化。