add cache module
Showing
6 changed files
with
472 additions
and
0 deletions
cache/README.md
0 → 100644
| 1 | ## cache | ||
| 2 | cache is a golang cache manager. It can use cache for many adapters. The repo is inspired by `database/sql` . | ||
| 3 | |||
| 4 | ##How to install | ||
| 5 | |||
| 6 | go get github.com/astaxie/beego/cache | ||
| 7 | |||
| 8 | |||
| 9 | ##how many adapter support | ||
| 10 | |||
| 11 | Now this cache support memory/redis/memcache | ||
| 12 | |||
| 13 | ## how to use it | ||
| 14 | first you must import it | ||
| 15 | |||
| 16 | |||
| 17 | import ( | ||
| 18 | "github.com/astaxie/beego/cache" | ||
| 19 | ) | ||
| 20 | |||
| 21 | then init an Cache(memory adapter) | ||
| 22 | |||
| 23 | bm, err := NewCache("memory", `{"interval":60}`) | ||
| 24 | |||
| 25 | use it like this: | ||
| 26 | |||
| 27 | bm.Put("astaxie", 1, 10) | ||
| 28 | bm.Get("astaxie") | ||
| 29 | bm.IsExist("astaxie") | ||
| 30 | bm.Delete("astaxie") | ||
| 31 | |||
| 32 | ## memory adapter | ||
| 33 | memory adapter config like this: | ||
| 34 | |||
| 35 | {"interval":60} | ||
| 36 | |||
| 37 | interval means the gc time. The cache will every interval time to check wheather have item expired. | ||
| 38 | |||
| 39 | ## memcache adapter | ||
| 40 | memory adapter use the vitess's [memcache](code.google.com/p/vitess/go/memcache) client. | ||
| 41 | |||
| 42 | the config like this: | ||
| 43 | |||
| 44 | {"conn":"127.0.0.1:11211"} | ||
| 45 | |||
| 46 | |||
| 47 | ## redis adapter | ||
| 48 | redis adapter use the [redigo](github.com/garyburd/redigo/redis) client. | ||
| 49 | |||
| 50 | the config like this: | ||
| 51 | |||
| 52 | {"conn":":6039"} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
cache/cache.go
0 → 100644
| 1 | package cache | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | ) | ||
| 6 | |||
| 7 | type Cache interface { | ||
| 8 | Get(key string) interface{} | ||
| 9 | Put(key string, val interface{}, timeout int) error | ||
| 10 | Delete(key string) error | ||
| 11 | IsExist(key string) bool | ||
| 12 | ClearAll() error | ||
| 13 | StartAndGC(config string) error | ||
| 14 | } | ||
| 15 | |||
| 16 | var adapters = make(map[string]Cache) | ||
| 17 | |||
| 18 | // Register makes a cache adapter available by the adapter name. | ||
| 19 | // If Register is called twice with the same name or if driver is nil, | ||
| 20 | // it panics. | ||
| 21 | func Register(name string, adapter Cache) { | ||
| 22 | if adapter == nil { | ||
| 23 | panic("cache: Register adapter is nil") | ||
| 24 | } | ||
| 25 | if _, dup := adapters[name]; dup { | ||
| 26 | panic("cache: Register called twice for adapter " + name) | ||
| 27 | } | ||
| 28 | adapters[name] = adapter | ||
| 29 | } | ||
| 30 | |||
| 31 | //config is json {"interval":360} | ||
| 32 | func NewCache(adapterName, config string) (Cache, error) { | ||
| 33 | adapter, ok := adapters[adapterName] | ||
| 34 | if !ok { | ||
| 35 | return nil, fmt.Errorf("cache: unknown adaptername %q (forgotten import?)", adapterName) | ||
| 36 | } | ||
| 37 | adapter.StartAndGC(config) | ||
| 38 | return adapter, nil | ||
| 39 | } |
cache/cache_test.go
0 → 100644
| 1 | package cache | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "testing" | ||
| 5 | "time" | ||
| 6 | ) | ||
| 7 | |||
| 8 | func Test_cache(t *testing.T) { | ||
| 9 | bm, err := NewCache("memory", `{"interval":60}`) | ||
| 10 | if err != nil { | ||
| 11 | t.Error("init err") | ||
| 12 | } | ||
| 13 | if err = bm.Put("astaxie", 1, 10); err != nil { | ||
| 14 | t.Error("set Error", err) | ||
| 15 | } | ||
| 16 | if !bm.IsExist("astaxie") { | ||
| 17 | t.Error("check err") | ||
| 18 | } | ||
| 19 | |||
| 20 | if v := bm.Get("astaxie"); v.(int) != 1 { | ||
| 21 | t.Error("get err") | ||
| 22 | } | ||
| 23 | |||
| 24 | time.Sleep(70 * time.Second) | ||
| 25 | |||
| 26 | if bm.IsExist("astaxie") { | ||
| 27 | t.Error("check err") | ||
| 28 | } | ||
| 29 | |||
| 30 | if err = bm.Put("astaxie", 1, 10); err != nil { | ||
| 31 | t.Error("set Error", err) | ||
| 32 | } | ||
| 33 | |||
| 34 | bm.Delete("astaxie") | ||
| 35 | if bm.IsExist("astaxie") { | ||
| 36 | t.Error("delete err") | ||
| 37 | } | ||
| 38 | } |
cache/memcache.go
0 → 100644
| 1 | package cache | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "code.google.com/p/vitess/go/memcache" | ||
| 5 | "encoding/json" | ||
| 6 | "errors" | ||
| 7 | ) | ||
| 8 | |||
| 9 | type MemcacheCache struct { | ||
| 10 | c *memcache.Connection | ||
| 11 | conninfo string | ||
| 12 | } | ||
| 13 | |||
| 14 | func NewMemCache() *MemcacheCache { | ||
| 15 | return &MemcacheCache{} | ||
| 16 | } | ||
| 17 | |||
| 18 | func (rc *MemcacheCache) Get(key string) interface{} { | ||
| 19 | if rc.c == nil { | ||
| 20 | rc.c = rc.connectInit() | ||
| 21 | } | ||
| 22 | v, _, err := rc.c.Get(key) | ||
| 23 | if err != nil { | ||
| 24 | return nil | ||
| 25 | } | ||
| 26 | var contain interface{} | ||
| 27 | contain = v | ||
| 28 | return contain | ||
| 29 | } | ||
| 30 | |||
| 31 | func (rc *MemcacheCache) Put(key string, val interface{}, timeout int) error { | ||
| 32 | if rc.c == nil { | ||
| 33 | rc.c = rc.connectInit() | ||
| 34 | } | ||
| 35 | v, ok := val.(string) | ||
| 36 | if !ok { | ||
| 37 | return errors.New("val must string") | ||
| 38 | } | ||
| 39 | stored, err := rc.c.Set(key, 0, uint64(timeout), []byte(v)) | ||
| 40 | if err == nil && stored == false { | ||
| 41 | return errors.New("stored fail") | ||
| 42 | } | ||
| 43 | return err | ||
| 44 | } | ||
| 45 | |||
| 46 | func (rc *MemcacheCache) Delete(key string) error { | ||
| 47 | if rc.c == nil { | ||
| 48 | rc.c = rc.connectInit() | ||
| 49 | } | ||
| 50 | _, err := rc.c.Delete(key) | ||
| 51 | return err | ||
| 52 | } | ||
| 53 | |||
| 54 | func (rc *MemcacheCache) IsExist(key string) bool { | ||
| 55 | if rc.c == nil { | ||
| 56 | rc.c = rc.connectInit() | ||
| 57 | } | ||
| 58 | v, _, err := rc.c.Get(key) | ||
| 59 | if err != nil { | ||
| 60 | return false | ||
| 61 | } | ||
| 62 | if len(v) == 0 { | ||
| 63 | return false | ||
| 64 | } else { | ||
| 65 | return true | ||
| 66 | } | ||
| 67 | return true | ||
| 68 | } | ||
| 69 | |||
| 70 | func (rc *MemcacheCache) ClearAll() error { | ||
| 71 | if rc.c == nil { | ||
| 72 | rc.c = rc.connectInit() | ||
| 73 | } | ||
| 74 | err := rc.c.FlushAll() | ||
| 75 | return err | ||
| 76 | } | ||
| 77 | |||
| 78 | func (rc *MemcacheCache) StartAndGC(config string) error { | ||
| 79 | var cf map[string]string | ||
| 80 | json.Unmarshal([]byte(config), &cf) | ||
| 81 | if _, ok := cf["conn"]; !ok { | ||
| 82 | return errors.New("config has no conn key") | ||
| 83 | } | ||
| 84 | rc.conninfo = cf["conn"] | ||
| 85 | rc.c = rc.connectInit() | ||
| 86 | if rc.c == nil { | ||
| 87 | return errors.New("dial tcp conn error") | ||
| 88 | } | ||
| 89 | return nil | ||
| 90 | } | ||
| 91 | |||
| 92 | func (rc *MemcacheCache) connectInit() *memcache.Connection { | ||
| 93 | c, err := memcache.Connect(rc.conninfo) | ||
| 94 | if err != nil { | ||
| 95 | return nil | ||
| 96 | } | ||
| 97 | return c | ||
| 98 | } | ||
| 99 | |||
| 100 | func init() { | ||
| 101 | Register("memcache", NewMemCache()) | ||
| 102 | } |
cache/memory.go
0 → 100644
| 1 | package cache | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "encoding/json" | ||
| 5 | "errors" | ||
| 6 | "fmt" | ||
| 7 | "strconv" | ||
| 8 | "sync" | ||
| 9 | "time" | ||
| 10 | ) | ||
| 11 | |||
| 12 | var ( | ||
| 13 | DefaultEvery int = 60 // 1 minute | ||
| 14 | ) | ||
| 15 | |||
| 16 | type MemoryItem struct { | ||
| 17 | val interface{} | ||
| 18 | Lastaccess time.Time | ||
| 19 | expired int | ||
| 20 | } | ||
| 21 | |||
| 22 | func (itm *MemoryItem) Access() interface{} { | ||
| 23 | itm.Lastaccess = time.Now() | ||
| 24 | return itm.val | ||
| 25 | } | ||
| 26 | |||
| 27 | type MemoryCache struct { | ||
| 28 | lock sync.RWMutex | ||
| 29 | dur time.Duration | ||
| 30 | items map[string]*MemoryItem | ||
| 31 | Every int // Run an expiration check Every seconds | ||
| 32 | } | ||
| 33 | |||
| 34 | // NewDefaultCache returns a new FileCache with sane defaults. | ||
| 35 | func NewMemoryCache() *MemoryCache { | ||
| 36 | cache := MemoryCache{items: make(map[string]*MemoryItem)} | ||
| 37 | return &cache | ||
| 38 | } | ||
| 39 | |||
| 40 | func (bc *MemoryCache) Get(name string) interface{} { | ||
| 41 | bc.lock.RLock() | ||
| 42 | defer bc.lock.RUnlock() | ||
| 43 | itm, ok := bc.items[name] | ||
| 44 | if !ok { | ||
| 45 | return nil | ||
| 46 | } | ||
| 47 | return itm.Access() | ||
| 48 | } | ||
| 49 | |||
| 50 | func (bc *MemoryCache) Put(name string, value interface{}, expired int) error { | ||
| 51 | bc.lock.Lock() | ||
| 52 | defer bc.lock.Unlock() | ||
| 53 | t := MemoryItem{val: value, Lastaccess: time.Now(), expired: expired} | ||
| 54 | if _, ok := bc.items[name]; ok { | ||
| 55 | return errors.New("the key is exist") | ||
| 56 | } else { | ||
| 57 | bc.items[name] = &t | ||
| 58 | } | ||
| 59 | return nil | ||
| 60 | } | ||
| 61 | |||
| 62 | func (bc *MemoryCache) Delete(name string) error { | ||
| 63 | bc.lock.Lock() | ||
| 64 | defer bc.lock.Unlock() | ||
| 65 | if _, ok := bc.items[name]; !ok { | ||
| 66 | return errors.New("key not exist") | ||
| 67 | } | ||
| 68 | delete(bc.items, name) | ||
| 69 | _, valid := bc.items[name] | ||
| 70 | if valid { | ||
| 71 | return errors.New("delete key error") | ||
| 72 | } | ||
| 73 | return nil | ||
| 74 | } | ||
| 75 | |||
| 76 | func (bc *MemoryCache) IsExist(name string) bool { | ||
| 77 | bc.lock.RLock() | ||
| 78 | defer bc.lock.RUnlock() | ||
| 79 | _, ok := bc.items[name] | ||
| 80 | return ok | ||
| 81 | } | ||
| 82 | |||
| 83 | func (bc *MemoryCache) ClearAll() error { | ||
| 84 | bc.lock.Lock() | ||
| 85 | defer bc.lock.Unlock() | ||
| 86 | bc.items = make(map[string]*MemoryItem) | ||
| 87 | return nil | ||
| 88 | } | ||
| 89 | |||
| 90 | // Start activates the file cache; it will | ||
| 91 | func (bc *MemoryCache) StartAndGC(config string) error { | ||
| 92 | var cf map[string]int | ||
| 93 | json.Unmarshal([]byte(config), &cf) | ||
| 94 | if _, ok := cf["every"]; !ok { | ||
| 95 | cf["interval"] = DefaultEvery | ||
| 96 | } | ||
| 97 | dur, err := time.ParseDuration(fmt.Sprintf("%ds", cf["interval"])) | ||
| 98 | if err != nil { | ||
| 99 | return err | ||
| 100 | } | ||
| 101 | bc.Every = cf["interval"] | ||
| 102 | bc.dur = dur | ||
| 103 | go bc.vaccuum() | ||
| 104 | return nil | ||
| 105 | } | ||
| 106 | |||
| 107 | func (bc *MemoryCache) vaccuum() { | ||
| 108 | if bc.Every < 1 { | ||
| 109 | return | ||
| 110 | } | ||
| 111 | for { | ||
| 112 | <-time.After(time.Duration(bc.dur)) | ||
| 113 | if bc.items == nil { | ||
| 114 | return | ||
| 115 | } | ||
| 116 | for name, _ := range bc.items { | ||
| 117 | bc.item_expired(name) | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | // item_expired returns true if an item is expired. | ||
| 123 | func (bc *MemoryCache) item_expired(name string) bool { | ||
| 124 | bc.lock.Lock() | ||
| 125 | defer bc.lock.Unlock() | ||
| 126 | itm, ok := bc.items[name] | ||
| 127 | if !ok { | ||
| 128 | return true | ||
| 129 | } | ||
| 130 | dur := time.Now().Sub(itm.Lastaccess) | ||
| 131 | sec, err := strconv.Atoi(fmt.Sprintf("%0.0f", dur.Seconds())) | ||
| 132 | if err != nil { | ||
| 133 | delete(bc.items, name) | ||
| 134 | return true | ||
| 135 | } else if sec >= itm.expired { | ||
| 136 | delete(bc.items, name) | ||
| 137 | return true | ||
| 138 | } | ||
| 139 | return false | ||
| 140 | } | ||
| 141 | |||
| 142 | func init() { | ||
| 143 | Register("memory", NewMemoryCache()) | ||
| 144 | } |
cache/redis.go
0 → 100644
| 1 | package cache | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "encoding/json" | ||
| 5 | "errors" | ||
| 6 | "github.com/garyburd/redigo/redis" | ||
| 7 | ) | ||
| 8 | |||
| 9 | var ( | ||
| 10 | DefaultKey string = "beecacheRedis" | ||
| 11 | ) | ||
| 12 | |||
| 13 | type RedisCache struct { | ||
| 14 | c redis.Conn | ||
| 15 | conninfo string | ||
| 16 | key string | ||
| 17 | } | ||
| 18 | |||
| 19 | func NewRedisCache() *RedisCache { | ||
| 20 | return &RedisCache{key: DefaultKey} | ||
| 21 | } | ||
| 22 | |||
| 23 | func (rc *RedisCache) Get(key string) interface{} { | ||
| 24 | if rc.c == nil { | ||
| 25 | rc.c = rc.connectInit() | ||
| 26 | } | ||
| 27 | v, err := rc.c.Do("HGET", rc.key, key) | ||
| 28 | if err != nil { | ||
| 29 | return nil | ||
| 30 | } | ||
| 31 | return v | ||
| 32 | } | ||
| 33 | |||
| 34 | func (rc *RedisCache) Put(key string, val interface{}, timeout int) error { | ||
| 35 | if rc.c == nil { | ||
| 36 | rc.c = rc.connectInit() | ||
| 37 | } | ||
| 38 | _, err := rc.c.Do("HSET", rc.key, key, val) | ||
| 39 | return err | ||
| 40 | } | ||
| 41 | |||
| 42 | func (rc *RedisCache) Delete(key string) error { | ||
| 43 | if rc.c == nil { | ||
| 44 | rc.c = rc.connectInit() | ||
| 45 | } | ||
| 46 | _, err := rc.c.Do("HDEL", rc.key, key) | ||
| 47 | return err | ||
| 48 | } | ||
| 49 | |||
| 50 | func (rc *RedisCache) IsExist(key string) bool { | ||
| 51 | if rc.c == nil { | ||
| 52 | rc.c = rc.connectInit() | ||
| 53 | } | ||
| 54 | v, err := redis.Bool(rc.c.Do("HEXISTS", rc.key, key)) | ||
| 55 | if err != nil { | ||
| 56 | return false | ||
| 57 | } | ||
| 58 | return v | ||
| 59 | } | ||
| 60 | |||
| 61 | func (rc *RedisCache) ClearAll() error { | ||
| 62 | if rc.c == nil { | ||
| 63 | rc.c = rc.connectInit() | ||
| 64 | } | ||
| 65 | _, err := rc.c.Do("DEL", rc.key) | ||
| 66 | return err | ||
| 67 | } | ||
| 68 | |||
| 69 | func (rc *RedisCache) StartAndGC(config string) error { | ||
| 70 | var cf map[string]string | ||
| 71 | json.Unmarshal([]byte(config), &cf) | ||
| 72 | if _, ok := cf["key"]; !ok { | ||
| 73 | cf["key"] = DefaultKey | ||
| 74 | } | ||
| 75 | if _, ok := cf["conn"]; !ok { | ||
| 76 | return errors.New("config has no conn key") | ||
| 77 | } | ||
| 78 | rc.key = cf["key"] | ||
| 79 | rc.conninfo = cf["conn"] | ||
| 80 | rc.c = rc.connectInit() | ||
| 81 | if rc.c == nil { | ||
| 82 | return errors.New("dial tcp conn error") | ||
| 83 | } | ||
| 84 | return nil | ||
| 85 | } | ||
| 86 | |||
| 87 | func (rc *RedisCache) connectInit() redis.Conn { | ||
| 88 | c, err := redis.Dial("tcp", rc.conninfo) | ||
| 89 | if err != nil { | ||
| 90 | return nil | ||
| 91 | } | ||
| 92 | return c | ||
| 93 | } | ||
| 94 | |||
| 95 | func init() { | ||
| 96 | Register("redis", NewRedisCache()) | ||
| 97 | } |
-
Please register or sign in to post a comment