List 列表
列表类型是用来存储多个有序的字符串,
如图:
a、b、c、d、e 五个元素从左到右组成 了⼀个有序的列表,列表中的每个字符串称为元素(element),⼀个列表最多可以存储个元素。在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、 获取指定索引下标的元素等列表是⼀种⽐较灵活的数据结构,它可以充当栈和队列的⻆⾊,在实际开发上有很多应⽤场景
Redis列表(Lists)类型的特点如下:
-
有序性:Redis列表是按照插入顺序来保存元素的。
-
可重复性:Redis列表中的元素可以重复。
-
长度可变性:Redis列表可以动态增长和缩小,可以随时添加或删除元素。
-
支持左右两端插入和删除操作:Redis列表提供了从左端或右端插入和删除元素的操作,可以方便地实现队列和栈。
-
支持范围操作:Redis列表提供了类似于数组的下标范围操作,可以获取指定范围内的元素。
-
适合存储有序数据:Redis列表适合存储有序数据,如消息队列、最新消息列表、日志记录等。
总之,Redis列表是一种非常灵活的数据结构,可以用于各种不同的场景,具有很高的实用价值。
Redis基本命令:
- LPUSH key value1 [value2 ...] - 在列表左边插入一个或多个元素
- RPUSH key value1 [value2 ...] - 在列表右边插入一个或多个元素
- LPOP key - 移除并返回列表最左边的元素
- RPOP key - 移除并返回列表最右边的元素
- LINDEX key index - 返回列表中指定索引位置的元素
- LLEN key - 返回列表的长度
- LRANGE key start stop - 返回列表中指定范围内的元素
- LREM key count value - 移除列表中指定数量的元素
- LTRIM key start stop - 保留列表中指定范围内的元素,其它元素均被删除
- BLPOP key [key ...] timeout - 阻塞并等待列表最左边的元素,并在超时时间内返回结果
- BRPOP key [key ...] timeout - 阻塞并等待列表最右边的元素,并在超时时间内返回结果
从这里我们可以发现,每个基本类型中基本命令开头必带有类型的首字母,但是在 List 中发现首字母不仅只有 L 还有 R;
原因在于: list 是一个双向链表,既可以头插头删,也可以尾插尾删;
L 代表 Left R 代表 Right
不只是 L 和 R ;这里居然还有 B 开头的。
这是我自己记的笔记:百度网盘 ,可以查看命令比较具体的细节
BLPOP:
B 代表 Block 阻塞,L 表示 Left ,这个命令就意味着:阻塞并等待列表最左边的元素,并在超时时间内返回结果
返回值为:取出的元素或者 nil。
内部编码
列表类型的内部编码有两种:
- ziplist(压缩列表):当列表的元素个数⼩于 list-max-ziplist-entries 配置(默认 512 个),同时列表中每个元素的⻓度都⼩于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选⽤ziplist 来作为列表的内部编码实现来减少内存消耗
- linkedlist(链表):当列表类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内部实现。
当元素个数较少并没有大元素时,内部编码为 ziplist:
1 127.0.0.1:6379> rpush listkey e1 e2 e3
2 OK
3 127.0.0.1:6379> object encoding listkey
4 "ziplist"
1 127.0.0.1:6379> rpush listkey e1 e2 e3 ... 省略 e512 e513
2 OK
3 127.0.0.1:6379> object encoding listkey
4 "linkedlist"
1 127.0.0.1:6379> rpush listkey "one string is bigger than 64 bytes ... 省略 ..."
2 OK
3 127.0.0.1:6379> object encoding listkey
4 "linkedlist"
这个所谓超出某个数字并非唯一的,同样可以在 .conf 配置文件中修改的。
使用场景
消息队列
Redis 可以使用 lpush + brpop 组合命令实现生产者-消费者模型队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地从队列中 “争抢” 队首元素。通过多个客户端来保证消费的负载均衡和高可用。
微博 Timeline
hmset mblog: 1 title xx timestamp 1476536196 content xxxxx...hmset mblog:n title xx timestamp 1476536196 content xxxxx
lpush user: 1 :mblogs mblog: 1 mblog: 3...lpush user:k:mblogs mblog: 9
keylist = lrange user: 1 :mblogs 0 9for key in keylist {hgetall key}
- 1 + n 问题。即如果每次分⻚获取的微博个数较多,需要执⾏多次 hgetall 操作,此时可以考虑使⽤pipeline(流⽔线)模式批量提交命令,或者微博不采⽤哈希类型,⽽是使⽤序列化的字符串类型,使⽤ mget 获取。
- 分裂获取⽂章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。
注意:
同侧存取(lpush + lpop 或者 rpush + rpop)为栈异侧存取(lpush + rpop 或者 rpush + lpop)为队列
Set 类型
如图:
⼀个集合中最多可以存储 2^32 - 1 个元素。Redis 除了⽀持 集合内的增删查改操作,同时还⽀持多个集合取交集、并集、差集,合理地使⽤好集合类型,能在实 际开发中解决很多问题。
基本命令
- SADD key member1 [member2]:向集合中添加一个或多个元素
- SCARD key:获取集合中元素数量
- SDIFF key1 [key2]:返回第一个集合与其它集合的差集
- SDIFFSTORE destination key1 [key2]:将第一个集合与其它集合的差集存储到一个新的集合中
- SINTER key1 [key2]:返回第一个集合与其它集合的交集
- SINTERSTORE destination key1 [key2]:将第一个集合与其它集合的交集存储到一个新的集合中
- SISMEMBER key member:判断元素是否在集合中
- SMEMBERS key:获取集合中所有元素
- SMOVE source destination member:将一个元素从一个集合中移动到另一个集合中
- SPOP key [count]:随机移除并返回集合中一个或多个元素
- SRANDMEMBER key [count]:随机获取集合中一个或多个元素
- SREM key member1 [member2]:从集合中移除一个或多个元素
- SUNION key1 [key2]:返回多个集合的并集
- SUNIONSTORE destination key1 [key2]:将多个集合的并集存储到一个新的集合中
这是我自己记的笔记:百度网盘 ,可以查看命令比较具体的细节
内部编码
集合类型的内部编码同样也分两种:
- intset(整数集合):当集合中的元素都是整数并且元素的个数⼩于 set-max-intset-entries 配置 (默认 512 个)时,Redis 会选⽤ intset 来作为集合的内部实现,从⽽减少内存的使⽤。
- hashtable(哈希表):当集合类型⽆法满⾜ intset 的条件时,Redis 会使⽤ hashtable 作为集合的内部实现。
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 513
127.0.0.1:6379> object encoding setkey
"hashtable"
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"
同样的,这里所谓超出某个数字并非唯一的,同样可以在 .conf 配置文件中修改的。
使用场景
集合类型⽐较典型的使⽤场景是标签(tag)。例如 A ⽤⼾对娱乐、体育板块⽐较感兴趣,B ⽤⼾ 对历史、新闻⽐较感兴趣,这些兴趣点可以被抽象为标签。有了这些数据就可以得到喜欢同⼀个标签的⼈,以及⽤⼾的共同喜好的标签,这些数据对于增强⽤⼾体验和⽤⼾黏度都⾮常有帮助。 例如⼀个电⼦商务⽹站会对不同标签的⽤⼾做不同的产品推荐。
ZSet 类型
基本命令
Redis中ZSet类型是有序集合,每个元素都有一个对应的分数(score)。以下是ZSet类型的基本命令:
- ZADD key score member:向有序集合添加一个或多个成员,或者更新已存在成员的分数
- ZCARD key:获取有序集合的元素数量
- ZCOUNT key min max:获取有序集合中分数范围内的成员数量
- ZINCRBY key increment member:对有序集合中指定成员的分数增加increment
- ZRANK key member:获取有序集合中指定成员的排名(从小到大)
- ZREVRANK key member:获取有序集合中指定成员的排名(从大到小)
- ZSCORE key member:获取有序集合中指定成员的分数
- ZRANGE key start stop [WITHSCORES]:获取有序集合中指定排名范围内的成员(从小到大),如果WITHSCORES选项被指定,则同时返回成员的分数
- ZREVRANGE key start stop [WITHSCORES]:获取有序集合中指定排名范围内的成员(从大到小)
- ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]:获取有序集合中指定分数范围内的成员,如果WITHSCORES选项被指定,则同时返回成员的分数,如果LIMIT选项被指定,则返回指定数量的成员(从小到大)
- ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]:获取有序集合中指定分数范围内的成员,如果WITHSCORES选项被指定,则同时返回成员的分数,如果LIMIT选项被指定,则返回指定数量的成员(从大到小)
- ZREM key member:从有序集合中移除一个或多个成员
- ZREMRENGEByRANK key start stop:移除有序集合中指定排名范围内的成员(从小到大)
- ZREMRENGEBySCORE key min max:移除有序集合中指定分数范围内的成员
- ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]:计算给定numkeys个有序集合的并集,并将结果保存到destination,如果WEIGHTS选项被指定,则计算时使用指定权重,如果AGGREGATE选项被指定,则使用指定聚合方式
这是我自己记的笔记:百度网盘 ,可以查看命令比较具体的细节
内部编码
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会⽤ ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
- skiplist(跳表):当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时ziplist 的操作效率会下降。
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 ... 省略 ... 82 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
127.0.0.1:6379> zadd zsetkey 50 "one string bigger than 64 bytes ... 省略 ..."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"
使用场景
例如⽤⼾ james 发布了⼀篇⽂章,并获得 3 个赞,可以使⽤有序集合的 zadd 和 zincrby 功能:zadd user:ranking: 2022 - 03 - 15 3 james之后如果再获得赞,可以使⽤ zincrby:zincrby user:ranking: 2022 - 03 - 15 1 james
由于各种原因(例如⽤⼾注销、⽤⼾作弊等)需要将⽤⼾删除,此时需要将⽤⼾从榜单中删除掉,可以使⽤ zrem。例如删除成员 tom:zrem user:ranking: 2022 - 03 - 15 tom
此功能使⽤ zrevrange 命令实现:zrevrangebyrank user:ranking: 2022 - 03 - 15 0 9
渐进式遍历
我来演示一下就明白我再说啥了。
我们现在一共存在了 11 个 key,那么现在我要使用 scan 渐进式遍历。
渐进性遍历 scan 虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增加、修改、删除),可能导致遍历时键的重复遍历或者遗漏,这点务必在实际开发中考虑。
本章节到此就结束了,具体的命令还是建议去看官网,当然也可以和 scan 一样有更优的解,不听从我的建议。