Redis 常用数据类型 (下)
文章目录
- 前言
- 一 Hash 哈希
- 1. Hash 相关命令
- hset 和 hget
- hexists
- hdel
- hkeys
- hvals
- hgetall
- hmget
- hlen
- hsetnx
- hincrby
- incrbyfloat
- 2. Hash 命令小结
- 3. Hash 内部编码
- Hash 在缓存中的应用
- 场景介绍
- 缓存方式对比
- 二、List 列表
- 1. LIST总体介绍
- 2. List 普通命令
- lpush
- lpushx
- rpush
- rpushx
- lrange
- lpop
- rpop
- linsert
- lindex
- llen
- lrem
- ltrim
- lset
- 3. List 阻塞版本命令
- blpop
- brpop
- 4. List 命令小结
- 5. List 内部编码
- List 使用场景
- 1. 消息队列
- 2. 微博timeline
- 三、Set 集合
- 1.Set 总体介绍
- 2. Set 普通命令
- sadd、 smembers、 sismember
- scard
- spop
- srandmember
- smove
- srem
- 3. 集合间操作
- sinter
- sinterstore
- sunion
- sunionstore
- sdiff
- sdiffstore
- 4. Set 命令小结
- 5. Set 内部编码
- 6.应用场景
- 四、Zset 有序集合
- 1. Zset 总体认识
- 2. Zset 普通命令
- zadd
- zcard
- zcount
- zrange
- zrevrange
- zrangebyscore
- zpopmax 和 zpopmin
- bzpopmax 和 bzpopmin
- zrank 和 zrevrank
- zscore
- zrem
- zremrangebyrank
- zremrangebyscore
- zincrby
- Zset 集合间操作命令
- zinterstore
- zunionstore
- 4. 命令小结
- 5. 内部编码
- 6. 应用场景---排行榜系统
- 五、其他类型
- 六、 补充
- 渐进式遍历
- 数据库操作
- select
- dbsize
- flushall 和 flushdb
前言
上篇中介绍到了Redis 的 String 类型,本文是下篇,将继续介绍 Redis 常用数据类型中的 哈希(Hash)** 、列表(List) 、集合(Set) 、有序集合(Sorted Set) 和一些其他类型。
Redis常用数据类型(上)
一 Hash 哈希
Redis 本身就是一种 Hash 结构,同时也提供了 Hash 这种数据类型。
下图阐述了二者的一些差别,同时也体现了 Hash 和 String 在使用时的差别
Redis 本身的键值对,官方称之为 key-value,为了区分,对于 Redis 的 Hash 数据类型,其键值对称为 field-value 。在这种嵌套结构中我们需要注意 value 的具体含义
1. Hash 相关命令
hset 和 hget
这是一对最核心的命令,分别用于设置键值对和获取 field 对应的 value
# 支持一次设置多组值
HSET key field value [field value ...]# 注意这里先指定 key 再指定 field
HGET key field
返回值:hset 返回添加的对数,hget 返回指定 filed 对应的值
示例
127.0.0.1:6379> hset key f1 111
(integer) 1
127.0.0.1:6379> hset key f2 222 f3 333 f4 444
(integer) 3
127.0.0.1:6379> hget key f1
"111"
127.0.0.1:6379> hget key f4
"444"
hexists
判断是否存在对应字段
hexists key filed
存在则返回 1,不存在返回 0
语法:
# 接着上一个例子,已经存在 f1, f2, f3, f4
127.0.0.1:6379> HEXISTS key f1
(integer) 1
127.0.0.1:6379> HEXISTS key f2
(integer) 1
127.0.0.1:6379> HEXISTS key f100
(integer) 0
hdel
删除 hash 中指定的字段,支持删除多个
返回删除成功的个数
# 接着上一个例子,已经存在 f1, f2, f3, f4
127.0.0.1:6379> hdel key f1 f100
(integer) 1
127.0.0.1:6379> hget key f1
(nil)
hkeys
获取指定 key 对应哈希表中 所有 field
hkey key
示例:
127.0.0.1:6379> hkeys key
1) "f2"
2) "f3"
3) "f4"
hkeys 有风险!当 key 对应的hash表数据过多,就会阻塞 redis 服务器
hvals
获取指定 key 对应 hash 表中 所有 value
hvals key
示例:
127.0.0.1:6379> hvals key
1) "222"
2) "333"
3) "444"
hgetall
获取指定 key 对应 hash 表中 所有 field-value 对
hgetall key
示例:
127.0.0.1:6379> hgetall key
1) "f2"
2) "222"
3) "f3"
4) "333"
5) "f4"
6) "444"
hmget
一次获取多个 field 对应的值
返回值:返回对应字段的值或者nill
hmget key field [field ...]
示例:
127.0.0.1:6379> hmget key f1 f2 f3 f4
1) (nil)
2) "222"
3) "333"
4) "444"
在使⽤ HGETALL 时,如果哈希元素个数⽐较多,会存在阻塞 Redis 的可能。如果开发⼈员只需要获取部分 field,可以使⽤ HMGET,如果⼀定要获取全部 field,可以尝试使⽤ HSCAN命令,该命令采⽤渐进式遍历哈希类型,HSCAN 会在后续章节介绍。
hlen
获取 hash 中所有字段的个数
hlen key
示例:
127.0.0.1:6379> hlen key
(integer) 3
hsetnx
若 field 存在 设置键值对,否则 不设置
语法:
hsetnx key field value
返回 1 表示设置成功,返回 0 表示失败
示例:
127.0.0.1:6379> HSETNX myhash field "Hello"
(integer) 1
127.0.0.1:6379> HSETNX myhash field "Hello"
(integer) 0
hincrby
Hash 中的 value 也可以当成整数
HINCRBY key field increment
对 Hash 中 field 对应的 value 值 加一
返回值:变化后的值,不是整数则报错
示例:
redis> HSET myhash field 5
(integer) 1
redis> HINCRBY myhash field 1
(integer) 6
redis> HINCRBY myhash field -1
(integer) 5
redis> HINCRBY myhash field -10
(integer) -5
incrbyfloat
HINCRBY 的浮点数版本。
语法:
HINCRBYFLOAT key field increment
示例:
redis> HSET mykey field 10.50
(integer) 1
redis> HINCRBYFLOAT mykey field 0.1
"10.6"
redis> HINCRBYFLOAT mykey field -5
"5.6"
redis> HSET mykey field 5.0e3
(integer) 0
redis> HINCRBYFLOAT mykey field 2.0e2
"5200"
2. Hash 命令小结
命令 | 执行效果 | 时间复杂度 |
---|---|---|
hset key field value | 设置值 | O(1) |
hget key field | 获取值 | O(1) |
hdel key field [field …] | 删除 field | O(k), k 是 field 个数 |
hlen key | 计算 field 个数 | O(1) |
hgetall key | 获取所有的 field - value | O(k), k 是 field 个数 |
hmget field [field …] | 批量获取 field - value | |
O(k), k 是 field 个数 | ||
hmset field value [field value …] | 批量获取 field - value | O(k), k 是 field 个数 |
hexists key field | 判断 field 是否存在 | O(1) |
hkeys key | 获取所有的 field | O(k), k 是 field 个数 |
hvals key | 获取所有的 value | O(k), k 是 field 个数 |
hsetnx key field value | 设置值,但必须在 field 不存在时才能设置成功 | O(1) |
hincrby key field n | 对应 field - value +n | O(1) |
hincrbyfloat key field n | 对应 field - value +n | O(1) |
hstrlen key field | 计算 value 的字符串长度 | O(1) |
3. Hash 内部编码
哈希的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数⼩于 hash-max-ziplist-entries 配置(默认 512 个)、同时所有值都⼩于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使⽤ ziplist 作为哈希的内部实现,ziplist 使⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐hashtable 更加优秀。
- hashtable(哈希表):当哈希类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,⽽ hashtable 的读写时间复杂度为 O(1)。
ziplist 的代价就是访问速度较慢,所以仅在元素个数少且 value 较短时使用
示例:
(1)元素个数较少时,内部编码采用 ziplist
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
(2)当有 value 大于 64 字节时,内部编码会转换成 hashtable:
127.0.0.1:6379> hset hashkey f3 "超过64字节 .. 省略"
OK
127.0.0.1:6379> object encoding hashkey
"hashtable
(3)当 field 个数超过 512 时,内部编码也会转换为 hashtable:
127.0.0.1:6379> hmset hashkey f1 v1 h2 v2 f3 v3 ... 省略 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
Hash 在缓存中的应用
场景介绍
前面提到 String 也可用作缓存场景中,但是在存储结构化数据上,使用 Hash 更优优势
使用关系型数据库存储:
使用redis 存储
- 哈希类型是稀疏的,⽽关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,⽽关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即便没有,也会默认设置nil,如下图所示
- 关系数据库可以做复杂的关系查询,⽽ Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本⾼。
缓存方式对比
缓存方式 | 实现方式示例 | 核心优势 | 主要不足 | 适用场景 |
---|---|---|---|---|
原生字符串类型 | 每个属性单独设键(如set user:1:name James ) | 实现极简,单个属性操作灵活 | 键数量过多,内存占用大,数据分散无内聚性 | 几乎不推荐,仅临时调试场景可能使用 |
序列化字符串类型(如JSON) | 整体序列化存储(如set user:1 '{"name":"James"}' ) | 数据内聚性好,整体操作高效,内存利用率高 | 序列化/反序列化有性能开销,局部操作不灵活 | 以整体读写为主的场景(如用户信息完整展示) |
哈希类型 | 集中存储键值对(如hmset user:1 name James age 23 ) | 兼顾简单性与灵活性,局部操作高效 | 需控制内部编码转换,可能引发内存消耗波动 | 频繁进行局部属性读写的场景(推荐首选) |
2.3.5 缓存方式对比
目前,缓存用户信息可采用以下三种方式,其实现方法及优缺点分析如下:
- 原生字符串类型——以字符串类型存储,每个属性对应一个键
set user:1:name James
set user:1:age 23
set user:1:city Beijing
优点:实现简单,对单个属性的变更操作灵活。
缺点:占用键数量过多,内存消耗较大;用户信息在Redis中存储分散,缺乏内聚性,实际实用性较低。
- 序列化字符串类型(如JSON格式)
set user:1 经过序列化 的用户对象字符串
优点:适合整体操作的信息场景适配性好,编程实现简单;若选择合适的序列化方案,内存使用效率较高。
缺点:存在序列化与反序列化的性能开销,对单个属性的操作灵活性差。
- 哈希类型
hmset user:1 name James age 23 city Beijing
优点:实现简单、直观,灵活性强,尤其适合信息的局部变更或获取操作。
缺点:需控制哈希在ziplist和hashtable两种内部编码间的转换,可能导致较大的内存消耗。
二、List 列表
1. LIST总体介绍
- 列表类型用来存储多个字符串,每个字符串可以称为列表的一个元素,一个列表最多存储 232−12^{32}-1232−1 个元素。
- 在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等(参考下图)
- 由于列表的两端插入这种灵活特性,它还可以充当栈和队列的角色,在实际开发中有很多应用
列表的插入和弹出:
列表的获取和删除
列表元素特点:
- 列表元素的位置确定,支持通过下标直接获取。例如我们可以通过
lindex key 4
获取列表的第 4 个元素 - 如上图,删除第二个元素,后面的三个元素均会左移一位
- 元素允许重复
Redis 的列表在功能上可类比 c++ 中的双端队列 deque,左右插入都是O(1) 常量级复杂度;随机访问的话需要从头遍历值访问的下标,是线性复杂度
2. List 普通命令
lpush
将一个或多个元素从左侧放入列表中(头插)
时间复杂度:O(k)(k 为插入元素个数,一般直接视为O(1))
LPUSH key element [element ...]
返回值:插入后 list 的长度
127.0.0.1:6379> lpush mylist "world"
(integer) 1
127.0.0.1:6379> lpush mylist "hello"
(integer) 2
127.0.0.1:6379> Lrange mylist 0 -1
1) "hello"
2) "world"
lpushx
当 key 存在时,将一个或多个元素从左侧放入列表中(头插),如果 key 不存在直接返回
返回值:插入 list 的长度
时间复杂度:O(k)(k 为插入元素个数,一般直接为O(1))
127.0.0.1:6379> lpush mylist1 world
(integer) 1
127.0.0.1:6379> lpushx mylist1 Hello
(integer) 2
127.0.0.1:6379> lpushx otherlist hello
(integer) 0
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
rpush
将一个或多个元素从右侧放入列表中(尾插)
RPUSH key element [element ...]
返回值插入后列表的长度:
时间复杂度:O(k) (k 为插入元素个数,一般直接为O(1))
rpushx
在 key 存在时,将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。
语法:
RPUSHX key element [element ...]
返回值:插入后 list 的长度
时间复杂度:O(N) (N 为插入元素个数,一般插入元素较少,可视为O(1))
示例:
redis> RPUSH mylist "World"
(integer) 1
redis> RPUSHX mylist "Hello"
(integer) 2
redis> RPUSHX otherlist "Hello"
(integer) 0
redis> LRANGE mylist 0 -1
1) "World"
2) "Hello"
redis> LRANGE otherlist 0 -1
(empty array)
lrange
获取从 start 到 end 区间的所有元素,左闭右闭。
语法:
LRANGE key start stop
最坏时间复杂度:O(N)
返回值:指定区间的元素。
示例:
127.0.0.1:6379> lrange mylist 0 0
1) "hello"
127.0.0.1:6379> lrange mylist -3 2
2) "hello"
3) "world"
127.0.0.1:6379> lrange mylist -100 100
4) "hello"
5) "world"
127.0.0.1:6379> lrange mylist 4 6
(empty array)
lpop
从 list 左侧取出数据(头删)
语法:
lop key
返回值:取出的元素或者 nil
时间复杂度:O(1)
示例:
127.0.0.1:6379> rpush mylist one two three four five
(integer) 5
127.0.0.1:6379> lpop mylist
"one"
127.0.0.1:6379> lpop mylist
"two"
127.0.0.1:6379> lpop mylist
"three"
127.0.0.1:6379> lrange mylist 0 -1
1) "four"
2) "five"
rpop
从右侧取出数据
语法:
rpop key
返回值:取出的元素或者 nil
时间复杂度: O(1)
示例:
127.0.0.1:6379> rpush mylist one two three four five
(integer) 5
127.0.0.1:6379> rpop mylist
"five"
127.0.0.1:6379> rpop mylist
"four"
127.0.0.1:6379> rpop mylist
"three"
linsert
根据提供的值查找位置,然后插入元素
LINSERT key <BEFORE | AFTER> pivot element
返回值:插⼊后的 list ⻓度。
时间复杂度:O(N)
示例:
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"
lindex
获取指定下标 index 位置的下标
语法:
lindex key index
时间复杂度:O(N)
返回值:取出的元素或者nil
示例:
127.0.0.1:6379> rpush mylist one two three four five
(integer) 5
127.0.0.1:6379> lindex mylist 0
"one"
127.0.0.1:6379> lindex mylist 5
(nil)
127.0.0.1:6379> lindex mylist -1
"five"
127.0.0.1:6379> lindex mylist 4
"five"
127.0.0.1:6379> lindex mylist -4
"two"
127.0.0.1:6379> lindex mylist -5
"one"
llen
获取 list 长度
语法:
llen key
返回值:list 的长度
127.0.0.1:6379> lpush mylist1 "world"
(integer) 1
127.0.0.1:6379> llen mylist1
(integer) 1
lrem
删除 count 个 element 元素
LREM key count element
- count > 0: 从左边开始找 count 个 等于 element 的元素
- count < 0: 从右边开始找 ∣count∣|count|∣count∣ 个 等于 element 的元素
- count = 0 : 删除整个 List 中等于 element 的元素
时间复杂度:O(N)
示例:
- 先构造一个列表,内容为 a b a b a b a b
127.0.0.1:6379> rpush mylist a b
(integer) 2
127.0.0.1:6379> rpush mylist a b
(integer) 4
127.0.0.1:6379> rpush mylist a b
(integer) 6
127.0.0.1:6379> rpush mylist a b
(integer) 8
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
2) "b"
3) "a"
4) "b"
5) "a"
6) "b"
7) "a"
8) "b"
- 测试lrem 操作:
127.0.0.1:6379> lrem mylist 2 a
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "b"
3) "a"
4) "b"
5) "a"
6) "b"
127.0.0.1:6379> lrem mylist -2 a
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
7) "b"
8) "b"
9) "b"
10) "b"
127.0.0.1:6379> lrem mylist 0 b
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
(empty array)
ltrim
LTRIM key start stop
仅保留 【start, stop】闭区间中的元素,其余直接删除,注意下标从0开始
返回值:start, stop 区间内的元素
时间复杂度:O(N)
示例:
127.0.0.1:6379> rpush mylist1 a b c d e f g h
(integer) 8
127.0.0.1:6379> ltrim mylist1 2 5
OK
127.0.0.1:6379> lrange mylist1 0 -1
1) "c"
2) "d"
3) "e"
4) "f"
lset
lset key index element
修改 index 位置的元素为 element
时间复杂度:O(N)
返回值:成功返回 OK,下标超出范围会报错
1) "c"
2) "d"
3) "e"
4) "f"
127.0.0.1:6379> lset mylist1 2 "hello world"
OK
127.0.0.1:6379> lrange mylist1 0 -1
5) "c"
6) "d"
7) "hello world"
8) "f"
127.0.0.1:6379> lset mylist1 4 "1"
(error) ERR index out of range
3. List 阻塞版本命令
blpop 和 brpop 是 lpop 和 rpop 的阻塞版本:
- 在列表中有元素的情况下,阻塞和非阻塞表现是一致的。
- 如果列表中没有元素,执行阻塞版本命令的客户端会根据timeout 阻塞一段时间,期间redis 服务器能正常处理其他客户端的命令
- 等待过程中,列表中一旦有元素立即返回
- 若 Redis 中设置了多个键,那么就会从左向右进行遍历,一旦有一个键对应的列表中可以弹出元素,命令立即返回
blpop
lpop 的阻塞版本
语法:
blpop key [key ...] timeout
时间复杂度:O(1)
返回值:取出的数据或者nil
brpop
brpop 的 阻塞版本
语法:
brpop key [key ...] timeout
用法与 blpop 一致,此处不再赘述
brpop 和 blpop 适合用于阻塞队列
4. List 命令小结
操作类型 | 命令 | 时间复杂度 |
---|---|---|
添加 | rpush key value [value ...] | O(k)O(k)O(k),kkk 是元素个数 |
添加 | lpush key value [value ...] | O(k)O(k)O(k),kkk 是元素个数 |
添加 | linsert key before | after pivot value | O(n)O(n)O(n),nnn 是 pivot 距离头尾的距离 |
查找 | lrange key start end | O(s+n)O(s+n)O(s+n),sss 是 start 偏移量,nnn 是 start 到 end 的范围 |
查找 | lindex key index | O(n)O(n)O(n),nnn 是索引的偏移量 |
查找 | llen key | O(1)O(1)O(1) |
删除 | lpop key | O(1)O(1)O(1) |
删除 | rpop key | O(1)O(1)O(1) |
删除 | lrem key count value | O(k)O(k)O(k),kkk 是列表长度 |
删除 | ltrim key start end | O(k)O(k)O(k),kkk 是列表长度 |
修改 | lset key index value | O(k)O(k)O(k),kkk 是索引的偏移量 |
阻塞操作 | blpop brpop | O(1)O(1)O(1) |
5. List 内部编码
对于旧版本 Redis (Redis 3.2 之前):
- ziplist(压缩列表):元素个数少时使用。这种结构更为紧凑,但访问效率较低,所以仅个数少时使用。
- linkedlist(链表):当列表类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内部实现。
Redis 3.2 之后,采用 quicklist 作为 List 的底部编码
quicklist 时 ziplist 和 linkedlist 的结合,也就是 linkedlist 的每个节点是 ziplist 。
在 /etc/redis/redis.conf 文件中,存在一个配置项 list-max-ziplist-size 表示单个压缩列表允许的最大字节数。
List 使用场景
普通消息队列
1. 消息队列
如下图所示,生产通过 lpush 放入元素,多个消费者通过 brpop 排队阻塞等待元素。这多个消费者客户端就直接能保证负载均衡和高可用
分频道消息队列
由于 brpop 支持等待多个键的特性,我们可以直接基于此实现一个分频道消息队列,实现订阅不同频道的效果,如下图:
2. 微博timeline
每个用户都有自己的 timeline (微博列表),现需要按照分页来展示文章列表。此时可考虑使用列表,列表顺序固定,且支持按照索引获取元素
- 每篇微博使用 hash 结构存储
hmset mblog:1 title xx timestamp 1476536196 content xxxxx
...
hmset mblog:n title xx timestamp 1476536196 content xxxxx
- 向用户的 timeline 中添加微博,user:uid:mblogs作为键
lpush user:1:mblogs mblog:1 mblog:3
...
lpush user:k:mblogs mblog:9
- 分页获取用户的 timeline,例如获取用户1的前十篇微博
keylist = lrange user:1:mblogs 0 9
for key in keylist {hgetall key
}
这种方案存在两个问题:
- 第 3)步中,每篇微博都会执行一次 hgetall,这需要多次网络请求。更推荐的做法是通过 pipeline 流水线式 打包命令,批量提交,一页只请求一次。
- 分页获取文章是,lrange 在两端较快,若是在中间获取较慢,可考虑将列表做拆分
三、Set 集合
1.Set 总体介绍
集合类型也是保存多个字符串类型的元素的,需要注意:
- Set 中元素无序
- Set 中元素不能重复
- 一个集合最多存 232−12^{32}-1232−1 个元素
Redis 对 Set 提供了增删查改的基本操作,同时还支持取交集、并集、差集。
2. Set 普通命令
sadd、 smembers、 sismember
- sadd : 将一个或多个元素添加到set中。注意,重复的元素无法添加到 set 中
SADD key member [member ...]
- 时间复杂度:O(1)
- 返回值:本次成功添加的元素个数
示例:
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SADD myset "World"
(integer) 0
redis> SMEMBERS myset
1) "Hello"
2) "World"
- smembers : 获取出集合中的所有元素
- 时间复杂度: O(1)
- 返回值: 所有元素
示例:
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SADD myset "World"
(integer) 0
redis> SMEMBERS myset
1) "Hello"
2) "World"
- sismember : 判断一个元素在不在 set 中
SISMEMBER key member
- 时间复杂度:O(1)
- 返回值:1表示元素在 set 中,0表示不再
示例:
redis> SADD myset "one"
(integer) 1
redis> SISMEMBER myset "one"
(integer) 1
redis> SISMEMBER myset "two"
(integer) 0
scard
获取 set 中的元素个数
SCARD key
- 时间复杂度:O(1)
- 返回值:元素个数
示例:
127.0.0.1:6379> sadd myset 1 2 3 4 5
(integer) 5
127.0.0.1:6379> scard myset
(integer) 5
spop
从 set 中随机删除并返回一个或多个元素。
spop key [count]
- 时间复杂度 : O(k) k 是count
- 返回值:取出的元素
示例:
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> spop myset 3
6) "2"
7) "3"
8) "1"
127.0.0.1:6379> smembers myset
9) "4"
10) "5"
127.0.0.1:6379> spop myset 1
11) "4"
srandmember
随机取一个或多个元素
SRANDMEMBER key [count]
- 当 count 为正数时:
返回集合中不重复的随机元素,数量为 count(若集合元素总数小于 count,则返回所有元素) - 当 count 为负数时:
返回的随机元素可能重复,数量为 count 的绝对值(即使集合元素总数小于该绝对值,也会返回指定数量的元素,允许重复) - 当 count 未指定时:
默认返回 1 个随机元素(不重复)
- 时间复杂度:O(N) N 为count
smove
将一个元素从 源 set中取出放入目标 set
smove source destination member
- 时间复杂度:O(1)
- 1表示移动成功,0表示失败
如果 destination 集合中有指定元素,则不会认为移动失败,效果相当于从source中删除指定元素,当然如果 source中没有member,则会返回0认为移动失败
示例:
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myotherset "three"
(integer) 1
redis> SMOVE myset myotherset "two"
(integer) 1
redis> SMEMBERS myset
1) "one"redis> SMEMBERS myotherset
1) "three"
2) "two
srem
将指定元素删除
SREM key member [member ...]
- 时间复杂度 O(N) N为总元素个数
- 返回值:本次操作删除的元素个数
示例:
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myset "three"
(integer) 1
redis> SREM myset "one"
(integer) 1
redis> SREM myset "four"
(integer) 0
redis> SMEMBERS myset
1) "three"
2) "two"
3. 集合间操作
交集、并集、差集
sinter
获取指定集合的交集
SINTER key [key ...]
- 时间复杂度:O(N * M), N 是最⼩的集合元素个数. M 是最⼤的集合元素个数.
- 返回值:交集的元素
127.0.0.1:6379> sadd key 1 2 3 4
(integer) 4
127.0.0.1:6379> sadd key2 3 4 5 6
(integer) 4
127.0.0.1:6379> sinter key key2
1) "3"
2) "4"
sinterstore
求交集,并把交集元素放入一个新创建的 集合中
SINTERSTORE destination key [key ...]
- 时间复杂度:O(N * M), N 是最⼩的集合元素个数. M 是最⼤的集合元素个数.
- 返回值:交集的元素个数
示例:
127.0.0.1:6379> sadd key 1 2 3 4
(integer) 4
127.0.0.1:6379> sadd key2 3 4 5 6
(integer) 4
127.0.0.1:6379> SINTERSTORE inter_set key key2
(integer) 2
127.0.0.1:6379> SMEMBERS inter_set
1) "3"
2) "4"
sunion
获取指定集合的并集
SUNION key [key ...]
- 时间复杂度:O(N ), 总元素个数.
- 返回值:并集的元素
示例:
127.0.0.1:6379> sunion key key2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
sunionstore
求并集,并把并集元素放入一个新创建的 集合中
SINTERSTORE destination key [key ...]
- 时间复杂度:O(N),总元素个数
- 返回值:并集的元素个数
127.0.0.1:6379> sunionstore union_key key key2
(integer) 6
127.0.0.1:6379> SMEMBERS union_key
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
sdiff
获取给定 set 的元素个数
语法:
sdiff key [key ...]
时间复杂度:O(N),N给定的集合的总的元素个数
返回值:差集的元素
127.0.0.1:6379> sadd key2 3 4 5
(integer) 3
127.0.0.1:6379> sdiff key1 key2
1) "1"
2) "2"
127.0.0.1:6379> sdiff key2 key1
3) "5"
sdiffstore
求差集,并把差集元素放入一个新创建的 集合中
SDIFFSTORE destination key [key ...]
- O(N), N 给定的所有集合的总的元素个数
- 返回值:差集的元素个数。
4. Set 命令小结
以下是转换后的 Markdown 格式:
命令 | 时间复杂度 |
---|---|
sadd key element [element …] | O(k),k 是元素个数 |
srem key element [element …] | O(k),k 是元素个数 |
scard key | O(1) |
sismember key element | O(1) |
srandmember key [count] | O(n),n 是 count |
spop key [count] | O(n), n 是 count |
smembers key | O(k),k 是元素个数 |
sinter key [key …] sitnerstore | O(m * k),k 是几个集合中元素最小的个数,m 是键个数 |
sunion key [key …] sunionstore | O(k),k 是多个集合的元素个数总和 |
sdiff key [key …] sdiffstore | O(k),k 是多个集合的元素个数总和 |
5. Set 内部编码
集合的编码有两种:
- intset (整数集合):当集合中的元素都是整数并且元素的个数⼩于 set-max-intset-entries 配置(默认 512 个)时,Redis 会选⽤ intset 来作为集合的内部实现,从⽽减少内存的占用。
- hashtable(哈希表):当集合类型⽆法满⾜ intset 的条件时,Redis 会使⽤ hashtable 作为集合的内部实现。
6.应用场景
用户兴趣标签
场景:给用户打兴趣标签(如 “篮球”“音乐”“阅读”),用于个性化推荐。
实现:
- 用 Set 存储用户的兴趣标签:SADD user:10086:interests 篮球 音乐
推荐逻辑: - 计算用户兴趣交集:SINTER user:10086:interests user:10010:interests(找到与用户 10086 兴趣相似的用户 10010)
- 还可以通过交集计算用户群体的共同标签
- 基于标签推送内容:SUNION tag:篮球 tag:音乐(获取用户可能感兴趣的内容 ID)
四、Zset 有序集合
1. Zset 总体认识
有序集合保留了 Set 的元素不能重复特性,并在此基础上给每个元素引入了 分数(score),Zset 会基于分数对元素进行升序排序,这就是 Zset 的有序特性。
Zset 提供了获取指定分数和元素范围查找、计算成员排名等功能,合理利用 Zset 可以帮助我们在实际开发中解决很多问题
Zset 元素不能重复,但不同元素的分数可以一样
关于有序概念在不同上下文中的理解
数据结构 | 是否允许重复元素 | 是否有序 | 有序依据 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 时间轴、消息队列等 |
集合 | 否 | 否 | 标签、社交等 | |
有序集合 | 否 | 是 | 分数 | 排行榜系统、社交等 |
2. Zset 普通命令
zadd
- 添加或者更新指定的元素以及关联的分数到 zset 中,可添加多对。
- 分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。
语法:
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]
选项介绍:
- XX 表示仅在member 存在的情况下更新元素的分数;
- NX 表示仅在 member 不存在的情况下添加新元素和分数
- CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还会包含本次更新的元素的个数。
- INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。
- LT : less than 的缩写,表示仅在分数比原来小的时候更新分数
- GT : greater than 的缩写,表示仅在分数比原来大的时候更新分数
时间复杂度:O(log(N)) N 是 Zset 的总元素个数
返回值:本次添加成功的元素个数
示例:
- 不加选项(需要注意的就是每一对元素中,分数在前,名称在后)
127.0.0.1:6379> zadd my_zset 99 Tom 99 Jerry 97 David 95 Tim
(integer) 4
127.0.0.1:6379> zrange 0 -1
1) "Tim"
2) "David"
3) "Jerry"
4) "Tom"127.0.0.1:6379> zrange my_zset 0 -1 withscores
1) "Tim"
2) "95"
3) "David"
4) "97"
5) "Jerry"
6) "99"
7) "Tom"
8) "99"127.0.0.1:6379> zadd my_zset 90 Tom
(integer) 0
127.0.0.1:6379> zrange my_zset 0 -1
1) "Tom"
2) "Tim"
3) "David"
4) "Jerry"
- XX 和 NX
127.0.0.1:6379> zadd my_zset NX 0 Jerry
(integer) 0
127.0.0.1:6379> zrange my_zset 0 -1 withscores
1) "Tom"
2) "90"
3) "Tim"
4) "95"
5) "David"
6) "97"
7) "Jerry"
8) "99"
127.0.0.1:6379> zadd my_zset XX 0 Jerry
(integer) 0127.0.0.1:6379> zrange my_zset 0 -1 withscores
1) "Jerry"
2) "0"
3) "Tom"
4) "90"
5) "Tim"
6) "95"
7) "David"
8) "97"127.0.0.1:6379> zadd my_zset XX 100 zhangsan
(integer) 0
127.0.0.1:6379> zrange my_zset 0 -1 withscores
1) "Jerry"
2) "0"
3) "Tom"
4) "90"
5) "Tim"
6) "95"
7) "David"
8) "97"127.0.0.1:6379> zadd my_zset NX 100 zhangsan
(integer) 1
127.0.0.1:6379> zrange my_zset 0 -1 withscores1) "Jerry"2) "0"3) "Tom"4) "90"5) "Tim"6) "95"7) "David"8) "97"9) "zhangsan"
10) "100"
- CH选项
127.0.0.1:6379> zadd my_zset 100 Tom
(integer) 0
127.0.0.1:6379> zadd my_zset CH 100 Jerry
(integer) 1
- INCR
127.0.0.1:6379> zrange my_zset 0 -1 withscores1) "David"2) "97"3) "Jerry"4) "100"5) "Tim"6) "100"7) "Tom"8) "100"9) "zhangsan"
10) "100"
127.0.0.1:6379> zadd my_zset incr 100 Tim
"200"
127.0.0.1:6379> zrange my_zset 0 -1 withscores11) "David"12) "97"13) "Jerry"14) "100"15) "Tom"16) "100"17) "zhangsan"18) "100"19) "Tim"
20) "200"
zcard
获取 zset 中的元素个数
ZCARD key
- 时间复杂度:O(1)
- 返回值:zset 内的元素个数。
127.0.0.1:6379> zrange my_zset 0 -1 withscores1) "David"2) "97"3) "Jerry"4) "100"5) "Tim"6) "100"7) "Tom"8) "100"9) "zhangsan"
10) "100"
127.0.0.1:6379> zcard my_zset
(integer) 5
zcount
返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的。通过 ( 可以改为开区间
ZCOUNT key min max
- 时间复杂度:O(log(N))
- 返回值:满足条件的元素列表个数
示例:
127.0.0.1:6379> zrange my_zset 0 -1 withscores1) "David"2) "97"3) "Jerry"4) "100"5) "Tom"6) "100"7) "zhangsan"8) "100"9) "Tim"
10) "200"
127.0.0.1:6379> zcount my_zset 100 200
(integer) 4
127.0.0.1:6379> zcount my_zset (100 200
(integer) 1
127.0.0.1:6379> zcount my_zset (100 (200
(integer) 0
zrange
返回指定区间⾥的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。
ZRANGE key start stop [WITHSCORES]
此处的 [start, stop] 为下标构成的闭区间. 从 0 开始, ⽀持负数.
时间复杂度:O(log(N)+M N 是 Zset 总元素个数,M是区间内元素个数
返回值:区间内的元素列表。
zrevrange
返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。
ZREVRANGE key start stop [WITHSCORES]
备注:这个命令在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。本博客仅针对Redis 6.0 所以不做过多讨论
时间复杂度:O(log(N)+M)
返回值:区间内的元素列表。
zrangebyscore
返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 来改成开区间。
备注:这个命令在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
语法:
ZRANGEBYSCORE key min max [WITHSCORES]
时间复杂度:O(log(N)+M)
返回值:区间内的元素列表。
示例:
127.0.0.1:6379> zrangebyscore my_zset 0 100 withscores
1) "David"
2) "97"
3) "Jerry"
4) "100"
5) "Tom"
6) "100"
7) "zhangsan"
8) "100"
zpopmax 和 zpopmin
- zpopmax : 删除并返回分数最⾼的 count 个元素。
ZPOPMAX key [count]
- 时间复杂度:O(log(N) * M)
- 返回值:分数和元素列表。
优先删除字典序更低的元素
示例:
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZPOPMAX myzset
1) "three"
- zpopmin :删除并返回分数最低的 count 个元素。
ZPOPMIN key [count]
- 时间复杂度:O(log(N) * M)
- 返回值:分数和元素列表。
分数相同,优先删除字典序低的
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZPOPMIN myzset
1) "one"
2) "1"
bzpopmax 和 bzpopmin
这是 zpopmax 和 zpopmin 的阻塞版本((仅支持弹出一个元素,可阻塞等待多个 key ),可用于实现阻塞优先级队列。
- BZPOPMAX
1 BZPOPMAX key [key ...] timeout
- 时间复杂度:O(log(N))
- 返回值:元素列表。
⽰例:
redis> DEL zset1 zset2
(integer) 0
redis> ZADD zset1 0 a 1 b 2 c
(integer) 3
redis> BZPOPMAX zset1 zset2 0
1) "zset1"
2) "c"
3) "2"
- ZPOPMIN
BZPOPMIN key [key ...] timeout
- 时间复杂度:O(log(N))
- 返回值:元素列表。
示例:
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZPOPMIN myzset
1) "one"
2) "1
zrank 和 zrevrank
- zrank : 返回指定元素按照升序的排名
ZRANK key member
- 时间复杂度:O(log(N))
- 返回值:排名。
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZRANK myzset "three"
(integer) 2
redis> ZRANK myzset "four"
(nil)
- zrevrank:返回指定元素的排名,降序。
zrevrank key member
- 时间复杂度:O(log(N))
- 返回值:排名。
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZREVRANK myzset "one"
(integer) 2
redis> ZREVRANK myzset "four"
(nil)
zscore
返回指定元素的分数
ZSCORE key member
- 时间复杂度:O(1),redis对此做了特殊优化
- 返回值:分数。
示例:
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZSCORE myzset "one"
"1"
zrem
删除指定的元素
ZREM key member [member ...]
- 时间复杂度:O(M * log(N)) (M 是删除的元素个数)
- 返回值:本次操作删除的元素个数。
示例:
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZREM myzset "two"
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "three"
4) "3"
zremrangebyrank
按照升序删除指定范围的元素,闭区间
ZREMRANGEBYRANK key start stop
- 时间复杂度:O(log(N)+M) M 是被删除的元素个数
- 返回值:本次操作删除的元素个数。
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZREMRANGEBYRANK myzset 0 1
(integer) 2
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "three"
2) "3"
zremrangebyscore
按照分数删除指定范围的元素,左闭右闭。
ZREMRANGEBYSCORE key min max
- 时间复杂度:O(log(N)+M)
- 返回值:本次操作删除的元素个数。
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZREMRANGEBYSCORE myzset -inf (2
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "two"
2) "2"
3) "three"
4) "3"
zincrby
为指定的元素的关联分数加上指定的分数值。
ZINCRBY key increment member
- 时间复杂度:O(log(N))
- 返回值:增加后元素的分数
Zset 集合间操作命令
交集
zinterstore
求出给定有序集合中元素的交集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
选项介绍:
- numkeys : 必须指定要合并的 key 的数量
- WEIGHTS:可选,为待合并的key 分配权重
- AGGREGATE :可选 sum | min | max , 分别是按权重求和,乘以权重后取最小值,乘以权重后取最大值。
时间复杂度:O(N∗K)+O(M∗log(M))O(N*K)+O(M*log(M))O(N∗K)+O(M∗log(M)) N 是输⼊的有序集合中, 最小的有序集合的元素个数; K 是输入了几个有序集合; M 是最终结果的有序集合的元素个数
返回值:最终结果的有序集合的元素个数
示例:
zunionstore
求出给定有序集合中元素的并集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
- 时间复杂度:O(N)+O(M∗log(M))O(N)+O(M*log(M))O(N)+O(M∗log(M)) N 是输⼊的有序集合总的元素个数; M 是最终结果的有序集合的元素个数.
- 返回值:⽬标集合中的元素个数
选项和 zinterstore 一样
示例:
4. 命令小结
命令 | 时间复杂度 |
---|---|
zadd key score member [score member ...] | O(k⋅log(n))O(k \cdot \log(n))O(k⋅log(n)),kkk 是添加成员的个数,nnn 是当前有序集合的元素个数 |
zcard key | O(1)O(1)O(1) |
zscore key member | O(1)O(1)O(1) |
zrank key member zrevrank key member | O(log(n))O(\log(n))O(log(n)),nnn 是当前有序集合的元素个数 |
zrem key member [member ...] | O(k⋅log(n))O(k \cdot \log(n))O(k⋅log(n)),kkk 是删除成员的个数,nnn 是当前有序集合的元素个数 |
zincrby key increment member | O(log(n))O(\log(n))O(log(n)),nnn 是当前有序集合的元素个数 |
zrange key start end [withscores] zrevrange key start end [withscores] | O(k+log(n))O(k + \log(n))O(k+log(n)),kkk 是获取成员的个数,nnn 是当前有序集合的元素个数 |
zrangebyscore key min max [withscores] zrevrangebyscore key max min [withscores] | O(k+log(n))O(k + \log(n))O(k+log(n)),kkk 是获取成员的个数,nnn 是当前有序集合的元素个数 |
zcount key min max | O(log(n))O(\log(n))O(log(n)),nnn 是当前有序集合的元素个数 |
zremrangebyrank key start end | O(k+log(n))O(k + \log(n))O(k+log(n)),kkk 是获取成员的个数,nnn 是当前有序集合的元素个数 |
zremrangebyscore key min max | O(k+log(n))O(k + \log(n))O(k+log(n)),kkk 是获取成员的个数,nnn 是当前有序集合的元素个数 |
zinterstore destination numkeys key [key ...] | O(n⋅k)+O(m⋅log(m))O(n \cdot k) + O(m \cdot \log(m))O(n⋅k)+O(m⋅log(m)),nnn 是输入的集合最小的元素个数,kkk 是集合个数,mmm 是目标集合元素个数 |
zunionstore destination numkeys key [key ...] | O(n)+O(m⋅log(m))O(n) + O(m \cdot \log(m))O(n)+O(m⋅log(m)),nnn 是输入集合总元素个数,mmm 是目标集合元素个数 |
5. 内部编码
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会⽤ ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
- skiplist(跳表):当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时ziplist 的操作效率会下降。
关于跳表将在后续介绍
6. 应用场景—排行榜系统
在使用 Redis 的有序集合(Zset)维护每天的热榜(以点赞数为维度)这一场景中,Zset 的特性使其成为一个非常合适的选择,以下是具体的介绍:
-
初始化热榜:每天开始时,可以创建一个新的 Zset 来存储当天的热榜数据。例如,使用键名
hot_list:2024-10-01
来表示 2024年10月1日的热榜,使用ZADD
命令向其中添加帖子的 ID 作为成员,初始点赞数(假设为0)作为分数。ZADD hot_list:2024-10-01 0 post_1 ZADD hot_list:2024-10-01 0 post_2
-
更新点赞数:当有用户对帖子进行点赞操作时,通过
ZINCRBY
命令增加对应帖子在 Zset 中的点赞数(分数)。ZINCRBY hot_list:2024-10-01 1 post_1
-
获取热榜数据:要获取当天点赞数排名前10的帖子,可以使用
ZREVRANGE
命令(因为点赞数越高越热门,所以使用反向排序获取高分元素)。ZREVRANGE hot_list:2024-10-01 0 9 WITHSCORES
其中
WITHSCORES
参数用于在返回成员的同时返回其对应的分数(点赞数),方便展示。 -
获取特定帖子的排名:如果想知道某个帖子在热榜中的排名,可以使用
ZREVRANK
命令。ZREVRANK hot_list:2024-10-01 post_1
该命令会返回帖子
post_1
在热榜中的排名(从0开始计数)。
五、其他类型
Redis 数据类型
Redis 还提供了其他的一些数据类型,虽然这些类型使用率不高,但在特定场景下使用,还是方便且高效的。
- Streams : 类似于消息队列(阻塞队列),支持高效的消息发布、订阅等功能
- Geospatial : 用于存储地理坐标的(经纬度)。支持用户在不同坐标减进行距离查询等操作
- Hyperloglog : 以极低的空间代价,估算大集合元素的个数(单个 Hyperloglog 12 KB)
- Bitset :位图,用一个bit表示对应位的数字是否存在
- BitFields :位域类型,位域可以指定每一个元素所占比特位,从而更方便的进行位操作。(类似 c 的位段)
六、 补充
渐进式遍历
keys * 在生产环境中很危险。
通过渐进式遍历,就可以做到,既能够获取所有的key,同时又不会卡死服务器
以渐进式的方式进行键的遍历
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
- 时间复杂度 O(1)
- 下一次scan的的光标(cursor)以及本次得到的键
选项: - pattern 同 keys 命令
- count :限制本次遍历能获取到多少元素,默认为 10 。count不是精确值,实际返回的个数可能略有出入
- type : 仅返回 value 是对应类型的key
count 并非下标,实际返回的数字我们是无法预测的,我们只知道 0 表示开始以及接着上一次scan 命令返回的count 继续遍历
示例:
127.0.0.1:6379> mset k1 1 k2 2 k3 3 k4 4 k5 5 k6 6 k7 7 k8 8 k9 9 k0 0
OK
127.0.0.1:6379> scan 0
1) "15"
2) 1) "k7"2) "k8"3) "k1"4) "k5"5) "k2"6) "k0"7) "k3"8) "k9"9) "k4"3) "k6"127.0.0.1:6379> scan 15
1) "0"
2) (empty array)127.0.0.1:6379> scan 0 count 5
1) "14"
2) 1) "k7"3) "k8"4) "k1"5) "k5"6) "k2"127.0.0.1:6379> scan 14 count 5
1) "15"
2) 1) "k0"3) "k3"4) "k9"5) "k4"6) "k6"127.0.0.1:6379> scan 15 count 5
1) "0"
2) (empty array)
除了 scan 以外,Redis ⾯向哈希类型、集合类型、有序集合类型分别提供了 hscan、sscan、zscan 命令,它们的⽤法和 scan 基本类似,感兴趣的读者可以⾃⾏做扩展学习。
渐进性遍历 scan 虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增加、修改、删除),可能导致遍历时键的重复遍历或者遗漏,这点务必在实际开发中考虑。
数据库操作
MySQL 中有 database 的概念,一个 MySQL服务器可以有多个 database
Redis 不能创建数据库,而是本身就提供了16个数据库(0 - 15),各个数据库相互独立。默认操作 0 号数据库
Redis提供了数据库相关的命令:dbsize、select、flushdb、flushall 命令。
select
切换数据库
select dbIndex
各个数据库之间相互独立,dbIndex 取 0-15
示例:
127.0.0.1:6379> scan 0
1) "15"
2) 1) "k7"2) "k8"3) "k1"4) "k5"5) "k2"6) "k0"7) "k3"8) "k9"9) "k4"3) "k6"127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> scan 0
1) "0"
2) (empty array)
❗ Redis 中虽然⽀持多数据库,但随着版本的升级,其实不是特别建议使⽤多数据库特性。如果真的需要完全隔离的两套键值对,更好的做法是维护多个 Redis 实例,⽽不是在⼀个Redis 实例中维护多数据库。这是因为本⾝ Redis 并没有为多数据库提供太多的特性,其次⽆论是否有多个数据库,Redis 都是使⽤单线程模型,所以彼此之间还是需要排队等待命令的执⾏。同时多数据库还会让开发、调试和运维⼯作变得复杂。所以实践中,始终使⽤数据库 0 其实是⼀个很好的选择。
dbsize
返回当前数据库键的个数
127.0.0.1:6379> dbsize
(integer) 10
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
flushall 和 flushdb
- flushdb : 清空当前数据库
- flushall : 清空所有数据库
❗ 永远不要在线上环境执⾏清除数据的操作