├── .gitignore ├── go.mod ├── go.sum ├── helper.go ├── README.md ├── redis_test.go └── redis.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .idea/ 3 | .DS_Store 4 | vendor/* 5 | !vendor/vendor.json 6 | cmd/ 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aiscrm/redisgo 2 | 3 | require github.com/gomodule/redigo v2.0.0+incompatible 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= 2 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 3 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import ( 4 | "github.com/gomodule/redigo/redis" 5 | ) 6 | 7 | // Int is a helper that converts a command reply to an integer 8 | func Int(reply interface{}, err error) (int, error) { 9 | return redis.Int(reply, err) 10 | } 11 | 12 | // Int64 is a helper that converts a command reply to 64 bit integer 13 | func Int64(reply interface{}, err error) (int64, error) { 14 | return redis.Int64(reply, err) 15 | } 16 | 17 | // String is a helper that converts a command reply to a string 18 | func String(reply interface{}, err error) (string, error) { 19 | return redis.String(reply, err) 20 | } 21 | 22 | // Bool is a helper that converts a command reply to a boolean 23 | func Bool(reply interface{}, err error) (bool, error) { 24 | return redis.Bool(reply, err) 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redisgo 2 | 基于[github.com/gomodule/redigo](https://github.com/gomodule/redigo),对常用的redis用法进行了封装,让你更方便的使用。 3 | 4 | ## 特性 5 | 6 | - 支持连接池 7 | - 支持退出时关闭连接池 8 | - 支持各种类型数据的存储和读取,可以直接获取指定类型的存储值 9 | - 支持有序集合,可以用来做延迟队列或排行榜等用途 10 | - 支持redis订阅/发布,在redis故障或网络异常时,自动重新订阅 11 | - 支持列表,包含阻塞式和非阻塞式读取 12 | 13 | ## 安装 14 | 15 | ```sh 16 | go get -u github.com/aiscrm/cache 17 | ``` 18 | 19 | ## 快速开始 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/aiscrm/redisgo" 28 | ) 29 | 30 | type User struct { 31 | Name string 32 | Age int 33 | } 34 | 35 | func main() { 36 | c, err := redisgo.New( 37 | redisgo.Options{ 38 | Addr: "127.0.0.1:6379", 39 | Password: "", 40 | Prefix: "aiscrm_", 41 | }) 42 | if err != nil { 43 | panic(err) 44 | } 45 | c.Set("name", "corel", 30) 46 | c.Set("age", "23", 30) 47 | name, err := c.GetString("name") 48 | age, err := c.GetInt("age") 49 | fmt.Printf("Name=%s, age=%d", name, age) 50 | user := &User{ 51 | Name: "corel", 52 | Age: 23, 53 | } 54 | c.Set("user", user, 30) 55 | user2 := &User{} 56 | c.GetObject("user", user2) 57 | fmt.Printf("user: %+v", user2) 58 | } 59 | ``` 60 | 61 | ## 特别鸣谢 62 | 63 | - redis缓存部分基于 `github.com/gomodule/redigo` 进行封装 64 | - redis命令参考 http://redisdoc.com/ 和 http://www.runoob.com/redis/ -------------------------------------------------------------------------------- /redis_test.go: -------------------------------------------------------------------------------- 1 | package redisgo 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type User struct { 10 | Name string 11 | Age int 12 | } 13 | 14 | func NoError(t *testing.T, err error) { 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | } 19 | 20 | func Error(t *testing.T, err error) { 21 | if err == nil { 22 | t.Error("Expected an error.") 23 | } 24 | } 25 | 26 | func Equal(t *testing.T, expected, actual interface{}) { 27 | if !reflect.DeepEqual(expected, actual) { 28 | t.Errorf("Not equal: \n"+ 29 | "expected: %+v\n"+ 30 | "actual : %+v", expected, actual) 31 | } 32 | } 33 | 34 | func getCacher() *Cacher { 35 | c, err := New( 36 | Options{ 37 | Prefix: "zengate_", 38 | }) 39 | if err != nil { 40 | panic(err) 41 | } 42 | return c 43 | } 44 | 45 | func TestGetSet(t *testing.T) { 46 | var err error 47 | c := getCacher() 48 | 49 | // int 50 | err = c.Set("age", "23", 30) 51 | NoError(t, err) 52 | valInt, err := c.GetInt("age") 53 | NoError(t, err) 54 | Equal(t, 23, valInt) 55 | 56 | // string 57 | err = c.Set("name", "corel", 30) 58 | NoError(t, err) 59 | valString, err := c.GetString("name") 60 | NoError(t, err) 61 | Equal(t, "corel", valString) 62 | 63 | // bool 64 | err = c.Set("subscribe", true, 30) 65 | NoError(t, err) 66 | valBool, err := c.GetBool("subscribe") 67 | NoError(t, err) 68 | Equal(t, true, valBool) 69 | 70 | // user 71 | user := &User{ 72 | Name: "corel", 73 | Age: 23, 74 | } 75 | err = c.Set("user", user, 30) 76 | NoError(t, err) 77 | valUser := &User{} 78 | err = c.GetObject("user", valUser) 79 | NoError(t, err) 80 | Equal(t, "corel", valUser.Name) 81 | Equal(t, 23, valUser.Age) 82 | } 83 | 84 | func TestIncrDecr(t *testing.T) { 85 | var err error 86 | c := getCacher() 87 | c.Del("seq") 88 | val, err := c.Incr("seq") 89 | NoError(t, err) 90 | Equal(t, int64(1), val) 91 | val, err = c.Incr("seq") 92 | NoError(t, err) 93 | Equal(t, int64(2), val) 94 | val, err = c.IncrBy("seq", 5) 95 | NoError(t, err) 96 | Equal(t, int64(7), val) 97 | val, err = c.Decr("seq") 98 | NoError(t, err) 99 | Equal(t, int64(6), val) 100 | val, err = c.DecrBy("seq", 5) 101 | NoError(t, err) 102 | Equal(t, int64(1), val) 103 | } 104 | 105 | func TestExpire(t *testing.T) { 106 | var err error 107 | c := getCacher() 108 | err = c.Set("name", "corel", 1) 109 | NoError(t, err) 110 | 111 | time.Sleep(2 * time.Second) 112 | 113 | _, err = c.GetString("name") 114 | Error(t, err) 115 | } 116 | 117 | func TestHash(t *testing.T) { 118 | var err error 119 | c := getCacher() 120 | m := make(map[string]interface{}) 121 | m["name"] = "corel" 122 | m["age"] = 23 123 | err = c.HMSet("huser", m, 10) 124 | NoError(t, err) 125 | 126 | age, err := c.HGetInt("huser", "age") 127 | NoError(t, err) 128 | Equal(t, m["age"], age) 129 | } 130 | 131 | func TestSortedSet(t *testing.T) { 132 | var err error 133 | c := getCacher() 134 | _, err = c.ZAdd("scores", 82, "corel") 135 | NoError(t, err) 136 | _, err = c.ZAdd("scores", 86, "zen") 137 | NoError(t, err) 138 | score, err := c.ZScore("scores", "corel") 139 | NoError(t, err) 140 | Equal(t, int64(82), score) 141 | } 142 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | // Package redisgo 基于github.com/gomodule/redigo,封装了redis缓存的实现。 2 | // 使用时,可以参考 http://redisdoc.com/ 和 http://www.runoob.com/redis/ 中对redis命令及用法的讲解。 3 | // 对于没有封装的命令,也可以参考这里的实现方法,直接调用 `Do` 方法直接调用redis命令。 4 | package redisgo 5 | 6 | import ( 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | 15 | // "github.com/aiscrm/cache" 16 | 17 | "github.com/gomodule/redigo/redis" 18 | ) 19 | 20 | // Cacher 先构建一个Cacher实例,然后将配置参数传入该实例的StartAndGC方法来初始化实例和程序进程退出后的清理工作。 21 | type Cacher struct { 22 | pool *redis.Pool 23 | prefix string 24 | marshal func(v interface{}) ([]byte, error) 25 | unmarshal func(data []byte, v interface{}) error 26 | } 27 | 28 | // Options redis配置参数 29 | type Options struct { 30 | Network string // 通讯协议,默认为 tcp 31 | Addr string // redis服务的地址,默认为 127.0.0.1:6379 32 | Password string // redis鉴权密码 33 | Db int // 数据库 34 | MaxActive int // 最大活动连接数,值为0时表示不限制 35 | MaxIdle int // 最大空闲连接数 36 | IdleTimeout int // 空闲连接的超时时间,超过该时间则关闭连接。单位为秒。默认值是5分钟。值为0时表示不关闭空闲连接。此值应该总是大于redis服务的超时时间。 37 | Prefix string // 键名前缀 38 | Marshal func(v interface{}) ([]byte, error) // 数据序列化方法,默认使用json.Marshal序列化 39 | Unmarshal func(data []byte, v interface{}) error // 数据反序列化方法,默认使用json.Unmarshal序列化 40 | } 41 | 42 | // New 根据配置参数创建redis工具实例 43 | func New(options Options) (*Cacher, error) { 44 | r := &Cacher{} 45 | err := r.StartAndGC(options) 46 | return r, err 47 | } 48 | 49 | // StartAndGC 使用 Options 初始化redis,并在程序进程退出时关闭连接池。 50 | func (c *Cacher) StartAndGC(options interface{}) error { 51 | switch opts := options.(type) { 52 | case Options: 53 | if opts.Network == "" { 54 | opts.Network = "tcp" 55 | } 56 | if opts.Addr == "" { 57 | opts.Addr = "127.0.0.1:6379" 58 | } 59 | if opts.MaxIdle == 0 { 60 | opts.MaxIdle = 3 61 | } 62 | if opts.IdleTimeout == 0 { 63 | opts.IdleTimeout = 300 64 | } 65 | if opts.Marshal == nil { 66 | c.marshal = json.Marshal 67 | } 68 | if opts.Unmarshal == nil { 69 | c.unmarshal = json.Unmarshal 70 | } 71 | pool := &redis.Pool{ 72 | MaxActive: opts.MaxActive, 73 | MaxIdle: opts.MaxIdle, 74 | IdleTimeout: time.Duration(opts.IdleTimeout) * time.Second, 75 | 76 | Dial: func() (redis.Conn, error) { 77 | conn, err := redis.Dial(opts.Network, opts.Addr) 78 | if err != nil { 79 | return nil, err 80 | } 81 | if opts.Password != "" { 82 | if _, err := conn.Do("AUTH", opts.Password); err != nil { 83 | conn.Close() 84 | return nil, err 85 | } 86 | } 87 | if _, err := conn.Do("SELECT", opts.Db); err != nil { 88 | conn.Close() 89 | return nil, err 90 | } 91 | return conn, err 92 | }, 93 | 94 | TestOnBorrow: func(conn redis.Conn, t time.Time) error { 95 | _, err := conn.Do("PING") 96 | return err 97 | }, 98 | } 99 | 100 | c.pool = pool 101 | c.closePool() 102 | return nil 103 | default: 104 | return errors.New("Unsupported options") 105 | } 106 | } 107 | 108 | // Do 执行redis命令并返回结果。执行时从连接池获取连接并在执行完命令后关闭连接。 109 | func (c *Cacher) Do(commandName string, args ...interface{}) (reply interface{}, err error) { 110 | conn := c.pool.Get() 111 | defer conn.Close() 112 | return conn.Do(commandName, args...) 113 | } 114 | 115 | // Get 获取键值。一般不直接使用该值,而是配合下面的工具类方法获取具体类型的值,或者直接使用github.com/gomodule/redigo/redis包的工具方法。 116 | func (c *Cacher) Get(key string) (interface{}, error) { 117 | return c.Do("GET", c.getKey(key)) 118 | } 119 | 120 | // GetString 获取string类型的键值 121 | func (c *Cacher) GetString(key string) (string, error) { 122 | return String(c.Get(key)) 123 | } 124 | 125 | // GetInt 获取int类型的键值 126 | func (c *Cacher) GetInt(key string) (int, error) { 127 | return Int(c.Get(key)) 128 | } 129 | 130 | // GetInt64 获取int64类型的键值 131 | func (c *Cacher) GetInt64(key string) (int64, error) { 132 | return Int64(c.Get(key)) 133 | } 134 | 135 | // GetBool 获取bool类型的键值 136 | func (c *Cacher) GetBool(key string) (bool, error) { 137 | return Bool(c.Get(key)) 138 | } 139 | 140 | // GetObject 获取非基本类型stuct的键值。在实现上,使用json的Marshal和Unmarshal做序列化存取。 141 | func (c *Cacher) GetObject(key string, val interface{}) error { 142 | reply, err := c.Get(key) 143 | return c.decode(reply, err, val) 144 | } 145 | 146 | // Set 存并设置有效时长。时长的单位为秒。 147 | // 基础类型直接保存,其他用json.Marshal后转成string保存。 148 | func (c *Cacher) Set(key string, val interface{}, expire int64) error { 149 | value, err := c.encode(val) 150 | if err != nil { 151 | return err 152 | } 153 | if expire > 0 { 154 | _, err := c.Do("SETEX", c.getKey(key), expire, value) 155 | return err 156 | } 157 | _, err = c.Do("SET", c.getKey(key), value) 158 | return err 159 | } 160 | 161 | // Exists 检查键是否存在 162 | func (c *Cacher) Exists(key string) (bool, error) { 163 | return Bool(c.Do("EXISTS", c.getKey(key))) 164 | } 165 | 166 | //Del 删除键 167 | func (c *Cacher) Del(key string) error { 168 | _, err := c.Do("DEL", c.getKey(key)) 169 | return err 170 | } 171 | 172 | // Flush 清空当前数据库中的所有 key,慎用! 173 | func (c *Cacher) Flush() error { 174 | _, err := c.Do("FLUSHDB") 175 | return err 176 | } 177 | 178 | // TTL 以秒为单位。当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 179 | func (c *Cacher) TTL(key string) (ttl int64, err error) { 180 | return Int64(c.Do("TTL", c.getKey(key))) 181 | } 182 | 183 | // Expire 设置键过期时间,expire的单位为秒 184 | func (c *Cacher) Expire(key string, expire int64) error { 185 | _, err := Bool(c.Do("EXPIRE", c.getKey(key), expire)) 186 | return err 187 | } 188 | 189 | // Incr 将 key 中储存的数字值增一 190 | func (c *Cacher) Incr(key string) (val int64, err error) { 191 | return Int64(c.Do("INCR", c.getKey(key))) 192 | } 193 | 194 | // IncrBy 将 key 所储存的值加上给定的增量值(increment)。 195 | func (c *Cacher) IncrBy(key string, amount int64) (val int64, err error) { 196 | return Int64(c.Do("INCRBY", c.getKey(key), amount)) 197 | } 198 | 199 | // Decr 将 key 中储存的数字值减一。 200 | func (c *Cacher) Decr(key string) (val int64, err error) { 201 | return Int64(c.Do("DECR", c.getKey(key))) 202 | } 203 | 204 | // DecrBy key 所储存的值减去给定的减量值(decrement)。 205 | func (c *Cacher) DecrBy(key string, amount int64) (val int64, err error) { 206 | return Int64(c.Do("DECRBY", c.getKey(key), amount)) 207 | } 208 | 209 | // HMSet 将一个map存到Redis hash,同时设置有效期,单位:秒 210 | // Example: 211 | // 212 | // ```golang 213 | // m := make(map[string]interface{}) 214 | // m["name"] = "corel" 215 | // m["age"] = 23 216 | // err := c.HMSet("user", m, 10) 217 | // ``` 218 | func (c *Cacher) HMSet(key string, val interface{}, expire int) (err error) { 219 | conn := c.pool.Get() 220 | defer conn.Close() 221 | err = conn.Send("HMSET", redis.Args{}.Add(c.getKey(key)).AddFlat(val)...) 222 | if err != nil { 223 | return 224 | } 225 | if expire > 0 { 226 | err = conn.Send("EXPIRE", c.getKey(key), int64(expire)) 227 | } 228 | if err != nil { 229 | return 230 | } 231 | conn.Flush() 232 | _, err = conn.Receive() 233 | return 234 | } 235 | 236 | /** Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。 **/ 237 | 238 | // HSet 将哈希表 key 中的字段 field 的值设为 val 239 | // Example: 240 | // 241 | // ```golang 242 | // _, err := c.HSet("user", "age", 23) 243 | // ``` 244 | func (c *Cacher) HSet(key, field string, val interface{}) (interface{}, error) { 245 | value, err := c.encode(val) 246 | if err != nil { 247 | return nil, err 248 | } 249 | return c.Do("HSET", c.getKey(key), field, value) 250 | } 251 | 252 | // HGet 获取存储在哈希表中指定字段的值 253 | // Example: 254 | // 255 | // ```golang 256 | // val, err := c.HGet("user", "age") 257 | // ``` 258 | func (c *Cacher) HGet(key, field string) (reply interface{}, err error) { 259 | reply, err = c.Do("HGET", c.getKey(key), field) 260 | return 261 | } 262 | 263 | // HGetString HGet的工具方法,当字段值为字符串类型时使用 264 | func (c *Cacher) HGetString(key, field string) (reply string, err error) { 265 | reply, err = String(c.HGet(key, field)) 266 | return 267 | } 268 | 269 | // HGetInt HGet的工具方法,当字段值为int类型时使用 270 | func (c *Cacher) HGetInt(key, field string) (reply int, err error) { 271 | reply, err = Int(c.HGet(key, field)) 272 | return 273 | } 274 | 275 | // HGetInt64 HGet的工具方法,当字段值为int64类型时使用 276 | func (c *Cacher) HGetInt64(key, field string) (reply int64, err error) { 277 | reply, err = Int64(c.HGet(key, field)) 278 | return 279 | } 280 | 281 | // HGetBool HGet的工具方法,当字段值为bool类型时使用 282 | func (c *Cacher) HGetBool(key, field string) (reply bool, err error) { 283 | reply, err = Bool(c.HGet(key, field)) 284 | return 285 | } 286 | 287 | // HGetObject HGet的工具方法,当字段值为非基本类型的stuct时使用 288 | func (c *Cacher) HGetObject(key, field string, val interface{}) error { 289 | reply, err := c.HGet(key, field) 290 | return c.decode(reply, err, val) 291 | } 292 | 293 | // HGetAll HGetAll("key", &val) 294 | func (c *Cacher) HGetAll(key string, val interface{}) error { 295 | v, err := redis.Values(c.Do("HGETALL", c.getKey(key))) 296 | if err != nil { 297 | return err 298 | } 299 | 300 | if err := redis.ScanStruct(v, val); err != nil { 301 | fmt.Println(err) 302 | } 303 | //fmt.Printf("%+v\n", val) 304 | return err 305 | } 306 | 307 | /** 308 | Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边) 309 | **/ 310 | 311 | // BLPop 它是 LPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。 312 | // 超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。 313 | func (c *Cacher) BLPop(key string, timeout int) (interface{}, error) { 314 | values, err := redis.Values(c.Do("BLPOP", c.getKey(key), timeout)) 315 | if err != nil { 316 | return nil, err 317 | } 318 | if len(values) != 2 { 319 | return nil, fmt.Errorf("redisgo: unexpected number of values, got %d", len(values)) 320 | } 321 | return values[1], err 322 | } 323 | 324 | // BLPopInt BLPop的工具方法,元素类型为int时 325 | func (c *Cacher) BLPopInt(key string, timeout int) (int, error) { 326 | return Int(c.BLPop(key, timeout)) 327 | } 328 | 329 | // BLPopInt64 BLPop的工具方法,元素类型为int64时 330 | func (c *Cacher) BLPopInt64(key string, timeout int) (int64, error) { 331 | return Int64(c.BLPop(key, timeout)) 332 | } 333 | 334 | // BLPopString BLPop的工具方法,元素类型为string时 335 | func (c *Cacher) BLPopString(key string, timeout int) (string, error) { 336 | return String(c.BLPop(key, timeout)) 337 | } 338 | 339 | // BLPopBool BLPop的工具方法,元素类型为bool时 340 | func (c *Cacher) BLPopBool(key string, timeout int) (bool, error) { 341 | return Bool(c.BLPop(key, timeout)) 342 | } 343 | 344 | // BLPopObject BLPop的工具方法,元素类型为object时 345 | func (c *Cacher) BLPopObject(key string, timeout int, val interface{}) error { 346 | reply, err := c.BLPop(key, timeout) 347 | return c.decode(reply, err, val) 348 | } 349 | 350 | // BRPop 它是 RPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。 351 | // 超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。 352 | func (c *Cacher) BRPop(key string, timeout int) (interface{}, error) { 353 | values, err := redis.Values(c.Do("BRPOP", c.getKey(key), timeout)) 354 | if err != nil { 355 | return nil, err 356 | } 357 | if len(values) != 2 { 358 | return nil, fmt.Errorf("redisgo: unexpected number of values, got %d", len(values)) 359 | } 360 | return values[1], err 361 | } 362 | 363 | // BRPopInt BRPop的工具方法,元素类型为int时 364 | func (c *Cacher) BRPopInt(key string, timeout int) (int, error) { 365 | return Int(c.BRPop(key, timeout)) 366 | } 367 | 368 | // BRPopInt64 BRPop的工具方法,元素类型为int64时 369 | func (c *Cacher) BRPopInt64(key string, timeout int) (int64, error) { 370 | return Int64(c.BRPop(key, timeout)) 371 | } 372 | 373 | // BRPopString BRPop的工具方法,元素类型为string时 374 | func (c *Cacher) BRPopString(key string, timeout int) (string, error) { 375 | return String(c.BRPop(key, timeout)) 376 | } 377 | 378 | // BRPopBool BRPop的工具方法,元素类型为bool时 379 | func (c *Cacher) BRPopBool(key string, timeout int) (bool, error) { 380 | return Bool(c.BRPop(key, timeout)) 381 | } 382 | 383 | // BRPopObject BRPop的工具方法,元素类型为object时 384 | func (c *Cacher) BRPopObject(key string, timeout int, val interface{}) error { 385 | reply, err := c.BRPop(key, timeout) 386 | return c.decode(reply, err, val) 387 | } 388 | 389 | // LPop 移出并获取列表中的第一个元素(表头,左边) 390 | func (c *Cacher) LPop(key string) (interface{}, error) { 391 | return c.Do("LPOP", c.getKey(key)) 392 | } 393 | 394 | // LPopInt 移出并获取列表中的第一个元素(表头,左边),元素类型为int 395 | func (c *Cacher) LPopInt(key string) (int, error) { 396 | return Int(c.LPop(key)) 397 | } 398 | 399 | // LPopInt64 移出并获取列表中的第一个元素(表头,左边),元素类型为int64 400 | func (c *Cacher) LPopInt64(key string) (int64, error) { 401 | return Int64(c.LPop(key)) 402 | } 403 | 404 | // LPopString 移出并获取列表中的第一个元素(表头,左边),元素类型为string 405 | func (c *Cacher) LPopString(key string) (string, error) { 406 | return String(c.LPop(key)) 407 | } 408 | 409 | // LPopBool 移出并获取列表中的第一个元素(表头,左边),元素类型为bool 410 | func (c *Cacher) LPopBool(key string) (bool, error) { 411 | return Bool(c.LPop(key)) 412 | } 413 | 414 | // LPopObject 移出并获取列表中的第一个元素(表头,左边),元素类型为非基本类型的struct 415 | func (c *Cacher) LPopObject(key string, val interface{}) error { 416 | reply, err := c.LPop(key) 417 | return c.decode(reply, err, val) 418 | } 419 | 420 | // RPop 移出并获取列表中的最后一个元素(表尾,右边) 421 | func (c *Cacher) RPop(key string) (interface{}, error) { 422 | return c.Do("RPOP", c.getKey(key)) 423 | } 424 | 425 | // RPopInt 移出并获取列表中的最后一个元素(表尾,右边),元素类型为int 426 | func (c *Cacher) RPopInt(key string) (int, error) { 427 | return Int(c.RPop(key)) 428 | } 429 | 430 | // RPopInt64 移出并获取列表中的最后一个元素(表尾,右边),元素类型为int64 431 | func (c *Cacher) RPopInt64(key string) (int64, error) { 432 | return Int64(c.RPop(key)) 433 | } 434 | 435 | // RPopString 移出并获取列表中的最后一个元素(表尾,右边),元素类型为string 436 | func (c *Cacher) RPopString(key string) (string, error) { 437 | return String(c.RPop(key)) 438 | } 439 | 440 | // RPopBool 移出并获取列表中的最后一个元素(表尾,右边),元素类型为bool 441 | func (c *Cacher) RPopBool(key string) (bool, error) { 442 | return Bool(c.RPop(key)) 443 | } 444 | 445 | // RPopObject 移出并获取列表中的最后一个元素(表尾,右边),元素类型为非基本类型的struct 446 | func (c *Cacher) RPopObject(key string, val interface{}) error { 447 | reply, err := c.RPop(key) 448 | return c.decode(reply, err, val) 449 | } 450 | 451 | // LPush 将一个值插入到列表头部 452 | func (c *Cacher) LPush(key string, member interface{}) error { 453 | value, err := c.encode(member) 454 | if err != nil { 455 | return err 456 | } 457 | _, err = c.Do("LPUSH", c.getKey(key), value) 458 | return err 459 | } 460 | 461 | // RPush 将一个值插入到列表尾部 462 | func (c *Cacher) RPush(key string, member interface{}) error { 463 | value, err := c.encode(member) 464 | if err != nil { 465 | return err 466 | } 467 | _, err = c.Do("RPUSH", c.getKey(key), value) 468 | return err 469 | } 470 | 471 | // LREM 根据参数 count 的值,移除列表中与参数 member 相等的元素。 472 | // count 的值可以是以下几种: 473 | // count > 0 : 从表头开始向表尾搜索,移除与 member 相等的元素,数量为 count 。 474 | // count < 0 : 从表尾开始向表头搜索,移除与 member 相等的元素,数量为 count 的绝对值。 475 | // count = 0 : 移除表中所有与 member 相等的值。 476 | // 返回值:被移除元素的数量。 477 | func (c *Cacher) LREM(key string, count int, member interface{}) (int, error) { 478 | return Int(c.Do("LREM", c.getKey(key), count, member)) 479 | } 480 | 481 | // LLen 获取列表的长度 482 | func (c *Cacher) LLen(key string) (int64, error) { 483 | return Int64(c.Do("RPOP", c.getKey(key))) 484 | } 485 | 486 | // LRange 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。 487 | // 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 488 | // 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。 489 | // 和编程语言区间函数的区别:end 下标也在 LRANGE 命令的取值范围之内(闭区间)。 490 | func (c *Cacher) LRange(key string, start, end int) (interface{}, error) { 491 | return c.Do("LRANGE", c.getKey(key), start, end) 492 | } 493 | 494 | /** 495 | Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。 496 | 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 497 | 有序集合的成员是唯一的,但分数(score)却可以重复。 498 | 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 499 | **/ 500 | 501 | // ZAdd 将一个 member 元素及其 score 值加入到有序集 key 当中。 502 | func (c *Cacher) ZAdd(key string, score int64, member string) (reply interface{}, err error) { 503 | return c.Do("ZADD", c.getKey(key), score, member) 504 | } 505 | 506 | // ZRem 移除有序集 key 中的一个成员,不存在的成员将被忽略。 507 | func (c *Cacher) ZRem(key string, member string) (reply interface{}, err error) { 508 | return c.Do("ZREM", c.getKey(key), member) 509 | } 510 | 511 | // ZScore 返回有序集 key 中,成员 member 的 score 值。 如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。 512 | func (c *Cacher) ZScore(key string, member string) (int64, error) { 513 | return Int64(c.Do("ZSCORE", c.getKey(key), member)) 514 | } 515 | 516 | // ZRank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。score 值最小的成员排名为 0 517 | func (c *Cacher) ZRank(key, member string) (int64, error) { 518 | return Int64(c.Do("ZRANK", c.getKey(key), member)) 519 | } 520 | 521 | // ZRevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。分数值最大的成员排名为 0 。 522 | func (c *Cacher) ZRevrank(key, member string) (int64, error) { 523 | return Int64(c.Do("ZREVRANK", c.getKey(key), member)) 524 | } 525 | 526 | // ZRange 返回有序集中,指定区间内的成员。其中成员的位置按分数值递增(从小到大)来排序。具有相同分数值的成员按字典序(lexicographical order )来排列。 527 | // 以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。或 以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。 528 | func (c *Cacher) ZRange(key string, from, to int64) (map[string]int64, error) { 529 | return redis.Int64Map(c.Do("ZRANGE", c.getKey(key), from, to, "WITHSCORES")) 530 | } 531 | 532 | // ZRevrange 返回有序集中,指定区间内的成员。其中成员的位置按分数值递减(从大到小)来排列。具有相同分数值的成员按字典序(lexicographical order )来排列。 533 | // 以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。或 以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。 534 | func (c *Cacher) ZRevrange(key string, from, to int64) (map[string]int64, error) { 535 | return redis.Int64Map(c.Do("ZREVRANGE", c.getKey(key), from, to, "WITHSCORES")) 536 | } 537 | 538 | // ZRangeByScore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。 539 | // 具有相同分数值的成员按字典序来排列 540 | func (c *Cacher) ZRangeByScore(key string, from, to, offset int64, count int) (map[string]int64, error) { 541 | return redis.Int64Map(c.Do("ZRANGEBYSCORE", c.getKey(key), from, to, "WITHSCORES", "LIMIT", offset, count)) 542 | } 543 | 544 | // ZRevrangeByScore 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列。 545 | // 具有相同分数值的成员按字典序来排列 546 | func (c *Cacher) ZRevrangeByScore(key string, from, to, offset int64, count int) (map[string]int64, error) { 547 | return redis.Int64Map(c.Do("ZREVRANGEBYSCORE", c.getKey(key), from, to, "WITHSCORES", "LIMIT", offset, count)) 548 | } 549 | 550 | /** 551 | Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 552 | Redis 客户端可以订阅任意数量的频道。 553 | 当有新消息通过 PUBLISH 命令发送给频道 channel 时, 这个消息就会被发送给订阅它的所有客户端。 554 | **/ 555 | 556 | // Publish 将信息发送到指定的频道,返回接收到信息的订阅者数量 557 | func (c *Cacher) Publish(channel, message string) (int, error) { 558 | return Int(c.Do("PUBLISH", channel, message)) 559 | } 560 | 561 | // Subscribe 订阅给定的一个或多个频道的信息。 562 | // 支持redis服务停止或网络异常等情况时,自动重新订阅。 563 | // 一般的程序都是启动后开启一些固定channel的订阅,也不会动态的取消订阅,这种场景下可以使用本方法。 564 | // 复杂场景的使用可以直接参考 https://godoc.org/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe 565 | func (c *Cacher) Subscribe(onMessage func(channel string, data []byte) error, channels ...string) error { 566 | conn := c.pool.Get() 567 | psc := redis.PubSubConn{Conn: conn} 568 | err := psc.Subscribe(redis.Args{}.AddFlat(channels)...) 569 | // 如果订阅失败,休息1秒后重新订阅(比如当redis服务停止服务或网络异常) 570 | if err != nil { 571 | fmt.Println(err) 572 | time.Sleep(time.Second) 573 | return c.Subscribe(onMessage, channels...) 574 | } 575 | quit := make(chan int, 1) 576 | 577 | // 处理消息 578 | go func() { 579 | for { 580 | switch v := psc.Receive().(type) { 581 | case redis.Message: 582 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 583 | go onMessage(v.Channel, v.Data) 584 | case redis.Subscription: 585 | fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 586 | case error: 587 | quit <- 1 588 | fmt.Println(err) 589 | return 590 | } 591 | } 592 | }() 593 | 594 | // 异常情况下自动重新订阅 595 | go func() { 596 | <-quit 597 | time.Sleep(time.Second) 598 | psc.Close() 599 | c.Subscribe(onMessage, channels...) 600 | }() 601 | return err 602 | } 603 | 604 | /** 605 | GEO 地理位置 606 | */ 607 | 608 | // GeoOptions 用于GEORADIUS和GEORADIUSBYMEMBER命令的参数 609 | type GeoOptions struct { 610 | WithCoord bool 611 | WithDist bool 612 | WithHash bool 613 | Order string // ASC从近到远,DESC从远到近 614 | Count int 615 | } 616 | 617 | // GeoResult 用于GEORADIUS和GEORADIUSBYMEMBER命令的查询结果 618 | type GeoResult struct { 619 | Name string 620 | Longitude float64 621 | Latitude float64 622 | Dist float64 623 | Hash int64 624 | } 625 | 626 | // GeoAdd 将给定的空间元素(纬度、经度、名字)添加到指定的键里面,这些数据会以有序集合的形式被储存在键里面,所以删除可以使用`ZREM`。 627 | func (c *Cacher) GeoAdd(key string, longitude, latitude float64, member string) error { 628 | _, err := redis.Int(c.Do("GEOADD", c.getKey(key), longitude, latitude, member)) 629 | return err 630 | } 631 | 632 | // GeoPos 从键里面返回所有给定位置元素的位置(经度和纬度)。 633 | func (c *Cacher) GeoPos(key string, members ...interface{}) ([]*[2]float64, error) { 634 | args := redis.Args{} 635 | args = args.Add(c.getKey(key)) 636 | args = args.Add(members...) 637 | return redis.Positions(c.Do("GEOPOS", args...)) 638 | } 639 | 640 | // GeoDist 返回两个给定位置之间的距离。 641 | // 如果两个位置之间的其中一个不存在, 那么命令返回空值。 642 | // 指定单位的参数 unit 必须是以下单位的其中一个: 643 | // m 表示单位为米。 644 | // km 表示单位为千米。 645 | // mi 表示单位为英里。 646 | // ft 表示单位为英尺。 647 | // 如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。 648 | func (c *Cacher) GeoDist(key string, member1, member2, unit string) (float64, error) { 649 | _, err := redis.Float64(c.Do("GEODIST", c.getKey(key), member1, member2, unit)) 650 | return 0, err 651 | } 652 | 653 | // GeoRadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。 654 | func (c *Cacher) GeoRadius(key string, longitude, latitude, radius float64, unit string, options GeoOptions) ([]*GeoResult, error) { 655 | args := redis.Args{} 656 | args = args.Add(c.getKey(key), longitude, latitude, radius, unit) 657 | if options.WithDist { 658 | args = args.Add("WITHDIST") 659 | } 660 | if options.WithCoord { 661 | args = args.Add("WITHCOORD") 662 | } 663 | if options.WithHash { 664 | args = args.Add("WITHHASH") 665 | } 666 | if options.Order != "" { 667 | args = args.Add(options.Order) 668 | } 669 | if options.Count > 0 { 670 | args = args.Add("Count", options.Count) 671 | } 672 | 673 | reply, err := c.Do("GEORADIUS", args...) 674 | return toGeoResult(reply, err, options) 675 | } 676 | 677 | // GeoRadiusByMember 这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。 678 | func (c *Cacher) GeoRadiusByMember(key string, member string, radius float64, unit string, options GeoOptions) ([]*GeoResult, error) { 679 | args := redis.Args{} 680 | args = args.Add(c.getKey(key), member, radius, unit) 681 | if options.WithDist { 682 | args = args.Add("WITHDIST") 683 | } 684 | if options.WithCoord { 685 | args = args.Add("WITHCOORD") 686 | } 687 | if options.WithHash { 688 | args = args.Add("WITHHASH") 689 | } 690 | if options.Order != "" { 691 | args = args.Add(options.Order) 692 | } 693 | if options.Count > 0 { 694 | args = args.Add("Count", options.Count) 695 | } 696 | 697 | reply, err := c.Do("GEORADIUSBYMEMBER", args...) 698 | return toGeoResult(reply, err, options) 699 | } 700 | 701 | // GeoHash 返回一个或多个位置元素的 Geohash 表示。 702 | func (c *Cacher) GeoHash(key string, members ...interface{}) ([]string, error) { 703 | args := redis.Args{} 704 | args = args.Add(c.getKey(key)) 705 | args = args.Add(members...) 706 | return redis.Strings(c.Do("GEOHASH", args...)) 707 | } 708 | 709 | func toGeoResult(reply interface{}, err error, options GeoOptions) ([]*GeoResult, error) { 710 | values, err := redis.Values(reply, err) 711 | if err != nil { 712 | return nil, err 713 | } 714 | results := make([]*GeoResult, len(values)) 715 | for i := range values { 716 | if values[i] == nil { 717 | continue 718 | } 719 | p, ok := values[i].([]interface{}) 720 | if !ok { 721 | return nil, fmt.Errorf("redisgo: unexpected element type for interface slice, got type %T", values[i]) 722 | } 723 | geoResult := &GeoResult{} 724 | pos := 0 725 | name, err := redis.String(p[pos], nil) 726 | if err != nil { 727 | return nil, err 728 | } 729 | geoResult.Name = name 730 | if options.WithDist { 731 | pos = pos + 1 732 | dist, err := redis.Float64(p[pos], nil) 733 | if err != nil { 734 | return nil, err 735 | } 736 | geoResult.Dist = dist 737 | } 738 | if options.WithHash { 739 | pos = pos + 1 740 | hash, err := redis.Int64(p[pos], nil) 741 | if err != nil { 742 | return nil, err 743 | } 744 | geoResult.Hash = hash 745 | } 746 | if options.WithCoord { 747 | pos = pos + 1 748 | pp, ok := p[pos].([]interface{}) 749 | if !ok { 750 | return nil, fmt.Errorf("redisgo: unexpected element type for interface slice, got type %T", p[i]) 751 | } 752 | if len(pp) > 0 { 753 | lat, err := redis.Float64(pp[0], nil) 754 | if err != nil { 755 | return nil, err 756 | } 757 | lon, err := redis.Float64(pp[1], nil) 758 | if err != nil { 759 | return nil, err 760 | } 761 | geoResult.Latitude = lat 762 | geoResult.Longitude = lon 763 | } 764 | } 765 | if err != nil { 766 | return nil, err 767 | } 768 | results[i] = geoResult 769 | } 770 | return results, nil 771 | } 772 | 773 | // getKey 将健名加上指定的前缀。 774 | func (c *Cacher) getKey(key string) string { 775 | return c.prefix + key 776 | } 777 | 778 | // encode 序列化要保存的值 779 | func (c *Cacher) encode(val interface{}) (interface{}, error) { 780 | var value interface{} 781 | switch v := val.(type) { 782 | case string, int, uint, int8, int16, int32, int64, float32, float64, bool: 783 | value = v 784 | default: 785 | b, err := c.marshal(v) 786 | if err != nil { 787 | return nil, err 788 | } 789 | value = string(b) 790 | } 791 | return value, nil 792 | } 793 | 794 | // decode 反序列化保存的struct对象 795 | func (c *Cacher) decode(reply interface{}, err error, val interface{}) error { 796 | str, err := String(reply, err) 797 | if err != nil { 798 | return err 799 | } 800 | return c.unmarshal([]byte(str), val) 801 | } 802 | 803 | // closePool 程序进程退出时关闭连接池 804 | func (c *Cacher) closePool() { 805 | ch := make(chan os.Signal, 1) 806 | signal.Notify(ch, os.Interrupt) 807 | signal.Notify(ch, syscall.SIGTERM) 808 | signal.Notify(ch, syscall.SIGKILL) 809 | go func() { 810 | <-ch 811 | c.pool.Close() 812 | os.Exit(0) 813 | }() 814 | } 815 | 816 | // init 注册到cache 817 | // func init() { 818 | // cache.Register("redis", &Cacher{}) 819 | // } 820 | --------------------------------------------------------------------------------