Redis 基本数据结构详解
String
-
底层数据实现是SDS( S i m p l e D y n a m i c S t r i n g Simple Dynamic String SimpleDynamicString),优点有:相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1),并且不会出现缓冲区溢出。
-
常用的命令有:
命令 介绍 SET key value 设置指定 key 的值 SETNX key value 只有在 key 不存在时设置 key 的值 GET key 获取指定 key 的值 INCR key 将 key 中储存的数字值增一 DECR key 将 key 中储存的数字值减一 EXISTS key 判断指定 key 是否存在 DEL key(通用) 删除指定的 key EXPIRE key seconds(通用) 给指定 key 设置过期时间 MSET key1 value1 key2 value2 … 设置一个或多个指定 key 的值 MGET key1 key2 … 获取一个或多个指定 key 的值 STRLEN key 返回 key 所储存的字符串值的长度 -
基本应用场景
需要存储常规数据的场景
- 举例:缓存 session、token、图片地址、序列化后的对象(相比较于 Hash 存储更节省内存)。
- 相关命令:
SET
、GET
。
需要计数的场景
- 举例:用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数。
- 相关命令:
SET
、GET
、INCR
、DECR
。
分布式锁
利用
SETNX key value
命令可以实现一个最简易的分布式锁(存在一些缺陷,通常不建议这样实现分布式锁)。 -
代码实例:
package main import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" ) var redisClient *redis.Client type Data struct { Key string `from:"key",json:"key"` Value string `from:"key",json:"value"` } func main() { redisClient = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) r := gin.Default() r.POST("/set/", setStringHandler) r.GET("/get", getStringHandler) r.POST("/delete", deleteStringHandler) r.POST("/incr", incrStringHandler) r.POST("/decr", decrStringHandler) r.POST("/exist", existStringHandler) r.POST("/expire", expireStringHandler) r.POST("/mset", mSetStringHandler) r.POST("/mget", mGetStringHandler) r.Run(":8080") } func mSetStringHandler(c *gin.Context) { var kvs map[string]string err := c.ShouldBindJSON(&kvs) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "MSet请求数据绑定失败!!!!!", }) return } var pairs []interface{} for k, v := range kvs { pairs = append(pairs, k, v) } err = redisClient.MSet(c, pairs...).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "MSet数据批处理绑定失败!!!!!", }) return } c.JSON(http.StatusOK, gin.H{ "message": "MSet 批处理数据成功!!!!", }) } func mGetStringHandler(c *gin.Context) { var keys []string err := c.ShouldBindJSON(&keys) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "MGet请求数据绑定失败!!!!!", }) return } res, err := redisClient.MGet(c, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "MGet数据批处理绑定失败!!!!!", }) return } keyValueMap := make(map[string]interface{}) for i, key := range keys { keyValueMap[key] = res[i] } c.JSON(http.StatusOK, gin.H{ "message": "MGet 批处理数据成功!!!!", "data": keyValueMap, }) } func expireStringHandler(c *gin.Context) { key := c.PostForm("key") durationStr := c.PostForm("duration") duration, err := time.ParseDuration(durationStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "duration 解析失败!!!!", }) return } _, err = redisClient.Expire(c, key, duration).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "设置过期时间失败!!!!", }) return } c.JSON(http.StatusOK, gin.H{ "message": "String 数据设置过期时间成功!!!!", //"expire": fmt.Sprintf("%t", res), }) } func existStringHandler(c *gin.Context) { key := c.PostForm("key") nums, err := redisClient.Exists(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "所请求的查询失败!!!!", }) return } c.JSON(http.StatusOK, gin.H{ "nums": nums, "message": "数据查询成功!!!!", }) } func incrStringHandler(c *gin.Context) { key := c.PostForm("key") err := redisClient.Incr(c, key).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "所请求的数据新增1失败!!!!", }) return } c.JSON(http.StatusOK, gin.H{ "message": "所请求的数据新增1成功!!!!", }) } func decrStringHandler(c *gin.Context) { key := c.PostForm("key") err := redisClient.Decr(c, key).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "所请求的数据减少1失败!!!!", }) return } c.JSON(http.StatusOK, gin.H{ "message": "所请求的数据减少1成功!!!!", }) } func deleteStringHandler(c *gin.Context) { key := c.PostForm("key") err := redisClient.Del(c, key).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "String数据删除失败!!!!", }) return } c.JSON(http.StatusOK, gin.H{ "message": "String数据删除成功!!!", }) } func setStringHandler(c *gin.Context) { var data = Data{} if err := c.ShouldBindJSON(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "数据绑定失败!!!", }) return } err := redisClient.Set(c, data.Key, data.Value, 0).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "String类型数据插入失败!!!", }) return } c.JSON(http.StatusOK, gin.H{ "message": "String类型数据插入成功!!!!", }) } func getStringHandler(c *gin.Context) { key := c.Query("key") value, err := redisClient.Get(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "String数据查询失败!!!", }) return } c.JSON(http.StatusOK, gin.H{ "key": key, "value": value, "message": "String类型数据查询成功!!!", }) }
List
-
底层实现为:LinkedList/ZipList/QuickList
-
常用命令有:
命令 介绍 RPUSH key value1 value2 … 在指定列表的尾部(右边)添加一个或多个元素 LPUSH key value1 value2 … 在指定列表的头部(左边)添加一个或多个元素 LSET key index value 将指定列表索引 index 位置的值设置为 value LPOP key 移除并获取指定列表的第一个元素(最左边) RPOP key 移除并获取指定列表的最后一个元素(最右边) LLEN key 获取列表元素数量 LRANGE key start end 获取列表 start 和 end 之间 的元素 -
使用场景为:
信息流展示
- 举例:最新文章、最新动态。
- 相关命令:
LPUSH
、LRANGE
。
消息队列
通过
RPUSH/RPOP
或者LPUSH/LPOP
实现栈 -
实现代码:
package main import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" ) var redisClient *redis.Client func main() { redisClient = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) r := gin.Default() r.POST("/rpush", rPushHandler) r.POST("/lpush", lPushHandler) r.POST("/lset", lSetHandler) r.GET("/lpop", lPopHandler) r.GET("/rpop", rPopHandler) r.GET("/llen", lLenHandler) r.GET("/lrange", lRangeHandler) r.Run(":8080") } // 在指定列表的尾部(右边)添加一个或多个元素 func rPushHandler(c *gin.Context) { key := c.PostForm("key") values := c.PostFormArray("values") strValues := make([]interface{}, len(values)) for i, val := range values { strValues[i] = val } err := redisClient.RPush(c, key, strValues...).Err() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "RPUSH操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "RPUSH操作成功", }) } // 在指定列表的头部(左边)添加一个或多个元素 func lPushHandler(c *gin.Context) { key := c.PostForm("key") values := c.PostFormArray("values") strValues := make([]interface{}, len(values)) for i, val := range values { strValues[i] = val } err := redisClient.LPush(c, key, strValues...).Err() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "LPUSH操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "LPUSH操作成功", }) } // 将指定列表索引 index 位置的值设置为 value,相当于修改index这个位置上的值 func lSetHandler(c *gin.Context) { key := c.PostForm("key") index, _ := strconv.ParseInt(c.PostForm("index"), 10, 64) value := c.PostForm("value") err := redisClient.LSet(c, key, index, value).Err() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "LSET操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "LSET操作成功", }) } // 移除并获取指定列表的第一个元素(最左边) func lPopHandler(c *gin.Context) { key := c.Query("key") value, err := redisClient.LPop(c, key).Result() if err != nil && err != redis.Nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "LPOP操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "value": value, "message": "LPOP操作成功", }) } // 移除并获取指定列表的最后一个元素(最右边) func rPopHandler(c *gin.Context) { key := c.Query("key") value, err := redisClient.RPop(c, key).Result() if err != nil && err != redis.Nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "RPOP操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "value": value, "message": "RPOP操作成功", }) } // 获取列表元素数量 func lLenHandler(c *gin.Context) { key := c.Query("key") length, err := redisClient.LLen(c, key).Result() if err != nil && err != redis.Nil { //redis.Nil防止查询的key为不在数据库中的值 c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "LLEN操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "listName": key, "length": length, "message": "LLEN操作成功", }) } // LRANGE key start end func lRangeHandler(c *gin.Context) { key := c.Query("key") start, _ := strconv.ParseInt(c.Query("start"), 10, 64) end, _ := strconv.ParseInt(c.Query("end"), 10, 64) result, err := redisClient.LRange(c, key, start, end).Result() if err != nil && err != redis.Nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "LRANGE操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "listName": key, "result": result, "message": "LRANGE操作成功", }) }
Hash(哈希)
-
底层实现为:Dict、ZipList
-
常用命令有:
命令 介绍 HSET key field value 设置指定哈希表中指定字段的值 HSETNX key field value 只有指定字段不存在时设置指定字段的值 HINCRBY key field increment 对指定哈希中的指定字段做运算操作(正数为加,负数为减) HGET key field 获取指定哈希表中指定字段的值 HMGET key field1 field2 … 获取指定哈希表中一个或者多个指定字段的值 HGETALL key 获取指定哈希表中所有的键值对 HEXISTS key field 查看指定哈希表中指定的字段是否存在 HDEL key field1 field2 … 删除一个或多个哈希表字段 HLEN key 获取指定哈希表中字段的数量 -
使用场景
对象数据存储场景
- 举例:用户信息、商品信息、文章信息、购物车信息。
- 相关命令:
HSET
(设置单个字段的值)、HMSET
(设置多个字段的值)、HGET
(获取单个字段的值)、HMGET
(获取多个字段的值)。
-
实现代码
package main import ( "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" "net/http" "strconv" ) var redisClient *redis.Client type Data struct { Key string `form:"key"json:"key"` Field string `from:"field"json:"field"` Value string `from:"value"json:"value"` } func main() { redisClient = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) r := gin.Default() r.POST("/hset", hsetHandler) r.POST("/hsetnx", hsetnxHandler) r.GET("/hget", hgetHandler) r.GET("/hmget", hmgetHandler) r.GET("/hgetall", hgetallHandler) r.GET("/hexists", hexistsHandler) r.POST("/hdel", hdelHandler) r.GET("/hlen", hlenHandler) r.POST("/hincrby", hincrbyHandler) r.Run(":8080") } // 设置指定哈希表中指定字段的值 func hsetHandler(c *gin.Context) { var data Data if err := c.ShouldBindJSON(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "数据绑定失败", }) return } err := redisClient.HSet(c, data.Key, data.Field, data.Value).Err() //如何实现string和map类型 if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "设置指定哈希表中指定字段的值失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "设置指定哈希表中指定字段的值成功", }) } // 只有指定字段不存在时设置指定字段的值 func hsetnxHandler(c *gin.Context) { var data Data if err := c.ShouldBindJSON(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "数据绑定失败", }) return } result, err := redisClient.HSetNX(c, data.Key, data.Field, data.Value).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "只有指定字段不存在时设置指定字段的值失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "hsetnx只有指定字段不存在时设置指定字段的值成功", "result": result, }) } // 获取指定哈希表中指定字段的值 func hgetHandler(c *gin.Context) { key := c.Query("key") field := c.Query("field") value, err := redisClient.HGet(c, key, field).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定哈希表中指定字段的值失败", }) return } c.JSON(http.StatusOK, gin.H{ "value": value, "message": "获取指定哈希表中指定字段的值成功", }) } // 获取指定哈希表中一个或者多个指定字段的值. func hmgetHandler(c *gin.Context) { key := c.Query("key") fields := c.QueryArray("fields") values, err := redisClient.HMGet(c, key, fields...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定哈希表中一个或者多个指定字段的值失败", }) return } result := make(map[string]interface{}) for i, field := range fields { result[field] = values[i] } c.JSON(http.StatusOK, gin.H{ "data": result, "message": "获取指定哈希表中一个或者多个指定字段的值成功", }) } // 获取指定哈希表中所有的键值对 func hgetallHandler(c *gin.Context) { key := c.Query("key") result, err := redisClient.HGetAll(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定哈希表中所有的键值对失败", }) return } c.JSON(http.StatusOK, gin.H{ "data": result, "message": "获取指定哈希表中所有的键值对成功", }) } // 查看指定哈希表中指定的字段是否存在 func hexistsHandler(c *gin.Context) { key := c.Query("key") field := c.Query("field") result, err := redisClient.HExists(c, key, field).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "查看指定哈希表中指定的字段是否存在失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "查看指定哈希表中指定的字段是否存在成功", }) } // 删除一个或多个哈希表字段 func hdelHandler(c *gin.Context) { key := c.PostForm("key") fields := c.PostFormArray("fields") count, err := redisClient.HDel(c, key, fields...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "删除一个或多个哈希表字段失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "删除一个或多个哈希表字段成功", }) } // 获取指定哈希表中字段的数量 func hlenHandler(c *gin.Context) { key := c.Query("key") count, err := redisClient.HLen(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定哈希表中字段的数量失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "获取指定哈希表中字段的数量成功", }) } // 对指定哈希中的指定字段做运算操作(正数为加,负数为减) func hincrbyHandler(c *gin.Context) { key := c.PostForm("key") field := c.PostForm("field") increment := c.PostForm("increment") inc, _ := strconv.ParseInt(increment, 10, 64) result, err := redisClient.HIncrBy(c, key, field, inc).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "对指定哈希中的指定字段做运算操作失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "对指定哈希中的指定字段做运算操作成功", }) }
Set
-
底层实现:Dict、Intset
-
常用命令:
命令 介绍 SADD key member1 member2 … 向指定集合添加一个或多个元素 SMEMBERS key 获取指定集合中的所有元素 SCARD key 获取指定集合的元素数量 SISMEMBER key member 判断指定元素是否在指定集合中 SINTER key1 key2 … 获取给定所有集合的交集 SINTERSTORE destination key1 key2 … 将给定所有集合的交集存储在 destination 中 SUNION key1 key2 … 获取给定所有集合的并集 SUNIONSTORE destination key1 key2 … 将给定所有集合的并集存储在 destination 中 SDIFF key1 key2 … 获取给定所有集合的差集 SDIFFSTORE destination key1 key2 … 将给定所有集合的差集存储在 destination 中 SPOP key count 随机移除并获取指定集合中一个或多个元素 SRANDMEMBER key count 随机获取指定集合中指定数量的元素 -
使用场景:
需要存放的数据不能重复的场景
- 举例:网站 UV 统计(数据量巨大的场景还是
HyperLogLog
更适合一些)、文章点赞、动态点赞等场景。 - 相关命令:
SCARD
(获取集合数量) 。
需要获取多个数据源交集、并集和差集的场景
- 举例:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等场景。
- 相关命令:
SINTER
(交集)、SINTERSTORE
(交集)、SUNION
(并集)、SUNIONSTORE
(并集)、SDIFF
(差集)、SDIFFSTORE
(差集)。
需要随机获取数据源中的元素的场景
- 举例:抽奖系统、随机点名等场景。
- 相关命令:
SPOP
(随机获取集合中的元素并移除,适合不允许重复中奖的场景)、SRANDMEMBER
(随机获取集合中的元素,适合允许重复中奖的场景)
- 举例:网站 UV 统计(数据量巨大的场景还是
-
相关代码:
package main import ( "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" "net/http" "strconv" ) var redisClient *redis.Client type Data struct { Key string `form:"key" json:"key"` Members []string `form:"members[]" json:"members"` Field string `form:"field" json:"field"` Value string `form:"value" json:"value"` } func main() { redisClient = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) r := gin.Default() r.POST("/sadd", saddHandler) r.GET("/smembers", smembersHandler) r.GET("/scard", scardHandler) r.GET("/sismember", sismemberHandler) r.GET("/sinter", sinterHandler) r.GET("/sinterstore", sinterstoreHandler) r.GET("/sunion", sunionHandler) r.GET("/sunionstore", sunionstoreHandler) r.GET("/sdiff", sdiffHandler) r.GET("/sdiffstore", sdiffstoreHandler) r.GET("/spop", spopHandler) r.GET("/srandmember", srandmemberHandler) r.Run(":8080") } // 向指定集合添加一个或多个元素 func saddHandler(c *gin.Context) { var data Data if err := c.ShouldBindJSON(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "数据绑定失败", }) return } members := make([]interface{}, len(data.Members)) for i, member := range data.Members { members[i] = member } result, err := redisClient.SAdd(c, data.Key, members...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "向指定集合添加元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "向指定集合添加元素成功", }) } // 获取指定集合中的所有元素 func smembersHandler(c *gin.Context) { key := c.Query("key") result, err := redisClient.SMembers(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定集合中的所有元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "获取指定集合中的所有元素成功", }) } // 获取指定集合的元素数量 func scardHandler(c *gin.Context) { key := c.Query("key") count, err := redisClient.SCard(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定集合的元素数量失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "获取指定集合的元素数量成功", }) } // 判断指定元素是否在指定集合中 func sismemberHandler(c *gin.Context) { key := c.Query("key") member := c.Query("member") result, err := redisClient.SIsMember(c, key, member).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "判断指定元素是否在指定集合中失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "判断指定元素是否在指定集合中成功", }) } // 获取给定所有集合的交集 func sinterHandler(c *gin.Context) { keys := c.QueryArray("keys") result, err := redisClient.SInter(c, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取给定所有集合的交集失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "获取给定所有集合的交集成功", }) } // 将给定所有集合的交集存储在 destination 中 func sinterstoreHandler(c *gin.Context) { destination := c.Query("destination") keys := c.QueryArray("keys") count, err := redisClient.SInterStore(c, destination, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "将给定所有集合的交集存储失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "将给定所有集合的交集存储成功", }) } // 获取给定所有集合的并集 func sunionHandler(c *gin.Context) { keys := c.QueryArray("keys") result, err := redisClient.SUnion(c, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取给定所有集合的并集失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "获取给定所有集合的并集成功", }) } // 将给定所有集合的并集存储在 destination 中 func sunionstoreHandler(c *gin.Context) { destination := c.Query("destination") keys := c.QueryArray("keys") count, err := redisClient.SUnionStore(c, destination, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "将给定所有集合的并集存储失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "将给定所有集合的并集存储成功", }) } // 获取给定所有集合的差集 func sdiffHandler(c *gin.Context) { keys := c.QueryArray("keys") result, err := redisClient.SDiff(c, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取给定所有集合的差集失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "获取给定所有集合的差集成功", }) } // 将给定所有集合的差集存储在 destination 中 func sdiffstoreHandler(c *gin.Context) { destination := c.Query("destination") keys := c.QueryArray("keys") count, err := redisClient.SDiffStore(c, destination, keys...).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "将给定所有集合的差集存储失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "将给定所有集合的差集存储成功", }) } // 随机移除并获取指定集合中一个 func spopHandler(c *gin.Context) { key := c.Query("key") result, err := redisClient.SPop(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "移除指定集合中的元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "移除指定集合中的元素成功", }) } // 随机获取指定集合中一个或多个元素 func srandmemberHandler(c *gin.Context) { key := c.Query("key") count, _ := strconv.Atoi(c.DefaultQuery("count", "1")) result, err := redisClient.SRandMemberN(c, key, int64(count)).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "随机获取指定集合中的元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "随机获取指定集合中的元素成功", }) }
Sorted Set(ZSet)
-
底层实现:ZipList、SkipList
-
基本命令:
命令 介绍 ZADD key score1 member1 score2 member2 … 向指定有序集合添加一个或多个元素 ZCARD KEY 获取指定有序集合的元素数量 ZSCORE key member 获取指定有序集合中指定元素的 score 值 ZINTERSTORE destination numkeys key1 key2 … 将给定所有有序集合的交集存储在 destination 中,对相同元素对应的 score 值进行 SUM 聚合操作,numkeys 为集合数量 ZUNIONSTORE destination numkeys key1 key2 … 求并集,其它和 ZINTERSTORE 类似 ZDIFFSTORE destination numkeys key1 key2 … 求差集,其它和 ZINTERSTORE 类似 ZRANGE key start end 获取指定有序集合 start 和 end 之间的元素(score 从低到高) ZREVRANGE key start end 获取指定有序集合 start 和 end 之间的元素(score 从高到底) ZREVRANK key member 获取指定有序集合中指定元素的排名(score 从大到小排序) -
使用场景:
需要随机获取数据源中的元素根据某个权重进行排序的场景
- 举例:各种排行榜比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
- 相关命令:
ZRANGE
(从小到大排序)、ZREVRANGE
(从大到小排序)、ZREVRANK
(指定元素排名)。
需要存储的数据有优先级或者重要程度的场景 比如优先级任务队列。
- 举例:优先级任务队列。
- 相关命令:
ZRANGE
(从小到大排序)、ZREVRANGE
(从大到小排序)、ZREVRANK
(指定元素排名)。
-
实现代码:
package main import ( "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" "net/http" "strconv" ) var redisClient *redis.Client type Data struct { Key string `form:"key" json:"key"` Score int64 `form:"score" json:"score"` Member string `form:"member" json:"member"` } func main() { redisClient = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) r := gin.Default() r.POST("/zadd", zaddHandler) r.GET("/zcard", zcardHandler) r.GET("/zscore", zscoreHandler) r.POST("/zinterstore", zinterstoreHandler) r.POST("/zunionstore", zunionstoreHandler) r.POST("/zdiffstore", zdiffstoreHandler) r.GET("/zrange", zrangeHandler) r.GET("/zrevrange", zrevrangeHandler) r.GET("/zrevrank", zrevrankHandler) r.Run(":8080") } // 向指定有序集合添加一个或多个元素 func zaddHandler(c *gin.Context) { var data []Data if err := c.ShouldBindJSON(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "数据绑定失败", }) return } z := make([]*redis.Z, len(data)) for i, d := range data { z[i] = &redis.Z{ Score: float64(d.Score), Member: d.Member, } } ctx := c.Request.Context() // 从 gin 的上下文中获取 context.Context err := redisClient.ZAdd(ctx, data[0].Key, z...).Err() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "message": "向指定有序集合添加元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "向指定有序集合添加元素成功", }) //以下写法会报错,报错内容为:redis: can't marshal *string (implement encoding.BinaryMarshaler) //var data []Data //if err := c.ShouldBindJSON(&data); err != nil { // c.JSON(http.StatusBadRequest, gin.H{ // "error": err.Error(), // "message": "数据绑定失败", // }) // return //} // //z := make([]*redis.Z, 0) //for _, d := range data { // //z[i].Member = &d.Member // //z[i].Score = float64(d.Score) // z = append(z, &redis.Z{ // Score: float64(d.Score), // Member: &d.Member, // }) //} // //err := redisClient.ZAdd(c, data[0].Key, z...).Err() //if err != nil { // c.JSON(http.StatusBadRequest, gin.H{ // "error": err.Error(), // "message": "向指定有序集合添加元素失败", // }) // return //} // //c.JSON(http.StatusOK, gin.H{ // "message": "向指定有序集合添加元素成功", //}) } // 获取指定有序集合的元素数量 func zcardHandler(c *gin.Context) { key := c.Query("key") count, err := redisClient.ZCard(c, key).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定有序集合的元素数量失败", }) return } c.JSON(http.StatusOK, gin.H{ "count": count, "message": "获取指定有序集合的元素数量成功", }) } // 获取指定有序集合中指定元素的 score 值 func zscoreHandler(c *gin.Context) { key := c.Query("key") member := c.Query("member") score, err := redisClient.ZScore(c, key, member).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定有序集合中指定元素的 score 值失败", }) return } c.JSON(http.StatusOK, gin.H{ "score": score, "message": "获取指定有序集合中指定元素的 score 值成功", }) } // 将给定所有有序集合的交集存储在 destination 中,对相同元素对应的 score 值进行 SUM 聚合操作 func zinterstoreHandler(c *gin.Context) { destination := c.PostForm("destination") _, _ = strconv.Atoi(c.PostForm("numKeys")) keys := c.PostFormArray("keys") err := redisClient.ZInterStore(c, destination, &redis.ZStore{ Keys: keys, Aggregate: "SUM", }).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "将给定所有有序集合的交集存储失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "将给定所有有序集合的交集存储成功", }) } // 求并集,其它和 ZINTERSTORE 类似 func zunionstoreHandler(c *gin.Context) { destination := c.PostForm("destination") _, _ = strconv.Atoi(c.PostForm("numkeys")) keys := c.PostFormArray("keys") err := redisClient.ZUnionStore(c, destination, &redis.ZStore{ Keys: keys, Aggregate: "SUM", }).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "求并集失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "求并集成功", }) } // 求差集,其它和 ZINTERSTORE 类似 func zdiffstoreHandler(c *gin.Context) { destination := c.PostForm("destination") _, _ = strconv.Atoi(c.PostForm("numkeys")) keys := c.PostFormArray("keys") err := redisClient.ZInterStore(c, destination, &redis.ZStore{ Keys: keys, Aggregate: "SUM", }).Err() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "求差集失败", }) return } c.JSON(http.StatusOK, gin.H{ "message": "求差集成功", }) } // 获取指定有序集合 start 和 end 之间的元素(score 从低到高) func zrangeHandler(c *gin.Context) { key := c.Query("key") start, _ := strconv.Atoi(c.Query("start")) end, _ := strconv.Atoi(c.Query("end")) result, err := redisClient.ZRange(c, key, int64(start), int64(end)).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定有序集合元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "获取指定有序集合元素成功", }) } // 获取指定有序集合 start 和 end 之间的元素(score 从高到底) func zrevrangeHandler(c *gin.Context) { key := c.Query("key") start, _ := strconv.Atoi(c.Query("start")) end, _ := strconv.Atoi(c.Query("end")) result, err := redisClient.ZRevRange(c, key, int64(start), int64(end)).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定有序集合元素失败", }) return } c.JSON(http.StatusOK, gin.H{ "result": result, "message": "获取指定有序集合元素成功", }) } // 获取指定有序集合中指定元素的排名(score 从大到小排序) func zrevrankHandler(c *gin.Context) { key := c.Query("key") member := c.Query("member") rank, err := redisClient.ZRevRank(c, key, member).Result() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), "message": "获取指定有序集合中指定元素的排名失败", }) return } c.JSON(http.StatusOK, gin.H{ "rank": rank, "message": "获取指定有序集合中指定元素的排名成功", }) }