├── .travis.yml ├── LICENSE ├── cache_test.go ├── lru ├── lru_test.go └── lru.go ├── redis ├── redis.go └── redis_test.go ├── memcache ├── memcache.go └── memcache_test.go ├── README.md ├── memory_test.go ├── memory.go └── cache.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | go: 5 | - 1.5 6 | - 1.6 7 | - tip 8 | 9 | env: 10 | 11 | services: 12 | - redis-server 13 | - memcached 14 | 15 | before_install: 16 | - export PATH=$PATH:$GOPATH/bin 17 | - go get github.com/bradfitz/gomemcache/memcache 18 | - go get gopkg.in/redis.v3 19 | - go get golang.org/x/tools/cmd/cover 20 | - go get github.com/modocache/gover 21 | - go get github.com/mattn/goveralls 22 | 23 | install: 24 | - go get -t -v ./... 25 | 26 | script: 27 | - go vet ./... 28 | - go test -v -race ./... 29 | - diff -u <(echo -n) <(gofmt -d -s .) 30 | - go test -v -coverprofile=baa.coverprofile 31 | - gover 32 | - goveralls -coverprofile=gover.coverprofile -service=travis-ci 33 | 34 | notifications: 35 | email: 36 | on_success: change 37 | on_failure: always 38 | 39 | matrix: 40 | allow_failures: 41 | - go: tip 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 go-baa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestCacheGob1(t *testing.T) { 10 | Convey("cache Gob", t, func() { 11 | item := NewItem("1", 6) 12 | b, err := item.Encode() 13 | item2, err2 := b.Item() 14 | 15 | Convey("encode", func() { 16 | So(err, ShouldBeNil) 17 | }) 18 | 19 | Convey("decode", func() { 20 | So(err2, ShouldBeNil) 21 | So(item2.Val, ShouldEqual, "1") 22 | }) 23 | }) 24 | } 25 | 26 | func TestCache1(t *testing.T) { 27 | c := New(Options{ 28 | Name: "test", 29 | Adapter: "memory", 30 | }) 31 | 32 | Convey("cache", t, func() { 33 | 34 | Convey("set", func() { 35 | err := c.Set("test", "1", 6) 36 | So(err, ShouldBeNil) 37 | }) 38 | 39 | Convey("get", func() { 40 | var v string 41 | c.Get("test", &v) 42 | So(v, ShouldEqual, "1") 43 | }) 44 | }) 45 | } 46 | 47 | func TestCacheMulti(t *testing.T) { 48 | Convey("test multiple adapter", t, func() { 49 | c1 := New(Options{ 50 | Name: "test1", 51 | Adapter: "memory", 52 | Config: map[string]interface{}{ 53 | "host": "127.0.0.1", 54 | "port": "6379", 55 | "password": "", 56 | "poolsize": 10, 57 | }, 58 | }) 59 | c2 := New(Options{ 60 | Name: "test2", 61 | Adapter: "memory", 62 | Config: map[string]interface{}{ 63 | "host": "10.1.1.31", 64 | "port": "6379", 65 | "password": "", 66 | "poolsize": 10, 67 | }, 68 | }) 69 | So(c1, ShouldNotEqual, c2) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /lru/lru_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package lru 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | type simpleStruct struct { 25 | int 26 | string 27 | } 28 | 29 | type complexStruct struct { 30 | int 31 | simpleStruct 32 | } 33 | 34 | var getTests = []struct { 35 | name string 36 | keyToAdd interface{} 37 | keyToGet interface{} 38 | expectedOk bool 39 | }{ 40 | {"string_hit", "myKey", "myKey", true}, 41 | {"string_miss", "myKey", "nonsense", false}, 42 | {"simple_struct_hit", simpleStruct{1, "two"}, simpleStruct{1, "two"}, true}, 43 | {"simeple_struct_miss", simpleStruct{1, "two"}, simpleStruct{0, "noway"}, false}, 44 | {"complex_struct_hit", complexStruct{1, simpleStruct{2, "three"}}, 45 | complexStruct{1, simpleStruct{2, "three"}}, true}, 46 | } 47 | 48 | func TestGet(t *testing.T) { 49 | for _, tt := range getTests { 50 | lru := New(0) 51 | lru.Add(tt.keyToAdd, 1234) 52 | val, ok := lru.Get(tt.keyToGet) 53 | if ok != tt.expectedOk { 54 | t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok) 55 | } else if ok && val != 1234 { 56 | t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val) 57 | } 58 | } 59 | } 60 | 61 | func TestRemove(t *testing.T) { 62 | lru := New(0) 63 | lru.Add("myKey", 1234) 64 | if val, ok := lru.Get("myKey"); !ok { 65 | t.Fatal("TestRemove returned no match") 66 | } else if val != 1234 { 67 | t.Fatalf("TestRemove failed. Expected %d, got %v", 1234, val) 68 | } 69 | 70 | lru.Remove("myKey") 71 | if _, ok := lru.Get("myKey"); ok { 72 | t.Fatal("TestRemove returned a removed entry") 73 | } 74 | } 75 | 76 | func TestEvict(t *testing.T) { 77 | evictedKeys := make([]Key, 0) 78 | onEvictedFun := func(key Key, value interface{}) { 79 | evictedKeys = append(evictedKeys, key) 80 | } 81 | 82 | lru := New(20) 83 | lru.OnEvicted = onEvictedFun 84 | for i := 0; i < 22; i++ { 85 | lru.Add(fmt.Sprintf("myKey%d", i), 1234) 86 | } 87 | 88 | if len(evictedKeys) != 2 { 89 | t.Fatalf("got %d evicted keys; want 2", len(evictedKeys)) 90 | } 91 | if evictedKeys[0] != Key("myKey0") { 92 | t.Fatalf("got %v in first evicted key; want %s", evictedKeys[0], "myKey0") 93 | } 94 | if evictedKeys[1] != Key("myKey1") { 95 | t.Fatalf("got %v in second evicted key; want %s", evictedKeys[1], "myKey1") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-baa/cache" 8 | "gopkg.in/redis.v3" 9 | ) 10 | 11 | // Redis implement a redis cache adapter for cacher 12 | type Redis struct { 13 | Name string 14 | Prefix string 15 | handle *redis.Client 16 | } 17 | 18 | // New create a cache instance of redis 19 | func New() cache.Cacher { 20 | return new(Redis) 21 | } 22 | 23 | // Exist return true if value cached by given key 24 | func (c *Redis) Exist(key string) bool { 25 | ok, err := c.handle.Exists(c.Prefix + key).Result() 26 | if err == nil && ok { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | // Get returns value by given key 33 | func (c *Redis) Get(key string, out interface{}) error { 34 | v, err := c.handle.Get(c.Prefix + key).Bytes() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | if cache.SimpleValue(v, out) { 40 | return nil 41 | } 42 | 43 | item, err := cache.ItemBinary(v).Item() 44 | if err != nil { 45 | return err 46 | } 47 | return item.Decode(out) 48 | } 49 | 50 | // Set cache value by given key, cache ttl second 51 | func (c *Redis) Set(key string, v interface{}, ttl int64) error { 52 | if !cache.SimpleType(v) { 53 | item := cache.NewItem(v, ttl) 54 | b, err := item.Encode() 55 | if err != nil { 56 | return err 57 | } 58 | v = []byte(b) 59 | } 60 | return c.handle.Set(c.Prefix+key, v, time.Second*time.Duration(ttl)).Err() 61 | } 62 | 63 | // Incr increases cached int-type value by given key as a counter 64 | // if key not exist, before increase set value with zero 65 | func (c *Redis) Incr(key string) (int64, error) { 66 | t := c.handle.Incr(c.Prefix + key) 67 | if t.Err() != nil { 68 | return 0, t.Err() 69 | } 70 | return t.Val(), nil 71 | } 72 | 73 | // Decr decreases cached int-type value by given key as a counter 74 | // if key not exist, return errors 75 | func (c *Redis) Decr(key string) (int64, error) { 76 | t := c.handle.Decr(c.Prefix + key) 77 | if t.Err() != nil { 78 | return 0, t.Err() 79 | } 80 | return t.Val(), nil 81 | } 82 | 83 | // Delete delete cached data by given key 84 | func (c *Redis) Delete(key string) error { 85 | return c.handle.Del(c.Prefix + key).Err() 86 | } 87 | 88 | // Flush flush cacher 89 | func (c *Redis) Flush() error { 90 | return c.handle.FlushDb().Err() 91 | } 92 | 93 | // Start new a cacher and start service 94 | func (c *Redis) Start(o cache.Options) error { 95 | c.Name = o.Name 96 | c.Prefix = o.Prefix 97 | var host, port, pass string 98 | var poolSzie int 99 | if val, ok := o.Config["host"]; ok { 100 | host = val.(string) 101 | } else { 102 | host = "127.0.0.1" 103 | } 104 | if val, ok := o.Config["port"]; ok { 105 | port = val.(string) 106 | } else { 107 | port = "6379" 108 | } 109 | if val, ok := o.Config["password"]; ok { 110 | pass = val.(string) 111 | } 112 | if val, ok := o.Config["poolsize"]; ok { 113 | poolSzie = val.(int) 114 | } else { 115 | poolSzie = 10 116 | } 117 | c.handle = redis.NewClient(&redis.Options{ 118 | Addr: host + ":" + port, 119 | Password: pass, 120 | DB: 0, 121 | PoolSize: poolSzie, 122 | }) 123 | pong, err := c.handle.Ping().Result() 124 | if err != nil || pong != "PONG" { 125 | return fmt.Errorf("redis connect err: %s", err) 126 | } 127 | return nil 128 | } 129 | 130 | func init() { 131 | cache.Register("redis", New) 132 | } 133 | -------------------------------------------------------------------------------- /memcache/memcache.go: -------------------------------------------------------------------------------- 1 | package memcache 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bradfitz/gomemcache/memcache" 7 | "github.com/go-baa/cache" 8 | ) 9 | 10 | // Memcache implement a memcache cache adapter for cacher 11 | type Memcache struct { 12 | Name string 13 | Prefix string 14 | handle *memcache.Client 15 | } 16 | 17 | // New create a cache instance of memcache 18 | func New() cache.Cacher { 19 | return new(Memcache) 20 | } 21 | 22 | // Exist return true if value cached by given key 23 | func (c *Memcache) Exist(key string) bool { 24 | _, err := c.handle.Get(c.Prefix + key) 25 | if err == nil { 26 | return true 27 | } 28 | return false 29 | } 30 | 31 | // Get returns value by given key 32 | func (c *Memcache) Get(key string, out interface{}) error { 33 | v, err := c.handle.Get(c.Prefix + key) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | if cache.SimpleValue(v.Value, out) { 39 | return nil 40 | } 41 | 42 | item, err := cache.ItemBinary(v.Value).Item() 43 | if err != nil { 44 | return err 45 | } 46 | return item.Decode(out) 47 | } 48 | 49 | // Set cache value by given key, cache ttl second 50 | func (c *Memcache) Set(key string, v interface{}, ttl int64) error { 51 | var t []byte 52 | if !cache.SimpleType(v) { 53 | item := cache.NewItem(v, ttl) 54 | b, err := item.Encode() 55 | if err != nil { 56 | return err 57 | } 58 | t = []byte(b) 59 | } else { 60 | t = []byte(fmt.Sprintf("%v", v)) 61 | } 62 | return c.handle.Set(&memcache.Item{Key: c.Prefix + key, Value: t, Expiration: int32(ttl)}) 63 | } 64 | 65 | // Incr increases cached int-type value by given key as a counter 66 | // if key not exist, before increase set value with zero 67 | func (c *Memcache) Incr(key string) (int64, error) { 68 | v, err := c.handle.Increment(c.Prefix+key, 1) 69 | if err != nil { 70 | if err == memcache.ErrCacheMiss { 71 | err = c.Set(key, 0, 0) 72 | if err == nil { 73 | return c.Incr(key) 74 | } 75 | } 76 | } 77 | return int64(v), err 78 | } 79 | 80 | // Decr decreases cached int-type value by given key as a counter 81 | // if key not exist, return errors 82 | func (c *Memcache) Decr(key string) (int64, error) { 83 | v, err := c.handle.Decrement(c.Prefix+key, 1) 84 | if err != nil { 85 | if err == memcache.ErrCacheMiss { 86 | err = c.Set(key, 0, 0) 87 | if err == nil { 88 | return c.Decr(key) 89 | } 90 | } 91 | } 92 | return int64(v), err 93 | } 94 | 95 | // Delete delete cached data by given key 96 | func (c *Memcache) Delete(key string) error { 97 | return c.handle.Delete(c.Prefix + key) 98 | } 99 | 100 | // Flush flush cacher 101 | func (c *Memcache) Flush() error { 102 | return c.handle.FlushAll() 103 | } 104 | 105 | // Start new a cacher and start service 106 | func (c *Memcache) Start(o cache.Options) error { 107 | c.Name = o.Name 108 | c.Prefix = o.Prefix 109 | var host, port string 110 | if val, ok := o.Config["host"]; ok { 111 | host = val.(string) 112 | } else { 113 | host = "127.0.0.1" 114 | } 115 | if val, ok := o.Config["port"]; ok { 116 | port = val.(string) 117 | } else { 118 | port = "11211" 119 | } 120 | 121 | c.handle = memcache.New(host + ":" + port) 122 | err := c.handle.Set(&memcache.Item{Key: c.Prefix + "foo", Value: []byte("bar")}) 123 | if err != nil { 124 | return fmt.Errorf("memcache connect err: %s", err) 125 | } 126 | return nil 127 | } 128 | 129 | func init() { 130 | cache.Register("memcache", New) 131 | } 132 | -------------------------------------------------------------------------------- /memcache/memcache_test.go: -------------------------------------------------------------------------------- 1 | package memcache 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/go-baa/cache" 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | // init a global cacher 15 | var c cache.Cacher 16 | 17 | func TestCacheMemcache1(t *testing.T) { 18 | Convey("cache redis", t, func() { 19 | Convey("set", func() { 20 | err := c.Set("test", "1", 2) 21 | So(err, ShouldBeNil) 22 | }) 23 | 24 | Convey("get", func() { 25 | var v string 26 | c.Get("test", &v) 27 | So(v, ShouldEqual, "1") 28 | }) 29 | 30 | Convey("get gc", func() { 31 | var v string 32 | time.Sleep(time.Second * 3) 33 | c.Get("test", &v) 34 | So(v, ShouldBeEmpty) 35 | }) 36 | 37 | Convey("set struct", func() { 38 | type b struct { 39 | Name string 40 | } 41 | gob.Register(b{}) 42 | v1 := b{"test"} 43 | err := c.Set("test", v1, 6) 44 | So(err, ShouldBeNil) 45 | var v2 b 46 | c.Get("test", &v2) 47 | So(v2.Name, ShouldEqual, v1.Name) 48 | var v3 *b 49 | err = c.Get("test", v3) 50 | So(err, ShouldNotBeNil) 51 | t.Logf("Get %#v: %v\n", v3, err) 52 | err = c.Get("test", &v3) 53 | So(err, ShouldBeNil) 54 | So(v3.Name, ShouldEqual, v1.Name) 55 | v4 := new(b) 56 | c.Get("test", v4) 57 | So(v4.Name, ShouldEqual, v1.Name) 58 | var v5 interface{} 59 | err = c.Get("test", &v5) 60 | So(err, ShouldNotBeNil) 61 | t.Logf("Get %#v: %v\n", v5, err) 62 | }) 63 | 64 | Convey("incr/decr", func() { 65 | c.Set("test", 1, 10) 66 | v, err := c.Incr("test") 67 | v, err = c.Incr("test") 68 | So(err, ShouldBeNil) 69 | So(v, ShouldEqual, 3) 70 | v, err = c.Decr("test") 71 | So(v, ShouldEqual, 2) 72 | c.Delete("test1") 73 | v, err = c.Incr("test1") 74 | So(v, ShouldEqual, 1) 75 | c.Delete("test2") 76 | v, err = c.Decr("test2") 77 | So(err, ShouldBeNil) 78 | So(v, ShouldEqual, 0) 79 | err = c.Delete("test") 80 | So(err, ShouldBeNil) 81 | c.Set("test", int16(1), 10) 82 | v, err = c.Incr("test") 83 | So(err, ShouldBeNil) 84 | So(v, ShouldEqual, 2) 85 | c.Set("test", uint16(5), 10) 86 | v, err = c.Incr("test") 87 | So(err, ShouldBeNil) 88 | So(v, ShouldEqual, 6) 89 | c.Set("test", 5.1, 10) 90 | v, err = c.Incr("test") 91 | So(err, ShouldNotBeNil) 92 | t.Logf("incr float: %#v, %v\n", v, err) 93 | }) 94 | 95 | Convey("flush", func() { 96 | err := c.Flush() 97 | So(err, ShouldBeNil) 98 | }) 99 | 100 | Convey("exists", func() { 101 | c.Set("test1", 1, 10) 102 | c.Incr("test1") 103 | ok := c.Exist("test1") 104 | So(ok, ShouldBeTrue) 105 | ok = c.Exist("testNotExist2") 106 | So(ok, ShouldBeFalse) 107 | }) 108 | 109 | Convey("large item", func() { 110 | v := strings.Repeat("A", 1024*1025) 111 | err := c.Set("test", v, 30) 112 | So(err, ShouldNotBeNil) 113 | v = strings.Repeat("A", 1024*513) 114 | err = c.Set("test2", v, 30) 115 | err = c.Set("test3", v, 30) 116 | So(err, ShouldBeNil) 117 | err = c.Delete("test3") 118 | So(err, ShouldBeNil) 119 | }) 120 | }) 121 | } 122 | 123 | func BenchmarkCacheMemorySet(b *testing.B) { 124 | for i := 0; i < b.N; i++ { 125 | c.Set(fmt.Sprintf("test%d", i), 1, 1800) 126 | } 127 | } 128 | 129 | func BenchmarkCacheMemoryGet(b *testing.B) { 130 | var v string 131 | for i := 0; i < b.N; i++ { 132 | c.Get(fmt.Sprintf("test%d", i), v) 133 | } 134 | } 135 | 136 | func init() { 137 | c = cache.New(cache.Options{ 138 | Name: "test", 139 | Adapter: "memcache", 140 | Config: map[string]interface{}{ 141 | "host": "127.0.0.1", 142 | "port": "11211", 143 | }, 144 | }) 145 | } 146 | -------------------------------------------------------------------------------- /redis/redis_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/go-baa/cache" 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | // init a global cacher 15 | var c cache.Cacher 16 | 17 | func TestCacheRedis1(t *testing.T) { 18 | Convey("cache redis", t, func() { 19 | Convey("set", func() { 20 | err := c.Set("test", "1", 2) 21 | So(err, ShouldBeNil) 22 | }) 23 | 24 | Convey("get", func() { 25 | var v string 26 | c.Get("test", &v) 27 | So(v, ShouldEqual, "1") 28 | }) 29 | 30 | Convey("get gc", func() { 31 | var v string 32 | time.Sleep(time.Second * 3) 33 | c.Get("test", &v) 34 | So(v, ShouldBeEmpty) 35 | }) 36 | 37 | Convey("set struct", func() { 38 | type b struct { 39 | Name string 40 | } 41 | gob.Register(b{}) 42 | v1 := b{"test"} 43 | err := c.Set("test", v1, 6) 44 | So(err, ShouldBeNil) 45 | var v2 b 46 | c.Get("test", &v2) 47 | So(v2.Name, ShouldEqual, v1.Name) 48 | var v3 *b 49 | err = c.Get("test", v3) 50 | So(err, ShouldNotBeNil) 51 | t.Logf("Get %#v: %v\n", v3, err) 52 | err = c.Get("test", &v3) 53 | So(err, ShouldBeNil) 54 | So(v3.Name, ShouldEqual, v1.Name) 55 | v4 := new(b) 56 | c.Get("test", v4) 57 | So(v4.Name, ShouldEqual, v1.Name) 58 | var v5 interface{} 59 | err = c.Get("test", &v5) 60 | So(err, ShouldNotBeNil) 61 | t.Logf("Get %#v: %v\n", v5, err) 62 | }) 63 | 64 | Convey("incr/decr", func() { 65 | c.Set("test", 1, 10) 66 | v, err := c.Incr("test") 67 | v, err = c.Incr("test") 68 | So(err, ShouldBeNil) 69 | So(v, ShouldEqual, 3) 70 | v, err = c.Decr("test") 71 | So(v, ShouldEqual, 2) 72 | c.Delete("test1") 73 | v, err = c.Incr("test1") 74 | So(v, ShouldEqual, 1) 75 | c.Delete("test2") 76 | v, err = c.Decr("test2") 77 | So(err, ShouldBeNil) 78 | So(v, ShouldEqual, -1) 79 | err = c.Delete("test") 80 | So(err, ShouldBeNil) 81 | c.Set("test", int16(1), 10) 82 | v, err = c.Incr("test") 83 | So(err, ShouldBeNil) 84 | So(v, ShouldEqual, 2) 85 | c.Set("test", uint16(5), 10) 86 | v, err = c.Incr("test") 87 | So(err, ShouldBeNil) 88 | So(v, ShouldEqual, 6) 89 | c.Set("test", 5.1, 10) 90 | v, err = c.Incr("test") 91 | So(err, ShouldNotBeNil) 92 | t.Logf("incr float: %#v, %v\n", v, err) 93 | }) 94 | 95 | Convey("flush", func() { 96 | err := c.Flush() 97 | So(err, ShouldBeNil) 98 | }) 99 | 100 | Convey("exists", func() { 101 | c.Incr("test1") 102 | ok := c.Exist("test1") 103 | So(ok, ShouldBeTrue) 104 | ok = c.Exist("testNotExist") 105 | So(ok, ShouldBeFalse) 106 | }) 107 | 108 | Convey("large item", func() { 109 | v := strings.Repeat("A", 1024*1025) 110 | err := c.Set("test", v, 30) 111 | So(err, ShouldBeNil) 112 | v = strings.Repeat("A", 1024*513) 113 | err = c.Set("test2", v, 30) 114 | err = c.Set("test3", v, 30) 115 | So(err, ShouldBeNil) 116 | err = c.Delete("test3") 117 | So(err, ShouldBeNil) 118 | 119 | }) 120 | }) 121 | } 122 | 123 | func BenchmarkCacheRedisSet(b *testing.B) { 124 | for i := 0; i < b.N; i++ { 125 | c.Set(fmt.Sprintf("test%d", i), 1, 1800) 126 | } 127 | } 128 | 129 | func BenchmarkCacheRedisGet(b *testing.B) { 130 | var v string 131 | for i := 0; i < b.N; i++ { 132 | c.Get(fmt.Sprintf("test%d", i), &v) 133 | } 134 | } 135 | 136 | func init() { 137 | c = cache.New(cache.Options{ 138 | Name: "test", 139 | Adapter: "redis", 140 | Config: map[string]interface{}{ 141 | "host": "127.0.0.1", 142 | "port": "6379", 143 | "password": "", 144 | "poolsize": 100, 145 | }, 146 | }) 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cache [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/go-baa/cache) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/go-baa/cache/master/LICENSE) [![Build Status](http://img.shields.io/travis/go-baa/cache.svg?style=flat-square)](https://travis-ci.org/go-baa/cache) [![Coverage Status](http://img.shields.io/coveralls/go-baa/cache.svg?style=flat-square)](https://coveralls.io/r/go-baa/cache) 2 | 3 | baa module providers a cache management. 4 | 5 | ## Features 6 | 7 | - multi storage support: memory, file, memcache, redis 8 | - Get/Set/Incr/Decr/Delete/Exist/Flush/Start 9 | 10 | ## Getting Started 11 | 12 | ``` 13 | package main 14 | 15 | import ( 16 | "github.com/go-baa/baa" 17 | "github.com/go-baa/cache" 18 | ) 19 | 20 | func main() { 21 | // new app 22 | app := baa.New() 23 | 24 | // register cache 25 | app.SetDI("cache", cache.New(cache.Options{ 26 | Name: "cache", 27 | Prefix: "MyApp", 28 | Adapter: "memory", 29 | Config: map[string]interface{}{}, 30 | })) 31 | 32 | // router 33 | app.Get("/", func(c *baa.Context) { 34 | ca := c.DI("cache").(cache.Cacher) 35 | ca.Set("test", "baa", 10) 36 | var v string 37 | ca.Get("test", &v) 38 | c.String(200, v) 39 | }) 40 | 41 | // run app 42 | app.Run(":1323") 43 | } 44 | ``` 45 | 46 | you should import cache adpater before use it, like this: 47 | 48 | ``` 49 | import( 50 | "github.com/go-baa/baa" 51 | "github.com/go-baa/cache" 52 | _ "github.com/go-baa/cache/memcache" 53 | _ "github.com/go-baa/cache/redis" 54 | ) 55 | ``` 56 | 57 | adapter ``memory`` has build in, do not need import. 58 | 59 | ## Configuration 60 | 61 | ### Common 62 | 63 | **Name** 64 | 65 | ``string`` 66 | 67 | the cache name 68 | 69 | **Prefix** 70 | 71 | ``string`` 72 | 73 | the cache key prefix, used for isolate different cache instance/app. 74 | 75 | **Adapter** 76 | 77 | ``string`` 78 | 79 | the cache adapter name, choose support adapter: memory, file, memcache, redis. 80 | 81 | **Config** 82 | 83 | ``map[string]interface{}`` 84 | 85 | the cache adapter config, use a dict, values was diffrent with adapter. 86 | 87 | ### Adapter Memory 88 | 89 | **bytesLimit** 90 | 91 | ``int64`` 92 | 93 | set the memory cache memory limit, default is 128m 94 | 95 | **Usage** 96 | 97 | ``` 98 | app.SetDI("cache", cache.New(cache.Options{ 99 | Name: "cache", 100 | Prefix: "MyApp", 101 | Adapter: "memory", 102 | Config: map[string]interface{}{ 103 | "bytesLimit": int64(128 * 1024 * 1024), // 128m 104 | }, 105 | })) 106 | ``` 107 | 108 | ### Adapter Memcache 109 | 110 | **host** 111 | 112 | ``string`` 113 | 114 | memcached server host. 115 | 116 | **port** 117 | 118 | ``string`` 119 | 120 | memcached server port. 121 | 122 | **Usage** 123 | 124 | ``` 125 | app.SetDI("cache", cache.New(cache.Options{ 126 | Name: "cache", 127 | Prefix: "MyApp", 128 | Adapter: "memcache", 129 | Config: map[string]interface{}{ 130 | "host": "127.0.0.1", 131 | "port": "11211", 132 | }, 133 | })) 134 | ``` 135 | 136 | ### Adapter Redis 137 | 138 | **host** 139 | 140 | ``string`` 141 | 142 | redis server host. 143 | 144 | **port** 145 | 146 | ``string`` 147 | 148 | redis server port. 149 | 150 | **password** 151 | 152 | ``string`` 153 | 154 | redis server auth, default none. 155 | 156 | **poolsize** 157 | 158 | ``int`` 159 | 160 | connection pool size, default 10. 161 | 162 | **Usage** 163 | 164 | ``` 165 | app.SetDI("cache", cache.New(cache.Options{ 166 | Name: "cache", 167 | Prefix: "MyApp", 168 | Adapter: "redis", 169 | Config: map[string]interface{}{ 170 | "host": "127.0.0.1", 171 | "port": "6379", 172 | "password": "", 173 | "poolsize": 10, 174 | }, 175 | })) 176 | ``` 177 | -------------------------------------------------------------------------------- /lru/lru.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package lru implements an LRU cache. 18 | package lru 19 | 20 | import ( 21 | "container/list" 22 | "sync" 23 | ) 24 | 25 | // Cache is an LRU cache. It is not safe for concurrent access. 26 | type Cache struct { 27 | // MaxEntries is the maximum number of cache entries before 28 | // an item is evicted. Zero means no limit. 29 | MaxEntries int 30 | 31 | // OnEvicted optionally specificies a callback function to be 32 | // executed when an entry is purged from the cache. 33 | OnEvicted func(key Key, value interface{}) 34 | 35 | ll *list.List 36 | cache map[interface{}]*list.Element 37 | mutex sync.RWMutex 38 | } 39 | 40 | // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators 41 | type Key interface{} 42 | 43 | type entry struct { 44 | key Key 45 | value interface{} 46 | } 47 | 48 | // New creates a new Cache. 49 | // If maxEntries is zero, the cache has no limit and it's assumed 50 | // that eviction is done by the caller. 51 | func New(maxEntries int) *Cache { 52 | return &Cache{ 53 | MaxEntries: maxEntries, 54 | ll: list.New(), 55 | cache: make(map[interface{}]*list.Element), 56 | } 57 | } 58 | 59 | // Add adds a value to the cache. 60 | func (c *Cache) Add(key Key, value interface{}) { 61 | if c.cache == nil { 62 | c.cache = make(map[interface{}]*list.Element) 63 | c.ll = list.New() 64 | } 65 | 66 | c.mutex.RLock() 67 | ee, ok := c.cache[key] 68 | c.mutex.RUnlock() 69 | if ok { 70 | c.ll.MoveToFront(ee) 71 | ee.Value.(*entry).value = value 72 | return 73 | } 74 | ele := c.ll.PushFront(&entry{key, value}) 75 | c.mutex.Lock() 76 | c.cache[key] = ele 77 | c.mutex.Unlock() 78 | if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { 79 | c.RemoveOldest() 80 | } 81 | } 82 | 83 | // Get looks up a key's value from the cache. 84 | func (c *Cache) Get(key Key) (value interface{}, ok bool) { 85 | if c.cache == nil { 86 | return 87 | } 88 | 89 | c.mutex.RLock() 90 | ele, hit := c.cache[key] 91 | c.mutex.RUnlock() 92 | if hit { 93 | c.ll.MoveToFront(ele) 94 | return ele.Value.(*entry).value, true 95 | } 96 | return 97 | } 98 | 99 | // GetOldest returns the oldest item from the cache. 100 | func (c *Cache) GetOldest() (value interface{}) { 101 | if c.cache == nil { 102 | return 103 | } 104 | ele := c.ll.Back() 105 | return ele.Value.(*entry).value 106 | } 107 | 108 | // Remove removes the provided key from the cache. 109 | func (c *Cache) Remove(key Key) { 110 | if c.cache == nil { 111 | return 112 | } 113 | 114 | c.mutex.RLock() 115 | ele, hit := c.cache[key] 116 | c.mutex.RUnlock() 117 | if hit { 118 | c.removeElement(ele) 119 | } 120 | } 121 | 122 | // RemoveOldest removes the oldest item from the cache. 123 | func (c *Cache) RemoveOldest() { 124 | if c.cache == nil { 125 | return 126 | } 127 | ele := c.ll.Back() 128 | if ele != nil { 129 | c.removeElement(ele) 130 | } 131 | } 132 | 133 | func (c *Cache) removeElement(e *list.Element) { 134 | c.ll.Remove(e) 135 | kv := e.Value.(*entry) 136 | c.mutex.Lock() 137 | delete(c.cache, kv.key) 138 | c.mutex.Unlock() 139 | if c.OnEvicted != nil { 140 | c.OnEvicted(kv.key, kv.value) 141 | } 142 | } 143 | 144 | // Len returns the number of items in the cache. 145 | func (c *Cache) Len() int { 146 | if c.cache == nil { 147 | return 0 148 | } 149 | return c.ll.Len() 150 | } 151 | 152 | // Clear purges all stored items from the cache. 153 | func (c *Cache) Clear() { 154 | c.mutex.Lock() 155 | if c.OnEvicted != nil { 156 | for _, e := range c.cache { 157 | kv := e.Value.(*entry) 158 | c.OnEvicted(kv.key, kv.value) 159 | } 160 | } 161 | c.ll = nil 162 | c.cache = nil 163 | c.mutex.Unlock() 164 | } 165 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | "time" 10 | 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | // init a global cacher 15 | var testCache Cacher 16 | var err error 17 | 18 | func TestCacheMemory1(t *testing.T) { 19 | c := New(Options{ 20 | Name: "test2", 21 | Adapter: "memory", 22 | Config: map[string]interface{}{ 23 | "bytesLimit": int64(1024 * 1024), // 1MB 24 | }, 25 | }) 26 | 27 | Convey("cache memory", t, func() { 28 | Convey("set", func() { 29 | err := c.Set("test", "1", 2) 30 | So(err, ShouldBeNil) 31 | }) 32 | 33 | Convey("get", func() { 34 | var v string 35 | c.Get("test", &v) 36 | So(v, ShouldEqual, "1") 37 | }) 38 | 39 | Convey("get expried", func() { 40 | time.Sleep(time.Second * 2) 41 | var v string 42 | c.Get("test", &v) 43 | So(v, ShouldBeEmpty) 44 | }) 45 | 46 | Convey("set 10000", func() { 47 | for i := 0; i < 10000; i++ { 48 | err = c.Set("test", "1", 10) 49 | } 50 | So(err, ShouldBeNil) 51 | }) 52 | 53 | Convey("get 10000", func() { 54 | var v string 55 | for i := 0; i < 10000; i++ { 56 | v = "" 57 | c.Get("test", &v) 58 | } 59 | So(v, ShouldEqual, "1") 60 | }) 61 | 62 | Convey("set struct", func() { 63 | type b struct { 64 | Name string 65 | } 66 | gob.Register(b{}) 67 | v1 := b{"test"} 68 | err := c.Set("test", v1, 6) 69 | So(err, ShouldBeNil) 70 | var v2 b 71 | c.Get("test", &v2) 72 | So(v2.Name, ShouldEqual, v1.Name) 73 | var v3 *b 74 | err = c.Get("test", v3) 75 | So(err, ShouldNotBeNil) 76 | t.Logf("Get %#v: %v\n", v3, err) 77 | err = c.Get("test", &v3) 78 | So(err, ShouldBeNil) 79 | So(v3.Name, ShouldEqual, v1.Name) 80 | v4 := new(b) 81 | c.Get("test", v4) 82 | So(v4.Name, ShouldEqual, v1.Name) 83 | var v5 interface{} 84 | err = c.Get("test", &v5) 85 | So(err, ShouldNotBeNil) 86 | t.Logf("Get %#v: %v\n", v5, err) 87 | }) 88 | 89 | Convey("incr/decr", func() { 90 | c.Set("test", 1, 10) 91 | v, err := c.Incr("test") 92 | v, err = c.Incr("test") 93 | So(err, ShouldBeNil) 94 | So(v, ShouldEqual, 3) 95 | v, err = c.Decr("test") 96 | So(v, ShouldEqual, 2) 97 | c.Delete("test1") 98 | v, err = c.Incr("test1") 99 | So(v, ShouldEqual, 1) 100 | c.Delete("test2") 101 | v, err = c.Decr("test2") 102 | So(err, ShouldBeNil) 103 | So(v, ShouldEqual, -1) 104 | err = c.Delete("test") 105 | So(err, ShouldBeNil) 106 | c.Set("test", int16(1), 10) 107 | v, err = c.Incr("test") 108 | So(err, ShouldBeNil) 109 | So(v, ShouldEqual, 2) 110 | c.Set("test", uint16(5), 10) 111 | v, err = c.Incr("test") 112 | So(err, ShouldBeNil) 113 | So(v, ShouldEqual, 6) 114 | c.Set("test", 5.1, 10) 115 | v, err = c.Incr("test") 116 | So(err, ShouldNotBeNil) 117 | t.Logf("incr float: %#v, %v\n", v, err) 118 | }) 119 | 120 | Convey("gc", func() { 121 | var v string 122 | for i := 0; i <= 11000; i++ { 123 | key := "test" + strconv.Itoa(i) 124 | err = c.Set(key, "01234567890123456789", 10) 125 | } 126 | So(err, ShouldBeNil) 127 | c.Get("test10000", &v) 128 | So(v, ShouldEqual, "01234567890123456789") 129 | v = "" 130 | c.Get("test6", &v) 131 | So(v, ShouldBeEmpty) 132 | }) 133 | 134 | Convey("flush", func() { 135 | err := c.Flush() 136 | So(err, ShouldBeNil) 137 | }) 138 | 139 | Convey("exists", func() { 140 | c.Incr("test1") 141 | ok := c.Exist("test1") 142 | So(ok, ShouldBeTrue) 143 | ok = c.Exist("testNotExist") 144 | So(ok, ShouldBeFalse) 145 | }) 146 | 147 | Convey("large item", func() { 148 | v := strings.Repeat("A", 1024*1025) 149 | err := c.Set("test", v, 30) 150 | So(err, ShouldNotBeNil) 151 | v = strings.Repeat("A", 1024*513) 152 | err = c.Set("test2", v, 30) 153 | err = c.Set("test3", v, 30) 154 | So(err, ShouldBeNil) 155 | err = c.Delete("test3") 156 | So(err, ShouldBeNil) 157 | }) 158 | }) 159 | } 160 | 161 | func BenchmarkCacheMemorySet(b *testing.B) { 162 | for i := 0; i < b.N; i++ { 163 | testCache.Set(fmt.Sprintf("test%d", i), 1, 1800) 164 | } 165 | } 166 | 167 | func BenchmarkCacheMemoryGet(b *testing.B) { 168 | var v string 169 | for i := 0; i < b.N; i++ { 170 | testCache.Get(fmt.Sprintf("test%d", i), &v) 171 | } 172 | } 173 | 174 | func init() { 175 | testCache = New(Options{ 176 | Name: "testMemory", 177 | Adapter: "memory", 178 | Config: map[string]interface{}{ 179 | "bytesLimit": int64(1024 * 1024), // 1MB 180 | }, 181 | }) 182 | } 183 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "github.com/go-baa/cache/lru" 10 | ) 11 | 12 | const ( 13 | // MemoryLimit default memory size limit, 128mb 14 | // 128 1 << 7 15 | // 1024 1 << 10 16 | MemoryLimit int64 = 1 << 27 17 | // MemoryLimitMin minimum value for memory limit, 1mb 18 | MemoryLimitMin int64 = 1 << 20 19 | // MenoryObjectMaxSize maximum bytes for object, 1mb 20 | MenoryObjectMaxSize int64 = 1 << 20 21 | ) 22 | 23 | // Memory implement a memory cache adapter for cacher 24 | type Memory struct { 25 | Name string 26 | Prefix string 27 | bytes int64 28 | bytesLimit int64 29 | mu sync.RWMutex 30 | store *lru.Cache 31 | } 32 | 33 | // NewMemory create a cache instance of memory 34 | func NewMemory() Cacher { 35 | return new(Memory) 36 | } 37 | 38 | // Exist return true if value cached by given key 39 | func (c *Memory) Exist(key string) bool { 40 | item := c.get(c.Prefix + key) 41 | if item != nil { 42 | return true 43 | } 44 | return false 45 | } 46 | 47 | // Get returns value by given key 48 | func (c *Memory) Get(key string, out interface{}) error { 49 | c.mu.RLock() 50 | c.mu.RUnlock() 51 | item := c.get(c.Prefix + key) 52 | if item == nil { 53 | return errors.New("cache: cache miss") 54 | } 55 | return item.Decode(out) 56 | } 57 | 58 | func (c *Memory) get(key string) *Item { 59 | v, ok := c.store.Get(key) 60 | if !ok { 61 | return nil 62 | } 63 | item, err := v.(ItemBinary).Item() 64 | if err != nil { 65 | return nil 66 | } 67 | if item.Expired() { 68 | c.Delete(key) 69 | return nil 70 | } 71 | return item 72 | } 73 | 74 | // Set cache value by given key, cache ttl second 75 | func (c *Memory) Set(key string, v interface{}, ttl int64) error { 76 | item := NewItem(v, ttl) 77 | b, err := item.Encode() 78 | if err != nil { 79 | return err 80 | } 81 | 82 | // if overwrite bytes count will error 83 | // so, delete first if exist 84 | if c.Exist(key) { 85 | c.store.Remove(c.Prefix + key) 86 | } 87 | 88 | c.mu.Lock() 89 | defer c.mu.Unlock() 90 | 91 | l := int64(len(b)) 92 | err = c.gc(l) 93 | if err != nil { 94 | return err 95 | } 96 | c.store.Add(c.Prefix+key, b) 97 | c.bytes += l 98 | 99 | return nil 100 | } 101 | 102 | // Incr increases cached int-type value by given key as a counter 103 | // if key not exist, before increase set value with zero 104 | func (c *Memory) Incr(key string) (int64, error) { 105 | item := c.get(c.Prefix + key) 106 | if item == nil { 107 | item = NewItem(0, 0) 108 | } 109 | err := item.Incr() 110 | if err != nil { 111 | return 0, err 112 | } 113 | ttl := item.TTL 114 | if ttl > 0 { 115 | ttl = int64((item.Expiration - time.Now().UnixNano()) / 1e9) 116 | } 117 | if ttl < 0 { 118 | return 0, fmt.Errorf("cache: expired") 119 | } 120 | err = c.Set(key, item.Val, ttl) 121 | if err != nil { 122 | return 0, err 123 | } 124 | return item.Val.(int64), nil 125 | } 126 | 127 | // Decr decreases cached int-type value by given key as a counter 128 | // if key not exist, return errors 129 | func (c *Memory) Decr(key string) (int64, error) { 130 | item := c.get(c.Prefix + key) 131 | if item == nil { 132 | item = NewItem(0, 0) 133 | } 134 | err := item.Decr() 135 | if err != nil { 136 | return 0, err 137 | } 138 | ttl := item.TTL 139 | if ttl > 0 { 140 | ttl = int64((item.Expiration - time.Now().UnixNano()) / 1e9) 141 | } 142 | if ttl < 0 { 143 | return 0, fmt.Errorf("cache: expired") 144 | } 145 | err = c.Set(key, item.Val, ttl) 146 | if err != nil { 147 | return 0, err 148 | } 149 | return item.Val.(int64), nil 150 | } 151 | 152 | // Delete delete cached data by given key 153 | func (c *Memory) Delete(key string) error { 154 | c.mu.Lock() 155 | defer c.mu.Unlock() 156 | c.store.Remove(c.Prefix + key) 157 | return nil 158 | } 159 | 160 | // Flush flush cacher 161 | func (c *Memory) Flush() error { 162 | c.mu.Lock() 163 | defer c.mu.Unlock() 164 | var l int 165 | for { 166 | l = c.store.Len() 167 | if l > 0 { 168 | c.store.RemoveOldest() 169 | } else { 170 | break 171 | } 172 | } 173 | c.bytes = 0 174 | 175 | return nil 176 | } 177 | 178 | // Start new a cacher and start service 179 | func (c *Memory) Start(o Options) error { 180 | c.Name = o.Name 181 | c.Prefix = o.Prefix 182 | c.bytesLimit = MemoryLimit 183 | if o.Config != nil { 184 | if v, ok := o.Config["bytesLimit"].(int64); ok { 185 | c.bytesLimit = v 186 | } 187 | } 188 | if c.bytesLimit < MemoryLimitMin { 189 | c.bytesLimit = MemoryLimitMin 190 | } 191 | 192 | if c.store == nil { 193 | c.store = lru.New(0) 194 | c.store.OnEvicted = func(key lru.Key, value interface{}) { 195 | c.bytes -= int64(len(value.(ItemBinary))) 196 | } 197 | } 198 | 199 | return nil 200 | } 201 | 202 | // gc release memory for storage new item 203 | // if free bytes can store item returns 204 | // remove items until bytes less than bytesLimit - size 205 | func (c *Memory) gc(size int64) error { 206 | if c.bytes+size < c.bytesLimit { 207 | return nil 208 | } 209 | 210 | if size > MenoryObjectMaxSize { 211 | return fmt.Errorf("cache: object size limit to %d bytes", MenoryObjectMaxSize) 212 | } 213 | 214 | releaseSize := c.bytesLimit - size*2 215 | if releaseSize <= 0 { 216 | releaseSize = c.bytesLimit - size 217 | } 218 | for c.bytes > releaseSize { 219 | if c.store.Len() > 0 { 220 | c.store.RemoveOldest() 221 | } else { 222 | break 223 | } 224 | } 225 | return nil 226 | } 227 | 228 | func init() { 229 | Register("memory", NewMemory) 230 | } 231 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | // Package cache providers a cache management for baa. 2 | package cache 3 | 4 | import ( 5 | "bytes" 6 | "encoding/gob" 7 | "fmt" 8 | "reflect" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | // Cacher a cache management for baa 14 | type Cacher interface { 15 | // Exist return true if value cached by given key 16 | Exist(key string) bool 17 | // Get returns value to out by given key 18 | Get(key string, out interface{}) error 19 | // Set cache value by given key, cache ttl second 20 | Set(key string, v interface{}, ttl int64) error 21 | // Incr increases cached int-type value by given key as a counter 22 | // if key not exist, before increase set value with zero 23 | Incr(key string) (int64, error) 24 | // Decr decreases cached int-type value by given key as a counter 25 | // if key not exist, before increase set value with zero 26 | // NOTE: memcached returns uint type cannot be less than zero 27 | Decr(key string) (int64, error) 28 | // Delete delete cached data by given key 29 | Delete(key string) error 30 | // Flush flush cacher 31 | Flush() error 32 | // Start new a cacher and start service 33 | Start(Options) error 34 | } 35 | 36 | // Item cache storage item 37 | type Item struct { 38 | Val interface{} // real object value 39 | TTL int64 // cache life time 40 | Expiration int64 // expired time 41 | } 42 | 43 | // ItemBinary cache item encoded data 44 | type ItemBinary []byte 45 | 46 | // Options cache options 47 | type Options struct { 48 | Name string // cache name 49 | Adapter string // adapter 50 | Prefix string // cache key prefix 51 | Config map[string]interface{} // config for adapter 52 | } 53 | 54 | type instanceFunc func() Cacher 55 | 56 | var adapters = make(map[string]instanceFunc) 57 | 58 | // New create a Cacher 59 | func New(o Options) Cacher { 60 | if o.Name == "" { 61 | o.Name = "_DEFAULT_" 62 | } 63 | if o.Adapter == "" { 64 | panic("cache.New: cannot use empty adapter") 65 | } 66 | c, err := NewCacher(o.Adapter, o) 67 | if err != nil { 68 | panic("cache.New: " + err.Error()) 69 | } 70 | return c 71 | } 72 | 73 | // NewCacher creates and returns a new cacher by given adapter name and configuration. 74 | // It panics when given adapter isn't registered and starts GC automatically. 75 | func NewCacher(name string, o Options) (Cacher, error) { 76 | f, ok := adapters[name] 77 | if !ok { 78 | return nil, fmt.Errorf("cache: unknown adapter '%s'(forgot to import?)", name) 79 | } 80 | adapter := f() 81 | return adapter, adapter.Start(o) 82 | } 83 | 84 | // Register registers a adapter 85 | func Register(name string, f instanceFunc) { 86 | if f == nil { 87 | panic("cache.Register: cannot register adapter with nil func") 88 | } 89 | if _, ok := adapters[name]; ok { 90 | panic(fmt.Errorf("cache.Register: cannot register adapter '%s' twice", name)) 91 | } 92 | adapters[name] = f 93 | } 94 | 95 | // NewItem create a cache item 96 | func NewItem(val interface{}, ttl int64) *Item { 97 | item := &Item{Val: val, TTL: ttl} 98 | if ttl > 0 { 99 | item.Expiration = time.Now().Add(time.Duration(ttl) * time.Second).UnixNano() 100 | } 101 | return item 102 | } 103 | 104 | // Expired check item has expired 105 | func (t *Item) Expired() bool { 106 | return t.TTL > 0 && time.Now().UnixNano() >= t.Expiration 107 | } 108 | 109 | // Incr increases given value 110 | func (t *Item) Incr() error { 111 | switch t.Val.(type) { 112 | case int, int8, int16, int32, int64: 113 | t.Val = reflect.ValueOf(t.Val).Int() + 1 114 | case uint, uint8, uint16, uint32, uint64: 115 | t.Val = int64(reflect.ValueOf(t.Val).Uint()) + 1 116 | default: 117 | return fmt.Errorf("item value is not int-type") 118 | } 119 | return nil 120 | } 121 | 122 | // Decr decreases given value 123 | func (t *Item) Decr() error { 124 | switch t.Val.(type) { 125 | case int, int8, int16, int32, int64: 126 | t.Val = reflect.ValueOf(t.Val).Int() - 1 127 | case uint, uint8, uint16, uint32, uint64: 128 | t.Val = int64(reflect.ValueOf(t.Val).Uint()) - 1 129 | default: 130 | return fmt.Errorf("item value is not int-type") 131 | } 132 | return nil 133 | } 134 | 135 | // Encode encode item to bytes by gob 136 | func (t *Item) Encode() (ItemBinary, error) { 137 | buf := bytes.NewBuffer(nil) 138 | err := gob.NewEncoder(buf).Encode(t) 139 | return buf.Bytes(), err 140 | } 141 | 142 | // Decode item value to out interface 143 | func (t *Item) Decode(out interface{}) error { 144 | rv := reflect.ValueOf(out) 145 | if rv.IsNil() { 146 | return fmt.Errorf("cache: out is nil") 147 | } 148 | if rv.Kind() != reflect.Ptr { 149 | return fmt.Errorf("cache: out must be a pointer") 150 | } 151 | for rv.Kind() == reflect.Ptr { 152 | if !rv.Elem().IsValid() && rv.IsNil() { 153 | rv.Set(reflect.New(rv.Type().Elem())) 154 | } 155 | rv = rv.Elem() 156 | } 157 | 158 | if !rv.CanSet() { 159 | return fmt.Errorf("cache: out cannot set value") 160 | } 161 | rt := reflect.ValueOf(t.Val) 162 | if rt.Kind() == reflect.Ptr { 163 | rt = rt.Elem() 164 | } 165 | if rv.Type() != rt.Type() { 166 | return fmt.Errorf("cache: out is different type with stored value %v, %v", rv.Type(), rt.Type()) 167 | } 168 | rv.Set(rt) 169 | return nil 170 | } 171 | 172 | // Item decode bytes data to cache item use gob 173 | func (t ItemBinary) Item() (*Item, error) { 174 | buf := bytes.NewBuffer(t) 175 | item := new(Item) 176 | err := gob.NewDecoder(buf).Decode(&item) 177 | return item, err 178 | } 179 | 180 | // SimpleType check value type is simple type or not 181 | func SimpleType(v interface{}) bool { 182 | switch v.(type) { 183 | case string: 184 | return true 185 | case int, int8, int16, int32, int64: 186 | return true 187 | case uint, uint8, uint16, uint32, uint64: 188 | return true 189 | case float32, float64: 190 | return true 191 | case bool: 192 | return true 193 | default: 194 | return false 195 | } 196 | } 197 | 198 | // SimpleValue return value to output with type convert 199 | func SimpleValue(v []byte, o interface{}) bool { 200 | switch o.(type) { 201 | case *string: 202 | *o.(*string) = string(v) 203 | case *bool: 204 | *o.(*bool), _ = strconv.ParseBool(string(v)) 205 | case *int: 206 | t, _ := strconv.ParseInt(string(v), 10, 64) 207 | *o.(*int) = int(t) 208 | case *int8: 209 | t, _ := strconv.ParseInt(string(v), 10, 64) 210 | *o.(*int8) = int8(t) 211 | case *int16: 212 | t, _ := strconv.ParseInt(string(v), 10, 64) 213 | *o.(*int16) = int16(t) 214 | case *int32: 215 | t, _ := strconv.ParseInt(string(v), 10, 64) 216 | *o.(*int32) = int32(t) 217 | case *int64: 218 | *o.(*int64), _ = strconv.ParseInt(string(v), 10, 64) 219 | case *uint: 220 | t, _ := strconv.ParseUint(string(v), 10, 64) 221 | *o.(*uint) = uint(t) 222 | case *uint8: 223 | t, _ := strconv.ParseUint(string(v), 10, 64) 224 | *o.(*uint8) = uint8(t) 225 | case *uint16: 226 | t, _ := strconv.ParseUint(string(v), 10, 64) 227 | *o.(*uint16) = uint16(t) 228 | case *uint32: 229 | t, _ := strconv.ParseUint(string(v), 10, 64) 230 | *o.(*uint32) = uint32(t) 231 | case *uint64: 232 | *o.(*uint64), _ = strconv.ParseUint(string(v), 10, 64) 233 | case *float32: 234 | t, _ := strconv.ParseFloat(string(v), 64) 235 | *o.(*float32) = float32(t) 236 | case *float64: 237 | *o.(*float64), _ = strconv.ParseFloat(string(v), 64) 238 | default: 239 | return false 240 | } 241 | return true 242 | } 243 | 244 | func init() { 245 | gob.Register(time.Time{}) 246 | gob.Register(&Item{}) 247 | } 248 | --------------------------------------------------------------------------------