简介
- 关于key的定义,注意如下几点:
- 不建议key名字太长,通常不超过1024,如果太长会影响查询的速度。
- 不建议太短,太短会降低可读性。
- 一般在公司,都有统一命名规范。
1.字符串类型string
1.1概述
字符串类型是Redis中最为基础的类型,它在Redis中以二进制保存,没有编码和解码的过程。无论存入的是字符串、整数、浮点类型都会以字符串写入。在Redis中字符串类型的Value最多可以容纳的数据长度是512M。这是以后最常用的数据类型。redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
1.2常用命令
添加一个key
//设置一个key value
set name liwenchao
//设置一个3秒的key,value, 3秒后查询就为nil
set name2 lili ex 3
获取一个key的内容
//可以重复多次定义一个key
set name liwenchao
//如上面演示内容
get name
删除一个key
//删除name=liwenchao这个key
del name
批量设置多个key,value
//mset key1 value1 key2 value2
mset name1 liwenchao name2 gaolili
批量根据key获取多个value
//mget key1 key2
mget name1 name2
追加数据
//append key1 appendvalue
append name1 app
数值递增
//设置一个key,value为int的值
set num 10
//让num的value自动+1
incr num
数值递减
//让num的value自动-1
decr num
获取字符串key的长度
//获取key为name1的value的长度
strlen name1
判断key是否存在
//有会返回1,没有会返回0
exists name1
查看key的过期时间
//ttl 查看key的剩余生存时间
// -1为永不过期,
// -2没有这个key
// num key的剩余有效时间
重新设置key的过期时间
expire name3 120
取消key的过期时间变为永久
persist name3
1.3应用场景
缓存
使用 String 来缓存对象有两种方式:
-
直接缓存整个对象的 JSON
例如,
SET user:1 '{"name":"wjm", "age":18}'
。 -
采用将 key 进行分离为 user:ID:属性,采用 MSET 存储,用 MGET 获取各属性值
例如,
MSET user:1:name wjm user:1:age 18 user:2:name xiaoli user:2:age 20
。
计数
因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
例如,计算文章的阅读量:
# 初始化文章的阅读量 | |
> SET aritcle:readcount:1001 0 | |
OK | |
# 阅读量+1 | |
> INCR aritcle:readcount:1001 | |
(integer) 1 | |
# 获取对应文章的阅读量 | |
> GET aritcle:readcount:1001 | |
"1" |
分布式锁
加锁
SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它来实现分布式锁:
-
如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
-
如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:
SET lock_key 1231234543534535 NX PX 10000 |
其中,
lock_key
:就是 key 键;1231234543534535
:是客户端生成的唯一的标识;NX
:代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;PX
:10000 表示设置 lock_key 的过期时间为 10s,避免客户端发生异常而无法释放锁。
释放锁
而解锁的过程就是将 lock_key 键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的值是否为加锁的客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。
if redis.call("get",KEYS[1]) == ARGV[1] then | |
return redis.call("del",KEYS[1]) | |
else | |
return 0 | |
end |
共享 Session 信息
在分布式场景下,在多个服务之间共享用户 Session 信息:
2.哈希类型hash
2.1概述
Redis中的Hash类型可以看成具String的键和String的值Map容器,每一个Hash可以存储40亿个键值对。Hash是一个健值对集合,是一个String类型的key与value的映射表,特别适合用于存储对象。Hash 是一个键值对(key - value)集合,其中 value 的形式如: value=[{field1,value1},...{fieldN,valueN}]。Hash 特别适合用于存储对象。
所以该类型非常适合于存储对象的信息。如一个用户有姓名,密码,年龄等信息,则可以有username、password等键它的存储结构如下:
Hash 与 String 对象的区别如下图所示:
2.2常用命令
生成hash key
//设置一个键值对,我这里设置两个的时候报错了,按理说不应该报错的,待观察
hset hash1 name liwenchao
type hash1
获取hash key字段值
//获取key为name的键值对的值
hget hash1 name
删除一个hash key的字段
hdel hash1 name
获取所有hash表中的key
hkeys hash1
获取指定hash的所有key,value
hgetall hash1
2.3 使用场景
存储、读取、修改用户属性
在 Memcached 中,我们经常将一些结构化的信息打包成 hashmap,在客户端序列化后存储为一个字符串的值(一般是 JSON 格式),比如用户的昵称、年龄、性别、积分等。这时候在需要修改其中某一项时,通常需要将字符串(JSON)取出来,然后进行反序列化,修改某一项的值,再序列化成字符串(JSON)存储回去。简单修改一个属性就干这么多事情,消耗必定是很大的,也不适用于一些可能并发操作的场合(比如两个并发的操作都需要修改积分)。而 Redis 的 Hash 结构可以使你像在数据库中 Update 一个属性一样只修改某一项属性值。
具体应用:
缓存对象
Hash 类型的 (key,field, value) 的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。在许多应用中,需要存储和管理用户的个人信息,使用Redis的哈希表结构可以方便地实现用户信息的存储和访问。例如,将每个用户的个人信息存储在一个哈希表中,使用用户ID作为键,个人信息的各个属性(如姓名、年龄、性别等)作为字段,对应的值作为属性的值。通过哈希表操作可以快速获取、更新用户的个人信息。
我们可以使用如下命令,将用户对象的信息存储到 Hash 类型:
# 存储一个哈希表uid:1的键值 | |
> HMSET uid:1 name Tom age 15 | |
2 | |
# 存储一个哈希表uid:2的键值 | |
> HMSET uid:2 name Jerry age 13 | |
2 | |
# 获取哈希表用户id为1中所有的键值 | |
> HGETALL uid:1 | |
1) "name" | |
2) "Tom" | |
3) "age" | |
4) "15" |
MIPSASM 复制 全屏
购物车
以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素
涉及的命令如下:
-
添加商品:
HSET cart:${用户id} ${商品id} 1
-
添加数量:
HINCRBY cart:${用户id} ${商品id} 1
-
商品总数:
HLEN cart:${用户id}
-
删除商品:
HDEL cart:${用户id} ${商品id}
-
获取购物车所有商品:
HGETALL cart:${用户id}
当前仅仅是将商品 ID 存储到了 Redis 中,在回显商品具体信息的时候,还需要拿着商品 id 查询一次数据库,获取完整的商品的信息。
3.列表类型list
3.1概述
在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其左部(left)和右部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表,如果这个键已经存在,则是向list添加元素。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是40亿个。
List 说白了就是链表(redis 使用双端链表实现的 List),相信学过数据结构知识的人都应该能理解其结构。使用 List 结构,我们可以轻松地实现最新消息排行等功能(比如新浪微博的 TimeLine )。List 的另一个应用就是消息队列,可以利用 List 的 *PUSH 操作,将任务存在 List 中,然后工作线程再用 POP 操作将任务取出进行执行。Redis 还提供了操作 List 中某一段元素的 API,你可以直接查询,删除 List 中某一段的元素
3.1常用命令
生成列表并插入数据
//创建list1的列表并插入value1,value2,value3,value3在列表的最左侧
lpush list1 value1 value2 value3
获取列表长度
llen list
向列表追加数据
//从左边追加
lpush list1 name1
//从右边追加
rpush list1 name2
获取列表指定范围数据
//name4在最左边,序列号为0 ,name3序列号为1
lrange list2 1 2
//如果只有4个元素。0-99可以打印所有的
lrange list2 0 99
移除左右一个元素
//从列表右边删除第一个
rpop list2
//从列表左边删除第一个
lpop list2
3.2应用场景
场景:消息队列
示例案例:异步任务处理
在许多应用中,需要处理大量的异步任务,使用消息队列可以有效地解耦任务的生产者和消费者。Redis的列表结构可以作为简单的消息队列来使用。例如,将任务的内容作为字符串添加到Redis列表的尾部,消费者从列表的头部获取任务进行处理,实现异步任务的分发和处理。
生产者-消费者模式
生产者可以使用LPUSH命令将消息从列表的左端(头部)插入到 Redis 列表中。消费者则使用BRPOP或RPOP命令从列表的右端(尾部)取出消息进行处理。
例如,在一个微服务架构中,一个服务可以将任务作为消息放入 Redis 列表,另一个服务作为消费者从列表中取出任务并执行。
这种方式实现的消息队列具有轻量级、易于部署和管理的优点。同时,Redis 的高性能可以确保消息的快速处理。
任务队列
可以将需要异步处理的任务放入 Redis 列表。例如,在一个电子商务网站中,当用户下单后,可以将订单处理任务放入列表。后台的工作进程从列表中取出任务,进行库存更新、订单确认等操作。
这样可以避免用户在下单时等待这些操作完成,提高用户体验。
场景:最新消息排行
示例案例:社交媒体动态更新
在社交媒体应用中,需要及时地向用户展示最新的消息或动态,使用Redis的列表结构可以方便地实现最新消息的排行。例如,将每条消息的内容作为字符串插入到Redis列表的头部,限制列表的长度为固定值,当超过指定长度时,自动删除最旧的消息,从而保持最新消息的更新。
场景:历史记录存储
用户操作历史
可以将用户的操作记录存储在 Redis 列表中。例如,在一个文档编辑系统中,可以将用户的每次编辑操作记录下来,以便用户可以随时查看历史版本或者进行撤销操作。
可以使用LPUSH命令将操作记录插入列表,使用LRANGE命令获取一定范围内的历史记录。
系统日志记录
对于系统的重要事件或错误日志,可以将其存储在 Redis 列表中。这样可以方便地进行日志的查询和分析。
例如,在一个 Web 应用中,可以将用户的登录日志、请求日志等存储在 Redis 列表中。当需要进行故障排查时,可以快速地获取相关的日志信息。
场景 排行榜
时间序列排行榜
可以将用户的行为按照时间顺序记录在 Redis 列表中。例如,在一个游戏中,可以将玩家的得分和时间戳作为一个元素放入列表。然后,通过对列表进行排序,可以得到不同时间段的玩家排行榜。
例如,“本周排行榜”可以通过只取本周内插入的元素进行排序得到。
动态排行榜
随着用户的行为不断变化,排行榜也需要实时更新。使用 Redis 列表可以快速地插入新的元素和删除旧的元素,从而实现动态排行榜。
例如,在一个直播平台中,观众可以通过送礼物来增加主播的人气值。人气值的变化可以实时反映在排行榜上。
4.无序集合类型set
4.1概述
在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。
Set可包含的最大元素数量是40亿,和List类型不同的是,Set集合中不允许出现重复的元素。
Set 就是一个集合,集合的概念就是一堆不重复值的组合。利用 Redis 提供的 Set 数据结构,可以存储一些集合性的数据。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
4.2常用命令
生成集合key
//生成无序集合set1
sadd set1 v2 v5
追加数据
//可以看到加入相同数据时,返回为0,表示没变化
sadd set1 v2
查看集合所有数据
//获取无序集合set1的所有数据
smembers set1
获取集合的差集,交集,并集
//差集,set1有而set2没有的
sdiff set1 set2
//交集,set1有,set2也有的
sinter set1 set2
//并集,set1和set2元素的集合并去重
sunion set1 set2
# 往集合 key 中存入元素,元素存在则忽略,若 key 不存在则新建
# SADD key member [member ...]
127.0.0.1:6379> SADD sid10t 唱 跳 RAP
(integer) 3
# 从集合 key 中删除元素
# SREM key member [member ...]
127.0.0.1:6379> SREM sid10t RAP
(integer) 1
# 获取集合 key 中所有元素
# SMEMBERS key
127.0.0.1:6379> SMEMBERS sid10t
1) "\xe8\xb7\xb3"
2) "\xe5\x94\xb1"
# 获取集合key中的元素个数
# SCARD key
127.0.0.1:6379> SCARD sid10t
(integer) 2
# 判断 member 元素是否存在于集合 key 中
# SISMEMBER key member
127.0.0.1:6379> SISMEMBER sid10t rap
(integer) 0
# 从集合 key 中随机选出 count 个元素,元素不从 key 中删除
# SRANDMEMBER key [count]
127.0.0.1:6379> SRANDMEMBER sid10t 1
1) "\xe8\xb7\xb3"
# 从集合 key 中随机选出 count 个元素,元素从 key 中删除
SPOP key [count]
127.0.0.1:6379> SPOP sid10t 2
1) "\xe5\x94\xb1"
2) "\xe8\xb7\xb3"
# 将一个指定的值,移动到另外一个 set 集合;
# SMOVE source destination member
127.0.0.1:6379> SMOVE sid10t ikun rap
(integer) 1
127.0.0.1:6379> SMEMBERS ikun
1) "rap"
4.3应用场景
1.共同好友、二度好友 2.利用唯一性,可以统计访问网站的所有独立 IP 3.好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐
文章标签管理
在文章管理系统中,经常需要为文章添加标签,方便用户进行分类和检索。使用Redis的集合结构可以实现高效的标签管理。例如,将每篇文章的标签存储在Redis的集合中,用户可以通过集合操作来查找具有特定标签的文章,还可以使用交集、并集等操作实现多标签的组合检索。
点赞
Set 类型可以保证一个用户只能点一个赞,这里举例子一个场景,key 是文章id,value 是用户id。
uid:1 、uid:2、uid:3 三个用户分别对 article:1 文章点赞了。
# uid:1 用户对文章 article:1 点赞 | |
> SADD article:1 uid:1 | |
(integer) 1 | |
# uid:2 用户对文章 article:1 点赞 | |
> SADD article:1 uid:2 | |
(integer) 1 | |
# uid:3 用户对文章 article:1 点赞 | |
> SADD article:1 uid:3 | |
(integer) 1 |
取消点赞:
> SREM article:1 uid:1 | |
(integer) 1 |
获取文章所有点赞用户:
> SMEMBERS article:1 | |
1) "uid:3" | |
2) "uid:2" |
获取文章的点赞用户数量:
> SCARD article:1 | |
(integer) 2 |
判断用户是否对文章点赞了:
> SISMEMBER article:1 uid:1 | |
(integer) 0 # 返回 0 说明没点赞,返回 1 则说明点赞了 |
共同关注好友
在社交网络应用中,好友关系的管理是核心功能之一。使用Redis的集合结构可以方便地实现好友关系的管理。例如,将每个用户的好友列表存储在Redis的集合中,使用集合操作可以快速判断两个用户是否是好友,还可以进行好友推荐等功能。
Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。
key 可以是用户 id,value 则是已关注的公众号的 id。
id:1 用户关注公众号 id 为 5、6、7、8、9,uid:2 用户关注公众号 id 为 7、8、9、10、11。
# uid:1 用户关注公众号 id 为 5、6、7、8、9 | |
> SADD uid:1 5 6 7 8 9 | |
(integer) 5 | |
# uid:2 用户关注公众号 id 为 7、8、9、10、11 | |
> SADD uid:2 7 8 9 10 11 | |
(integer) 5 |
uid:1 和 uid:2 共同关注的公众号:
# 获取共同关注 | |
> SINTER uid:1 uid:2 | |
1) "7" | |
2) "8" | |
3) "9" |
给 uid:2 推荐 uid:1 关注的公众号:
> SDIFF uid:1 uid:2 | |
1) "5" | |
2) "6" |
验证某个公众号是否同时被 uid:1 或 uid:2 关注:
> SISMEMBER uid:1 5 | |
(integer) 1 # 返回 0,说明关注了 | |
> SISMEMBER uid:2 5 | |
(integer) 0 # 返回 0,说明没关注 |
抽奖活动
存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。
key 为抽奖活动名,value 为员工名称,把所有员工名称放入抽奖箱 :
>SADD lucky Tom Jerry John Sean Marry Lindy Sary Mark | |
(integer) 5 |
如果允许重复中奖,可以使用 SRANDMEMBER
命令:
# 抽取 1 个一等奖: | |
> SRANDMEMBER lucky 1 | |
1) "Tom" | |
# 抽取 2 个二等奖: | |
> SRANDMEMBER lucky 2 | |
1) "Mark" | |
2) "Jerry" | |
# 抽取 3 个三等奖: | |
> SRANDMEMBER lucky 3 | |
1) "Sary" | |
2) "Tom" | |
3) "Jerry" |
MIPSASM 复制 全屏
如果不允许重复中奖,可以使用 SPOP
命令:
# 抽取一等奖1个 | |
> SPOP lucky 1 | |
1) "Sary" | |
# 抽取二等奖2个 | |
> SPOP lucky 2 | |
1) "Jerry" | |
2) "Mark" | |
# 抽取三等奖3个 | |
> SPOP lucky 3 | |
1) "John" | |
2) "Sean" | |
3) "Lindy" |
5.有序集合zset
5.1概述
Redis 有序集合和集合一样也是无序不可以重复。
不同的是每个元素都会关联一个分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复,每个集合可存储40多亿个成员。
Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成:元素值 和 排序值。
和Sets相比,Sorted Sets是将 Set 中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,比如一个存储全班同学成绩的 Sorted Sets,其集合 value 可以是同学的学号,而 score 就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用 Sorted Sets 来做带权重的队列,比如普通消息的 score 为1,重要消息的 score 为2,然后工作线程可以选择按 score 的倒序来获取工作任务。让重要的任务优先执行。
有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。
5.2常用命令
生成有序集合
//生成有序集合zset1
zadd zset1 1 v1
//获取类型
type zset1
//一次添加多个元素
zadd zset2 1 v1 2 v2 3 v3
集合排行
//显示所有key,value
zrevrange zset2 0 -1 withscores
//显示集合zset2内所有的key
zrange zset2 0 -1
获取集合长度
zcard zset1
基于索引返回数值
//id号从1到3的
zrange zset1 1 3
返回某个数值的索引
zrank zset2 v2
5.3应用场景
排行榜
有序集合比较典型的使用场景就是排行榜。音乐排行榜
在音乐播放应用中,常常需要展示热门歌曲的排行榜,使用Redis的有序集合结构可以方便地实现排行榜功能。例如,将每首歌曲的名称作为字符串元素,播放次数作为分数存储在有序集合中,可以通过有序集合操作按照播放次数进行排序,快速获取热门歌曲的排行。
例如,学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。
我们以博客点赞排名为例,wjm有五篇博文,分别获得赞为:200、40、100、50、150。
# arcticle:1 文章获得了200个赞 | |
> ZADD user:wjm:ranking 200 arcticle:1 | |
(integer) 1 | |
# arcticle:2 文章获得了40个赞 | |
> ZADD user:wjm:ranking 40 arcticle:2 | |
(integer) 1 | |
# arcticle:3 文章获得了100个赞 | |
> ZADD user:wjm:ranking 100 arcticle:3 | |
(integer) 1 | |
# arcticle:4 文章获得了50个赞 | |
> ZADD user:wjm:ranking 50 arcticle:4 | |
(integer) 1 | |
# arcticle:5 文章获得了150个赞 | |
> ZADD user:wjm:ranking 150 arcticle:5 | |
(integer) 1 |
文章 arcticle:4 新增一个赞,可以使用 ZINCRBY
命令:
> ZINCRBY user:wjm:ranking 1 arcticle:4 | |
"51" |
查看某篇文章的赞数,可以使用 ZSCORE
命令:
> ZSCORE user:wjm:ranking arcticle:4 | |
"50" |
获取文章赞数最多的 3 篇文章,可以使用 ZREVRANGE
命令:
# WITHSCORES 表示把 score 也显示出来 | |
> ZREVRANGE user:wjm:ranking 0 2 WITHSCORES | |
1) "arcticle:1" | |
2) "200" | |
3) "arcticle:5" | |
4) "150" | |
5) "arcticle:3" | |
6) "100" |
获取wjm100 赞到 200 赞的文章,可以使用 ZRANGEBYSCORE
命令:
> ZRANGEBYSCORE user:wjm:ranking 100 200 WITHSCORES | |
1) "arcticle:3" | |
2) "100" | |
3) "arcticle:5" | |
4) "150" | |
5) "arcticle:1" | |
6) "200" |
游戏积分排名
在游戏应用中,常常需要记录玩家的积分并进行排名,使用Redis的有序集合结构可以方便地实现计分系统。例如,将每个玩家的ID作为字符串元素,积分作为分数存储在有序集合中,通过有序集合操作可以按照积分进行排序,快速获取玩家的排名和积分。
2.1 成绩榜单
2.1.1 成绩表单添加数据
ZADD命令:添加or更新成员分数
//命令参数
ZADD key score member [[score member] [score member] ...]
将一个或多个member元素及其score值加入到有序集key当中。如果某个member已经是有序集的成员,那么更新这个member的score值,并通过重新插入这个member元素,来保证该member在正确的位置上。
score值可以是整数值或双精度浮点数。如果key不存在,则创建一个空的有序集并执行ZADD操作。当key存在但不是有序集类型时,返回一个错误。
//假设用户A(user1)当前游戏的分数为50,则
ZADD user_rank 50 user1
//添加用户B(user2)当前游戏的分数为60、用户C(user3)当前游戏的分数为70,则可批量操作,同时添加user2、user3 两个用户的分数
ZADD user_rank 60 user2 70 user3
2.1.2 ZREVRANK 获取成员当前的排名
//命令参数
ZREVRANK key member
返回有序集key中成员member的排名,其中有序集成员按score值递减(从大到小)排序。排名以0为底,也就是说,score 值最大的成员排名为0 。
//获取用户A当前的排名,user1 当前排名为第三,则输出2
ZREVRANK user_rank user1
2.2.3 ZSCORE 获取用户分数(排序值)
//命令参数
ZSCORE key member
返回有序集key中,成员member的score值。如果member元素不是有序集key的成员或key不存在返回nil。
// 获取用户A当前的分数,user1当前分数为50,则输出"50",注意返回值是字符串
ZSCORE user_rank user1
2.2 游戏排行榜
一个典型的游戏排行榜包括以下常见功能:
能够记录每个玩家的分数;
能够对玩家的分数进行更新;
能够查询每个玩家的分数和名次;
能够按名次查询排名前N名的玩家;
能够查询排在指定玩家前后M名的玩家。
更进一步,上面的操作都需要在短时间内实时完成,这样才能最大程度发挥排行榜的效用。由于一个玩家名次上升x位将会引起x+1位玩家的名次发生变化(包括该玩家),如果采用传统数据库(比如MySQL)来实现排行榜,当玩家人数较多时,将会导致对数据库的频繁修改,性能得不到满足,所以不合适。
2.2.1 zadd设置玩家分数
下面设置了4个玩家的分数,如果玩家分数已经存在,则会覆盖之前的分数。
> redis 127.0.0.1:6379> zadd lb 89 user1
> (integer) 1
> redis 127.0.0.1:6379> zadd lb 95 user2
> (integer) 1
> redis 127.0.0.1:6379> zadd lb 95 user3
> (integer) 1
> redis 127.0.0.1:6379> zadd lb 90 user4
> (integer) 1
2.2.2 zscore查看玩家分数
下面是查看user2这个玩家在lb排行榜中的分数。
redis 127.0.0.1:6379> zscore lb user2 “95”
2.2.3 zrevrange按名次查看排行榜
由于排行榜一般是按照分数由高到低排序的,所以我们使用zrevrange,而命令zrange是按照分数由低到高排序。起始位置和结束位置都是以0开始的索引,且都包含在内。如果结束位置为-1则查看范围为整个排行榜。带上withscores则会返回玩家分数。下面为查看所有玩家分数:
> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores
> 1) “user3”
> 2) “95”
> 3) “user2”
> 4) “95”
> 5) “user4”
> 6) “90”
> 7) “user1”
> 8) “89”
下面为查询前三名玩家分数:
> redis 127.0.0.1:6379> zrevrange lb 0 2 withscores
> 1) “user3”
> 2) “95”
> 3) “user2”
> 4) “95”
> 5) “user4”
> 6) “90”
2.2.4 zrevrank查看玩家的排名
与zrevrange类似,zrevrank是以分数由高到低的排序返回玩家排名(实际返回的是以0开始的索引),对应的zrank则是以分数由低到高的排序返回排名。下面是查询玩家user3和user4的排名:
> redis 127.0.0.1:6379> zrevrank lb user3
> (integer) 0
> redis 127.0.0.1:6379> zrevrank lb user1
> (integer) 3
2.2.5 zincrby增减玩家分数
有的排行榜是在变更时重新设置玩家的分数,而还有的排行榜则是以增量方式修改玩家分数,增量可正可负。如果执行zincrby时玩家尚不在排行榜中,则认为其原始分数为0,相当于执行zdd。下面将user4的分数增加6,使其名次上升到第一位。
> redis 127.0.0.1:6379> zincrby lb 6 user4
> “96”
> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores
> 1) “user4”
> 2) “96”
> 3) “user3”
> 4) “95”
> 5) “user2”
> 6) “95”
> 7) “user1”
> 8) “89”
2.2.6 zrem移除某个玩家
下面移除玩家user4
> redis 127.0.0.1:6379> zrem lb user4
> (integer) 1
> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores
> 1) “user3”
> 2) “95”
> 3) “user2”
> 4) “95”
> 5) “user1”
> 6) “89”
2.2.7 del删除排行榜
排行榜对象在我们首次调用zadd或zincrby时被创建,当我们要删除它时,调用redis通用的命令del即可。
> redis 127.0.0.1:6379> del lb
> (integer) 1
> redis 127.0.0.1:6379> get lb
> (nil)
6.Bitmap:
6.1概述
Bitmap是Redis中的一种特殊数据结构,用于存储位图索引,支持高效的位操作。
Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。
由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。
6.2常用命令
1)添加:SETBIT命令
功能:用来设置或者清除某一位上的值,其返回值是原来位上存储的值。key 所有的位的初始状态下都为0
,可以自动扩容,最大为40多个亿。
语法格式:SETBIT key offset value
offset表示偏移量(相当于数组的下标),也是从0开始
。
返回值: Integer(整型)返回值为set之前的值(0或1)。
示例如下:
127.0.0.1:6379> setbit t1 0 1
(integer) 0 # 返回为set之前的值
127.0.0.1:6379> getbit t1 1 # 初始状态下默认为0
(integer) 0
127.0.0.1:6379> setbit t1 1 1
(integer) 0
127.0.0.1:6379> getbit t1 0
(integer) 1
127.0.0.1:6379> getbit t1 1
(integer) 1
127.0.0.1:6379> getbit t1 2
(integer) 0
2)查询:GETBIT命令
功能:用来获取某一位上的值。
语法格式:GEIBIT KEY OFFSET
当偏移量 offset 比字符串的长度大,或者当 key 不存在时,返回默认值0。
返回值: Integer(整型)某一位上的值0或1。
示例如下:
127.0.0.1:6379> setbit t1 0 1
(integer) 0
127.0.0.1:6379> getbit t1 0
(integer) 1
127.0.0.1:6379> getbit t1 1
(integer) 0
127.0.0.1:6379> getbit t2 0
(integer) 0
127.0.0.1:6379>
3)统计:BITCOUNT命令
功能:统计key的位上为1的数量,通过指定额外的 start 或 end 参数,可以让计数只在特定的位置上进行。
语法格式:BITCOUNT key [start end [BYTE|BIT]]
start 和 end 设置统计的范围,单位默认设置BYTE。可以使用负数值:比如-1表示最后一个位,而-2表示倒数第二个位。start和end的值既可以是正数也可以是负数,正数表示从正序,反之为倒序。
BYTE|BIT 参数设置start和end参数的单位。默认为BYTE,对应8个偏移量。(v7.0版后开始增加)
如上 t1 【01000000 10000000 00000000 00000000】,对应start和end的值为【0,1,2,3】
返回值: Integer(整型)统计为1的数量。
示例如下:
127.0.0.1:6379> bitcount t1 # 获取整个key上为1的个数。
(integer) 2
127.0.0.1:6379> bitcount t1 0 0 # 获取0字节(转发为偏移量为0-7)长度的位置上为1的个数。
(integer) 1
127.0.0.1:6379> bitcount t1 1 1 # 获取1字节(转发为偏移量为8-15)长度的位置上为1的个数。
(integer) 1
127.0.0.1:6379> bitcount t1 0 1 # 获取0-1字节(转发为偏移量为0-15)长度的位置上为1的个数。
(integer) 2
127.0.0.1:6379> bitcount t1 0 2 BIT # 设置单位为BIT,获取0-2字(偏移量为0-2)长度的位置上为1的个数。
(integer) 1
127.0.0.1:6379> bitcount t1 0 16 BIT # 设置单位为BIT,获取0-2字(偏移量为0-2)长度的位置上为1的个数。
(integer) 2
4)位置查询:BITPOS命令
功能:返回bitmap中第一个被设置为0或者1的位的位置。
语法格式:BITPOS key bit [start [end [BYTE | BIT]]]
bit 参数为0或1
start 和end 参数默认情况下,范围被解释为一个字节的范围,而不是一个比特的范围,所以start=0和end=2意味着查看前三个字节;可以使用可选的BIT修饰符来指定应将范围解释为位范围,当使用BIT选项时,start=0和end=2表示看前三位。 不存在的key将被视为空字符串;start和end的值既可以是正数也可以是负数,正数表示从正序,反之为倒序。
返回值: Integer(整型),返回指定范围内第一个0或者1所在的位或者字节数。-1表示未查找到。
示例如下:
127.0.0.1:6379> bitpos t1 1 # 查询t1出现1的第一个位置
(integer) 0
127.0.0.1:6379> bitpos t1 1 1 # 查询1字节开始后出现1的第一个位置
(integer) 8
127.0.0.1:6379> bitpos t1 1 2 3 # 查询1字节到2字节出现1的第一个位置
(integer) -1
127.0.0.1:6379> bitpos t1 1 2 3 BIT # 查询2字到3字出现1的第一个位置
(integer) -1
127.0.0.1:6379> bitpos t1 1 1 3 BIT
(integer) -1
127.0.0.1:6379> bitpos t1 1 0 3 BIT
(integer) 0
5)位操作:BITOP命令
功能:在多个key之间执行逐位地进行位操作,并且将结果存储进指定key中。
语法格式:BITOP operation destkey key [key ...]
operation 参数可选项:AND
(逻辑与)、OR
(逻辑或)、XOR
(逻辑异或)、NOT
(逻辑非)(其中NOT操作只接受一个key)。
当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作0,空的 key 也被看作是包含0的字符串序列。
返回值: Integer(整型),返回存储在目标键中的字符串长度,该字符串长度等于输入键中对应的字符串中长度最长的值。
示例如下:
127.0.0.1:6379> bitop and t3 t1 t2
(integer) 2
127.0.0.1:6379> get t3
"\x00\x00"
上面的运算为 t1 【01000000 10000000】,t2 【01000100 00000000】逻辑与运算(全为1则为1,否则为0)结果为:【01000000 00000000】
6.3 使用场景
- 用户在线状态跟踪:用一个位表示一个用户的在线状态(1 表示在线,0 表示离线),用户ID作为偏移量。
- 用户签到系统:记录用户每天的签到情况,每位对应一天,偏移量对应日期。
- 访问统计:记录网站页面、广告点击等的访问次数,每个二进制位代表一次访问。
- 数据去重:利用位图快速判断某个整数值是否已存在于集合中,避免重复记录。
- 大范围计数:如统计某段时间内活跃用户数、订单数等,通过位图进行高效计数。
签到统计
在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。
签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。
假设我们要统计 ID 100 的用户在 2022 年 6 月份的签到情况,就可以按照下面的步骤进行操作:
-
第一步,执行下面的命令,记录该用户 6 月 3 号已签到
SETBIT uid:sign:100:202206 2 1
-
第二步,检查该用户 6 月 3 日是否签到。
GETBIT uid:sign:100:202206 2
-
第三步,统计该用户在 6 月份的签到次数。
BITCOUNT uid:sign:100:202206
这样,我们就知道该用户在 6 月份的签到情况了。
统计这个月首次打卡时间
Redis 提供了 BITPOS key bitValue [start] [end]
指令,返回数据表示 Bitmap 中第一个值为 bitValue 的 offset 位置。
在默认情况下, 命令将检测整个位图, 用户可以通过可选的 start 参数和 end 参数指定要检测的范围。
因此,我们可以通过 BITPOS 命令来获取 userID = 100 在 2022 年 6 月份首次打卡日期:
BITPOS uid:sign:100:202206 1 |
需要注意的是,因为 offset 从 0 开始的,所以我们需要将返回的 value + 1 。
判断用户登陆态
Bitmap 提供了 GETBIT、SETBIT 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。
只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 GETBIT判断对应的用户是否在线。 5000 万用户只需要 6 MB 的空间。
假如我们要判断 ID = 10086 的用户的登陆情况:
-
第一步,执行以下指令,表示用户已登录
SETBIT login_status 10086 1
-
第二步,检查该用户是否登陆,返回值 1 表示已登录
GETBIT login_status 10086
-
第三步,登出,将 offset 对应的 value 设置成 0
SETBIT login_status 10086 0
位图索引
示例案例:在线用户统计
在许多应用中,需要统计在线用户的数量,使用Redis的Bitmap结构可以方便地实现在线用户的统计。例如,使用Bitmap结构,可以为每个用户分配一个位,并将位设置为1表示用户在线,0表示用户离线。通过位操作可以快速计算在线用户的数量,还可以进行更复杂的位运算,如计算两组用户的交集、并集等。