-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
142 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package lcw | ||
|
||
import ( | ||
"sync/atomic" | ||
"time" | ||
|
||
"github.com/go-redis/redis" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// RedisCache implements LoadingCache for Redis. | ||
type RedisCache struct { | ||
options | ||
CacheStat | ||
backend *redis.Client | ||
} | ||
|
||
// NewRedisCache makes Redis LoadingCache implementation. | ||
func NewRedisCache(opts ...Option) (*RedisCache, error) { | ||
|
||
res := RedisCache{ | ||
options: options{ | ||
ttl: 5 * time.Minute, | ||
}, | ||
} | ||
for _, opt := range opts { | ||
if err := opt(&res.options); err != nil { | ||
return nil, errors.Wrap(err, "failed to set cache option") | ||
} | ||
} | ||
|
||
res.backend = redis.NewClient(res.redisOptions) | ||
|
||
return &res, nil | ||
} | ||
|
||
// Get gets value by key or load with fn if not found in cache | ||
func (c *RedisCache) Get(key string, fn func() (Value, error)) (data Value, err error) { | ||
|
||
v, ok := c.backend.Get(key).Result() | ||
if ok == nil { | ||
atomic.AddInt64(&c.Hits, 1) | ||
return v, nil | ||
} | ||
if ok == redis.Nil { | ||
if data, err = fn(); err != nil { | ||
atomic.AddInt64(&c.Errors, 1) | ||
return data, err | ||
} | ||
} else if ok != nil { | ||
atomic.AddInt64(&c.Errors, 1) | ||
return v, ok | ||
} | ||
atomic.AddInt64(&c.Misses, 1) | ||
|
||
if c.allowed(key, data) { | ||
c.backend.Set(key, data, c.ttl) | ||
} | ||
return data, nil | ||
} | ||
|
||
// Invalidate removes keys with passed predicate fn, i.e. fn(key) should be true to get evicted | ||
func (c *RedisCache) Invalidate(fn func(key string) bool) { | ||
for _, key := range c.backend.Keys("*").Val() { // Keys() returns copy of cache's key, safe to remove directly | ||
if fn(key) { | ||
c.backend.Del(key) | ||
} | ||
} | ||
} | ||
|
||
// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key. | ||
func (c *RedisCache) Peek(key string) (Value, bool) { | ||
ret, err := c.backend.Get(key).Result() | ||
if err != nil { | ||
return nil, false | ||
} | ||
return ret, true | ||
} | ||
|
||
// Purge clears the cache completely. | ||
func (c *RedisCache) Purge() { | ||
c.backend.FlushDB() | ||
|
||
} | ||
|
||
// Delete cache item by key | ||
func (c *RedisCache) Delete(key string) { | ||
c.backend.Del(key) | ||
} | ||
|
||
// Stat returns cache statistics | ||
func (c *RedisCache) Stat() CacheStat { | ||
return CacheStat{ | ||
Hits: c.Hits, | ||
Misses: c.Misses, | ||
Size: c.size(), | ||
Keys: c.keys(), | ||
Errors: c.Errors, | ||
} | ||
} | ||
|
||
func (c *RedisCache) size() int64 { | ||
return 0 | ||
} | ||
|
||
func (c *RedisCache) keys() int { | ||
return int(c.backend.DBSize().Val()) | ||
} | ||
|
||
func (c *RedisCache) allowed(key string, data Value) bool { | ||
if c.backend.DBSize().Val() >= int64(c.maxKeys) { | ||
return false | ||
} | ||
if c.maxKeySize > 0 && len(key) > c.maxKeySize { | ||
return false | ||
} | ||
if s, ok := data.(Sizer); ok { | ||
// Maximum allowed value size in Redis | ||
if s.Size() >= (512 * 1024 * 1024) { | ||
return false | ||
} | ||
} | ||
return true | ||
} |