forked from gin-contrib/cache
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredis.go
203 lines (180 loc) · 5.11 KB
/
redis.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package persistence
import (
"time"
"github.com/schollz/gincache/utils"
"github.com/gomodule/redigo/redis"
)
// RedisStore represents the cache with redis persistence
type RedisStore struct {
pool *redis.Pool
defaultExpiration time.Duration
}
// NewRedisCache returns a RedisStore
// until redigo supports sharding/clustering, only one host will be in hostList
func NewRedisCache(host string, password string, defaultExpiration time.Duration) *RedisStore {
var pool = &redis.Pool{
MaxIdle: 5,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
// the redis protocol should probably be made sett-able
c, err := redis.Dial("tcp", host)
if err != nil {
return nil, err
}
if len(password) > 0 {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
return nil, err
}
} else {
// check with PING
if _, err := c.Do("PING"); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
// custom connection test method
TestOnBorrow: func(c redis.Conn, t time.Time) error {
if _, err := c.Do("PING"); err != nil {
return err
}
return nil
},
}
return &RedisStore{pool, defaultExpiration}
}
// NewRedisCacheWithPool returns a RedisStore using the provided pool
// until redigo supports sharding/clustering, only one host will be in hostList
func NewRedisCacheWithPool(pool *redis.Pool, defaultExpiration time.Duration) *RedisStore {
return &RedisStore{pool, defaultExpiration}
}
// Set (see CacheStore interface)
func (c *RedisStore) Set(key string, value interface{}, expires time.Duration) error {
conn := c.pool.Get()
defer conn.Close()
return c.invoke(conn.Do, key, value, expires)
}
// Add (see CacheStore interface)
func (c *RedisStore) Add(key string, value interface{}, expires time.Duration) error {
conn := c.pool.Get()
defer conn.Close()
if exists(conn, key) {
return ErrNotStored
}
return c.invoke(conn.Do, key, value, expires)
}
// Replace (see CacheStore interface)
func (c *RedisStore) Replace(key string, value interface{}, expires time.Duration) error {
conn := c.pool.Get()
defer conn.Close()
if !exists(conn, key) {
return ErrNotStored
}
err := c.invoke(conn.Do, key, value, expires)
if value == nil {
return ErrNotStored
}
return err
}
// Get (see CacheStore interface)
func (c *RedisStore) Get(key string, ptrValue interface{}) error {
conn := c.pool.Get()
defer conn.Close()
raw, err := conn.Do("GET", key)
if raw == nil {
return ErrCacheMiss
}
item, err := redis.Bytes(raw, err)
if err != nil {
return err
}
return utils.Deserialize(item, ptrValue)
}
func exists(conn redis.Conn, key string) bool {
retval, _ := redis.Bool(conn.Do("EXISTS", key))
return retval
}
// Delete (see CacheStore interface)
func (c *RedisStore) Delete(key string) error {
conn := c.pool.Get()
defer conn.Close()
if !exists(conn, key) {
return ErrCacheMiss
}
_, err := conn.Do("DEL", key)
return err
}
// Increment (see CacheStore interface)
func (c *RedisStore) Increment(key string, delta uint64) (uint64, error) {
conn := c.pool.Get()
defer conn.Close()
// Check for existance *before* increment as per the cache contract.
// redis will auto create the key, and we don't want that. Since we need to do increment
// ourselves instead of natively via INCRBY (redis doesn't support wrapping), we get the value
// and do the exists check this way to minimize calls to Redis
val, err := conn.Do("GET", key)
if val == nil {
return 0, ErrCacheMiss
}
if err == nil {
currentVal, err := redis.Int64(val, nil)
if err != nil {
return 0, err
}
sum := currentVal + int64(delta)
_, err = conn.Do("SET", key, sum)
if err != nil {
return 0, err
}
return uint64(sum), nil
}
return 0, err
}
// Decrement (see CacheStore interface)
func (c *RedisStore) Decrement(key string, delta uint64) (newValue uint64, err error) {
conn := c.pool.Get()
defer conn.Close()
// Check for existance *before* increment as per the cache contract.
// redis will auto create the key, and we don't want that, hence the exists call
if !exists(conn, key) {
return 0, ErrCacheMiss
}
// Decrement contract says you can only go to 0
// so we go fetch the value and if the delta is greater than the amount,
// 0 out the value
currentVal, err := redis.Int64(conn.Do("GET", key))
if err == nil && delta > uint64(currentVal) {
tempint, err := redis.Int64(conn.Do("DECRBY", key, currentVal))
return uint64(tempint), err
}
tempint, err := redis.Int64(conn.Do("DECRBY", key, delta))
return uint64(tempint), err
}
// Flush (see CacheStore interface)
func (c *RedisStore) Flush() error {
conn := c.pool.Get()
defer conn.Close()
_, err := conn.Do("FLUSHALL")
return err
}
func (c *RedisStore) invoke(f func(string, ...interface{}) (interface{}, error),
key string, value interface{}, expires time.Duration) error {
switch expires {
case DEFAULT:
expires = c.defaultExpiration
case FOREVER:
expires = time.Duration(0)
}
b, err := utils.Serialize(value)
if err != nil {
return err
}
if expires > 0 {
_, err := f("SETEX", key, int32(expires/time.Second), b)
return err
}
_, err = f("SET", key, b)
return err
}