github.com/astaxie/beego@v1.12.3/cache/redis/redis.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package redis for cache provider
    16  //
    17  // depend on github.com/gomodule/redigo/redis
    18  //
    19  // go install github.com/gomodule/redigo/redis
    20  //
    21  // Usage:
    22  // import(
    23  //   _ "github.com/astaxie/beego/cache/redis"
    24  //   "github.com/astaxie/beego/cache"
    25  // )
    26  //
    27  //  bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
    28  //
    29  //  more docs http://beego.me/docs/module/cache.md
    30  package redis
    31  
    32  import (
    33  	"encoding/json"
    34  	"errors"
    35  	"fmt"
    36  	"strconv"
    37  	"time"
    38  
    39  	"github.com/gomodule/redigo/redis"
    40  
    41  	"strings"
    42  
    43  	"github.com/astaxie/beego/cache"
    44  )
    45  
    46  var (
    47  	// DefaultKey the collection name of redis for cache adapter.
    48  	DefaultKey = "beecacheRedis"
    49  )
    50  
    51  // Cache is Redis cache adapter.
    52  type Cache struct {
    53  	p        *redis.Pool // redis connection pool
    54  	conninfo string
    55  	dbNum    int
    56  	key      string
    57  	password string
    58  	maxIdle  int
    59  
    60  	//the timeout to a value less than the redis server's timeout.
    61  	timeout time.Duration
    62  }
    63  
    64  // NewRedisCache create new redis cache with default collection name.
    65  func NewRedisCache() cache.Cache {
    66  	return &Cache{key: DefaultKey}
    67  }
    68  
    69  // actually do the redis cmds, args[0] must be the key name.
    70  func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
    71  	if len(args) < 1 {
    72  		return nil, errors.New("missing required arguments")
    73  	}
    74  	args[0] = rc.associate(args[0])
    75  	c := rc.p.Get()
    76  	defer c.Close()
    77  
    78  	return c.Do(commandName, args...)
    79  }
    80  
    81  // associate with config key.
    82  func (rc *Cache) associate(originKey interface{}) string {
    83  	return fmt.Sprintf("%s:%s", rc.key, originKey)
    84  }
    85  
    86  // Get cache from redis.
    87  func (rc *Cache) Get(key string) interface{} {
    88  	if v, err := rc.do("GET", key); err == nil {
    89  		return v
    90  	}
    91  	return nil
    92  }
    93  
    94  // GetMulti get cache from redis.
    95  func (rc *Cache) GetMulti(keys []string) []interface{} {
    96  	c := rc.p.Get()
    97  	defer c.Close()
    98  	var args []interface{}
    99  	for _, key := range keys {
   100  		args = append(args, rc.associate(key))
   101  	}
   102  	values, err := redis.Values(c.Do("MGET", args...))
   103  	if err != nil {
   104  		return nil
   105  	}
   106  	return values
   107  }
   108  
   109  // Put put cache to redis.
   110  func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
   111  	_, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
   112  	return err
   113  }
   114  
   115  // Delete delete cache in redis.
   116  func (rc *Cache) Delete(key string) error {
   117  	_, err := rc.do("DEL", key)
   118  	return err
   119  }
   120  
   121  // IsExist check cache's existence in redis.
   122  func (rc *Cache) IsExist(key string) bool {
   123  	v, err := redis.Bool(rc.do("EXISTS", key))
   124  	if err != nil {
   125  		return false
   126  	}
   127  	return v
   128  }
   129  
   130  // Incr increase counter in redis.
   131  func (rc *Cache) Incr(key string) error {
   132  	_, err := redis.Bool(rc.do("INCRBY", key, 1))
   133  	return err
   134  }
   135  
   136  // Decr decrease counter in redis.
   137  func (rc *Cache) Decr(key string) error {
   138  	_, err := redis.Bool(rc.do("INCRBY", key, -1))
   139  	return err
   140  }
   141  
   142  // ClearAll clean all cache in redis. delete this redis collection.
   143  func (rc *Cache) ClearAll() error {
   144  	cachedKeys, err := rc.Scan(rc.key + ":*")
   145  	if err != nil {
   146  		return err
   147  	}
   148  	c := rc.p.Get()
   149  	defer c.Close()
   150  	for _, str := range cachedKeys {
   151  		if _, err = c.Do("DEL", str); err != nil {
   152  			return err
   153  		}
   154  	}
   155  	return err
   156  }
   157  
   158  // Scan scan all keys matching the pattern. a better choice than `keys`
   159  func (rc *Cache) Scan(pattern string) (keys []string, err error) {
   160  	c := rc.p.Get()
   161  	defer c.Close()
   162  	var (
   163  		cursor uint64 = 0 // start
   164  		result []interface{}
   165  		list   []string
   166  	)
   167  	for {
   168  		result, err = redis.Values(c.Do("SCAN", cursor, "MATCH", pattern, "COUNT", 1024))
   169  		if err != nil {
   170  			return
   171  		}
   172  		list, err = redis.Strings(result[1], nil)
   173  		if err != nil {
   174  			return
   175  		}
   176  		keys = append(keys, list...)
   177  		cursor, err = redis.Uint64(result[0], nil)
   178  		if err != nil {
   179  			return
   180  		}
   181  		if cursor == 0 { // over
   182  			return
   183  		}
   184  	}
   185  }
   186  
   187  // StartAndGC start redis cache adapter.
   188  // config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
   189  // the cache item in redis are stored forever,
   190  // so no gc operation.
   191  func (rc *Cache) StartAndGC(config string) error {
   192  	var cf map[string]string
   193  	json.Unmarshal([]byte(config), &cf)
   194  
   195  	if _, ok := cf["key"]; !ok {
   196  		cf["key"] = DefaultKey
   197  	}
   198  	if _, ok := cf["conn"]; !ok {
   199  		return errors.New("config has no conn key")
   200  	}
   201  
   202  	// Format redis://<password>@<host>:<port>
   203  	cf["conn"] = strings.Replace(cf["conn"], "redis://", "", 1)
   204  	if i := strings.Index(cf["conn"], "@"); i > -1 {
   205  		cf["password"] = cf["conn"][0:i]
   206  		cf["conn"] = cf["conn"][i+1:]
   207  	}
   208  
   209  	if _, ok := cf["dbNum"]; !ok {
   210  		cf["dbNum"] = "0"
   211  	}
   212  	if _, ok := cf["password"]; !ok {
   213  		cf["password"] = ""
   214  	}
   215  	if _, ok := cf["maxIdle"]; !ok {
   216  		cf["maxIdle"] = "3"
   217  	}
   218  	if _, ok := cf["timeout"]; !ok {
   219  		cf["timeout"] = "180s"
   220  	}
   221  	rc.key = cf["key"]
   222  	rc.conninfo = cf["conn"]
   223  	rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
   224  	rc.password = cf["password"]
   225  	rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
   226  
   227  	if v, err := time.ParseDuration(cf["timeout"]); err == nil {
   228  		rc.timeout = v
   229  	} else {
   230  		rc.timeout = 180 * time.Second
   231  	}
   232  
   233  	rc.connectInit()
   234  
   235  	c := rc.p.Get()
   236  	defer c.Close()
   237  
   238  	return c.Err()
   239  }
   240  
   241  // connect to redis.
   242  func (rc *Cache) connectInit() {
   243  	dialFunc := func() (c redis.Conn, err error) {
   244  		c, err = redis.Dial("tcp", rc.conninfo)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  
   249  		if rc.password != "" {
   250  			if _, err := c.Do("AUTH", rc.password); err != nil {
   251  				c.Close()
   252  				return nil, err
   253  			}
   254  		}
   255  
   256  		_, selecterr := c.Do("SELECT", rc.dbNum)
   257  		if selecterr != nil {
   258  			c.Close()
   259  			return nil, selecterr
   260  		}
   261  		return
   262  	}
   263  	// initialize a new pool
   264  	rc.p = &redis.Pool{
   265  		MaxIdle:     rc.maxIdle,
   266  		IdleTimeout: rc.timeout,
   267  		Dial:        dialFunc,
   268  	}
   269  }
   270  
   271  func init() {
   272  	cache.Register("redis", NewRedisCache)
   273  }