From fad810080428c6722a1ba3c45d30a9341babc4fc Mon Sep 17 00:00:00 2001 From: astaxie Date: Mon, 22 Apr 2013 18:56:30 +0800 Subject: [PATCH] add cache module --- cache/README.md | 52 ++++++++++++++++ cache/cache.go | 39 ++++++++++++ cache/cache_test.go | 38 ++++++++++++ cache/memcache.go | 102 +++++++++++++++++++++++++++++++ cache/memory.go | 144 ++++++++++++++++++++++++++++++++++++++++++++ cache/redis.go | 97 +++++++++++++++++++++++++++++ 6 files changed, 472 insertions(+) create mode 100644 cache/README.md create mode 100644 cache/cache.go create mode 100644 cache/cache_test.go create mode 100644 cache/memcache.go create mode 100644 cache/memory.go create mode 100644 cache/redis.go diff --git a/cache/README.md b/cache/README.md new file mode 100644 index 00000000..93868458 --- /dev/null +++ b/cache/README.md @@ -0,0 +1,52 @@ +## cache +cache is a golang cache manager. It can use cache for many adapters. The repo is inspired by `database/sql` . + +##How to install + + go get github.com/astaxie/beego/cache + + +##how many adapter support + +Now this cache support memory/redis/memcache + +## how to use it +first you must import it + + + import ( + "github.com/astaxie/beego/cache" + ) + +then init an Cache(memory adapter) + + bm, err := NewCache("memory", `{"interval":60}`) + +use it like this: + + bm.Put("astaxie", 1, 10) + bm.Get("astaxie") + bm.IsExist("astaxie") + bm.Delete("astaxie") + +## memory adapter +memory adapter config like this: + + {"interval":60} + +interval means the gc time. The cache will every interval time to check wheather have item expired. + +## memcache adapter +memory adapter use the vitess's [memcache](code.google.com/p/vitess/go/memcache) client. + +the config like this: + + {"conn":"127.0.0.1:11211"} + + +## redis adapter +redis adapter use the [redigo](github.com/garyburd/redigo/redis) client. + +the config like this: + + {"conn":":6039"} \ No newline at end of file diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 00000000..0353a038 --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,39 @@ +package cache + +import ( + "fmt" +) + +type Cache interface { + Get(key string) interface{} + Put(key string, val interface{}, timeout int) error + Delete(key string) error + IsExist(key string) bool + ClearAll() error + StartAndGC(config string) error +} + +var adapters = make(map[string]Cache) + +// Register makes a cache adapter available by the adapter name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, adapter Cache) { + if adapter == nil { + panic("cache: Register adapter is nil") + } + if _, dup := adapters[name]; dup { + panic("cache: Register called twice for adapter " + name) + } + adapters[name] = adapter +} + +//config is json {"interval":360} +func NewCache(adapterName, config string) (Cache, error) { + adapter, ok := adapters[adapterName] + if !ok { + return nil, fmt.Errorf("cache: unknown adaptername %q (forgotten import?)", adapterName) + } + adapter.StartAndGC(config) + return adapter, nil +} diff --git a/cache/cache_test.go b/cache/cache_test.go new file mode 100644 index 00000000..d43fe537 --- /dev/null +++ b/cache/cache_test.go @@ -0,0 +1,38 @@ +package cache + +import ( + "testing" + "time" +) + +func Test_cache(t *testing.T) { + bm, err := NewCache("memory", `{"interval":60}`) + if err != nil { + t.Error("init err") + } + if err = bm.Put("astaxie", 1, 10); err != nil { + t.Error("set Error", err) + } + if !bm.IsExist("astaxie") { + t.Error("check err") + } + + if v := bm.Get("astaxie"); v.(int) != 1 { + t.Error("get err") + } + + time.Sleep(70 * time.Second) + + if bm.IsExist("astaxie") { + t.Error("check err") + } + + if err = bm.Put("astaxie", 1, 10); err != nil { + t.Error("set Error", err) + } + + bm.Delete("astaxie") + if bm.IsExist("astaxie") { + t.Error("delete err") + } +} diff --git a/cache/memcache.go b/cache/memcache.go new file mode 100644 index 00000000..ae261989 --- /dev/null +++ b/cache/memcache.go @@ -0,0 +1,102 @@ +package cache + +import ( + "code.google.com/p/vitess/go/memcache" + "encoding/json" + "errors" +) + +type MemcacheCache struct { + c *memcache.Connection + conninfo string +} + +func NewMemCache() *MemcacheCache { + return &MemcacheCache{} +} + +func (rc *MemcacheCache) Get(key string) interface{} { + if rc.c == nil { + rc.c = rc.connectInit() + } + v, _, err := rc.c.Get(key) + if err != nil { + return nil + } + var contain interface{} + contain = v + return contain +} + +func (rc *MemcacheCache) Put(key string, val interface{}, timeout int) error { + if rc.c == nil { + rc.c = rc.connectInit() + } + v, ok := val.(string) + if !ok { + return errors.New("val must string") + } + stored, err := rc.c.Set(key, 0, uint64(timeout), []byte(v)) + if err == nil && stored == false { + return errors.New("stored fail") + } + return err +} + +func (rc *MemcacheCache) Delete(key string) error { + if rc.c == nil { + rc.c = rc.connectInit() + } + _, err := rc.c.Delete(key) + return err +} + +func (rc *MemcacheCache) IsExist(key string) bool { + if rc.c == nil { + rc.c = rc.connectInit() + } + v, _, err := rc.c.Get(key) + if err != nil { + return false + } + if len(v) == 0 { + return false + } else { + return true + } + return true +} + +func (rc *MemcacheCache) ClearAll() error { + if rc.c == nil { + rc.c = rc.connectInit() + } + err := rc.c.FlushAll() + return err +} + +func (rc *MemcacheCache) StartAndGC(config string) error { + var cf map[string]string + json.Unmarshal([]byte(config), &cf) + if _, ok := cf["conn"]; !ok { + return errors.New("config has no conn key") + } + rc.conninfo = cf["conn"] + rc.c = rc.connectInit() + if rc.c == nil { + return errors.New("dial tcp conn error") + } + return nil +} + +func (rc *MemcacheCache) connectInit() *memcache.Connection { + c, err := memcache.Connect(rc.conninfo) + if err != nil { + return nil + } + return c +} + +func init() { + Register("memcache", NewMemCache()) +} diff --git a/cache/memory.go b/cache/memory.go new file mode 100644 index 00000000..aef852e1 --- /dev/null +++ b/cache/memory.go @@ -0,0 +1,144 @@ +package cache + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "sync" + "time" +) + +var ( + DefaultEvery int = 60 // 1 minute +) + +type MemoryItem struct { + val interface{} + Lastaccess time.Time + expired int +} + +func (itm *MemoryItem) Access() interface{} { + itm.Lastaccess = time.Now() + return itm.val +} + +type MemoryCache struct { + lock sync.RWMutex + dur time.Duration + items map[string]*MemoryItem + Every int // Run an expiration check Every seconds +} + +// NewDefaultCache returns a new FileCache with sane defaults. +func NewMemoryCache() *MemoryCache { + cache := MemoryCache{items: make(map[string]*MemoryItem)} + return &cache +} + +func (bc *MemoryCache) Get(name string) interface{} { + bc.lock.RLock() + defer bc.lock.RUnlock() + itm, ok := bc.items[name] + if !ok { + return nil + } + return itm.Access() +} + +func (bc *MemoryCache) Put(name string, value interface{}, expired int) error { + bc.lock.Lock() + defer bc.lock.Unlock() + t := MemoryItem{val: value, Lastaccess: time.Now(), expired: expired} + if _, ok := bc.items[name]; ok { + return errors.New("the key is exist") + } else { + bc.items[name] = &t + } + return nil +} + +func (bc *MemoryCache) Delete(name string) error { + bc.lock.Lock() + defer bc.lock.Unlock() + if _, ok := bc.items[name]; !ok { + return errors.New("key not exist") + } + delete(bc.items, name) + _, valid := bc.items[name] + if valid { + return errors.New("delete key error") + } + return nil +} + +func (bc *MemoryCache) IsExist(name string) bool { + bc.lock.RLock() + defer bc.lock.RUnlock() + _, ok := bc.items[name] + return ok +} + +func (bc *MemoryCache) ClearAll() error { + bc.lock.Lock() + defer bc.lock.Unlock() + bc.items = make(map[string]*MemoryItem) + return nil +} + +// Start activates the file cache; it will +func (bc *MemoryCache) StartAndGC(config string) error { + var cf map[string]int + json.Unmarshal([]byte(config), &cf) + if _, ok := cf["every"]; !ok { + cf["interval"] = DefaultEvery + } + dur, err := time.ParseDuration(fmt.Sprintf("%ds", cf["interval"])) + if err != nil { + return err + } + bc.Every = cf["interval"] + bc.dur = dur + go bc.vaccuum() + return nil +} + +func (bc *MemoryCache) vaccuum() { + if bc.Every < 1 { + return + } + for { + <-time.After(time.Duration(bc.dur)) + if bc.items == nil { + return + } + for name, _ := range bc.items { + bc.item_expired(name) + } + } +} + +// item_expired returns true if an item is expired. +func (bc *MemoryCache) item_expired(name string) bool { + bc.lock.Lock() + defer bc.lock.Unlock() + itm, ok := bc.items[name] + if !ok { + return true + } + dur := time.Now().Sub(itm.Lastaccess) + sec, err := strconv.Atoi(fmt.Sprintf("%0.0f", dur.Seconds())) + if err != nil { + delete(bc.items, name) + return true + } else if sec >= itm.expired { + delete(bc.items, name) + return true + } + return false +} + +func init() { + Register("memory", NewMemoryCache()) +} diff --git a/cache/redis.go b/cache/redis.go new file mode 100644 index 00000000..6c39ce20 --- /dev/null +++ b/cache/redis.go @@ -0,0 +1,97 @@ +package cache + +import ( + "encoding/json" + "errors" + "github.com/garyburd/redigo/redis" +) + +var ( + DefaultKey string = "beecacheRedis" +) + +type RedisCache struct { + c redis.Conn + conninfo string + key string +} + +func NewRedisCache() *RedisCache { + return &RedisCache{key: DefaultKey} +} + +func (rc *RedisCache) Get(key string) interface{} { + if rc.c == nil { + rc.c = rc.connectInit() + } + v, err := rc.c.Do("HGET", rc.key, key) + if err != nil { + return nil + } + return v +} + +func (rc *RedisCache) Put(key string, val interface{}, timeout int) error { + if rc.c == nil { + rc.c = rc.connectInit() + } + _, err := rc.c.Do("HSET", rc.key, key, val) + return err +} + +func (rc *RedisCache) Delete(key string) error { + if rc.c == nil { + rc.c = rc.connectInit() + } + _, err := rc.c.Do("HDEL", rc.key, key) + return err +} + +func (rc *RedisCache) IsExist(key string) bool { + if rc.c == nil { + rc.c = rc.connectInit() + } + v, err := redis.Bool(rc.c.Do("HEXISTS", rc.key, key)) + if err != nil { + return false + } + return v +} + +func (rc *RedisCache) ClearAll() error { + if rc.c == nil { + rc.c = rc.connectInit() + } + _, err := rc.c.Do("DEL", rc.key) + return err +} + +func (rc *RedisCache) StartAndGC(config string) error { + var cf map[string]string + json.Unmarshal([]byte(config), &cf) + if _, ok := cf["key"]; !ok { + cf["key"] = DefaultKey + } + if _, ok := cf["conn"]; !ok { + return errors.New("config has no conn key") + } + rc.key = cf["key"] + rc.conninfo = cf["conn"] + rc.c = rc.connectInit() + if rc.c == nil { + return errors.New("dial tcp conn error") + } + return nil +} + +func (rc *RedisCache) connectInit() redis.Conn { + c, err := redis.Dial("tcp", rc.conninfo) + if err != nil { + return nil + } + return c +} + +func init() { + Register("redis", NewRedisCache()) +}