/
gredis.go
266 lines (241 loc) · 8.65 KB
/
gredis.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/rglujing/gf.
// Package gredis provides convenient client for redis server.
//
// Redis Client.
//
// Redis Commands Official: https://redis.io/commands
//
// Redis Chinese Documentation: http://redisdoc.com/
package gredis
import (
"context"
"fmt"
"github.com/rglujing/gf/internal/intlog"
"time"
"github.com/rglujing/gf/container/gmap"
"github.com/rglujing/gf/container/gvar"
"github.com/gomodule/redigo/redis"
)
// Redis client.
type Redis struct {
pool *redis.Pool // Underlying connection pool.
group string // Configuration group.
config *Config // Configuration.
ctx context.Context // Context.
}
// Conn is redis connection.
type Conn struct {
redis.Conn
ctx context.Context
redis *Redis
}
// Config is redis configuration.
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Db int `json:"db"`
Pass string `json:"pass"` // Password for AUTH.
MaxIdle int `json:"maxIdle"` // Maximum number of connections allowed to be idle (default is 10)
MaxActive int `json:"maxActive"` // Maximum number of connections limit (default is 0 means no limit).
IdleTimeout time.Duration `json:"idleTimeout"` // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0)
MaxConnLifetime time.Duration `json:"maxConnLifetime"` // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0)
ConnectTimeout time.Duration `json:"connectTimeout"` // Dial connection timeout.
TLS bool `json:"tls"` // Specifies the config to use when a TLS connection is dialed.
TLSSkipVerify bool `json:"tlsSkipVerify"` // Disables server name verification when connecting over TLS.
}
// PoolStats is statistics of redis connection pool.
type PoolStats struct {
redis.PoolStats
}
const (
defaultPoolIdleTimeout = 10 * time.Second
defaultPoolConnTimeout = 10 * time.Second
defaultPoolMaxIdle = 10
defaultPoolMaxActive = 100
defaultPoolMaxLifeTime = 30 * time.Second
)
var (
// Pool map.
pools = gmap.NewStrAnyMap(true)
)
// New creates a redis client object with given configuration.
// Redis client maintains a connection pool automatically.
func New(config *Config) *Redis {
// The MaxIdle is the most important attribute of the connection pool.
// Only if this attribute is set, the created connections from client
// can not exceed the limit of the server.
if config.MaxIdle == 0 {
config.MaxIdle = defaultPoolMaxIdle
}
// This value SHOULD NOT exceed the connection limit of redis server.
if config.MaxActive == 0 {
config.MaxActive = defaultPoolMaxActive
}
if config.IdleTimeout == 0 {
config.IdleTimeout = defaultPoolIdleTimeout
}
if config.ConnectTimeout == 0 {
config.ConnectTimeout = defaultPoolConnTimeout
}
if config.MaxConnLifetime == 0 {
config.MaxConnLifetime = defaultPoolMaxLifeTime
}
return &Redis{
config: config,
pool: pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
return &redis.Pool{
Wait: true,
IdleTimeout: config.IdleTimeout,
MaxActive: config.MaxActive,
MaxIdle: config.MaxIdle,
MaxConnLifetime: config.MaxConnLifetime,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial(
"tcp",
fmt.Sprintf("%s:%d", config.Host, config.Port),
redis.DialConnectTimeout(config.ConnectTimeout),
redis.DialUseTLS(config.TLS),
redis.DialTLSSkipVerify(config.TLSSkipVerify),
)
if err != nil {
return nil, err
}
intlog.Printf(context.TODO(), `open new connection, config:%+v`, config)
// AUTH
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {
return nil, err
}
}
// DB
if _, err := c.Do("SELECT", config.Db); err != nil {
return nil, err
}
return c, nil
},
// After the conn is taken from the connection pool, to test if the connection is available,
// If error is returned then it closes the connection object and recreate a new connection.
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}).(*redis.Pool),
}
}
// NewFromStr creates a redis client object with given configuration string.
// Redis client maintains a connection pool automatically.
// The parameter <str> like:
// 127.0.0.1:6379,0
// 127.0.0.1:6379,0,password
func NewFromStr(str string) (*Redis, error) {
config, err := ConfigFromStr(str)
if err != nil {
return nil, err
}
return New(config), nil
}
// Close closes the redis connection pool,
// it will release all connections reserved by this pool.
// It is not necessary to call Close manually.
func (r *Redis) Close() error {
if r.group != "" {
// If it is an instance object,
// it needs to remove it from the instance Map.
instances.Remove(r.group)
}
pools.Remove(fmt.Sprintf("%v", r.config))
return r.pool.Close()
}
// Clone clones and returns a new Redis object, which is a shallow copy of current one.
func (r *Redis) Clone() *Redis {
newRedis := New(r.config)
*newRedis = *r
return newRedis
}
// Ctx is a channing function which sets the context for next operation.
func (r *Redis) Ctx(ctx context.Context) *Redis {
newRedis := r.Clone()
newRedis.ctx = ctx
return newRedis
}
// Conn returns a raw underlying connection object,
// which expose more methods to communicate with server.
// **You should call Close function manually if you do not use this connection any further.**
func (r *Redis) Conn() *Conn {
return &Conn{
Conn: r.pool.Get(),
ctx: r.ctx,
redis: r,
}
}
// GetConn is alias of Conn, see Conn.
// Deprecated, use Conn instead.
func (r *Redis) GetConn() *Conn {
return r.Conn()
}
// SetMaxIdle sets the maximum number of idle connections in the pool.
func (r *Redis) SetMaxIdle(value int) {
r.pool.MaxIdle = value
}
// SetMaxActive sets the maximum number of connections allocated by the pool at a given time.
// When zero, there is no limit on the number of connections in the pool.
//
// Note that if the pool is at the MaxActive limit, then all the operations will wait for
// a connection to be returned to the pool before returning.
func (r *Redis) SetMaxActive(value int) {
r.pool.MaxActive = value
}
// SetIdleTimeout sets the IdleTimeout attribute of the connection pool.
// It closes connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout.
func (r *Redis) SetIdleTimeout(value time.Duration) {
r.pool.IdleTimeout = value
}
// SetMaxConnLifetime sets the MaxConnLifetime attribute of the connection pool.
// It closes connections older than this duration. If the value is zero, then
// the pool does not close connections based on age.
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
r.pool.MaxConnLifetime = value
}
// Stats returns pool's statistics.
func (r *Redis) Stats() *PoolStats {
return &PoolStats{r.pool.Stats()}
}
// Do sends a command to the server and returns the received reply.
// Do automatically get a connection from pool, and close it when the reply received.
// It does not really "close" the connection, but drops it back to the connection pool.
func (r *Redis) Do(commandName string, args ...interface{}) (interface{}, error) {
conn := &Conn{
Conn: r.pool.Get(),
ctx: r.ctx,
redis: r,
}
defer conn.Close()
return conn.Do(commandName, args...)
}
// DoWithTimeout sends a command to the server and returns the received reply.
// The timeout overrides the read timeout set when dialing the connection.
func (r *Redis) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {
conn := &Conn{
Conn: r.pool.Get(),
ctx: r.ctx,
redis: r,
}
defer conn.Close()
return conn.DoWithTimeout(timeout, commandName, args...)
}
// DoVar returns value from Do as gvar.Var.
func (r *Redis) DoVar(commandName string, args ...interface{}) (*gvar.Var, error) {
return resultToVar(r.Do(commandName, args...))
}
// DoVarWithTimeout returns value from Do as gvar.Var.
// The timeout overrides the read timeout set when dialing the connection.
func (r *Redis) DoVarWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (*gvar.Var, error) {
return resultToVar(r.DoWithTimeout(timeout, commandName, args...))
}