摘要
Redis version:6.2.6
Redis官方文档:https://redis.io/documentation (opens new window)
# 一:前言
Redis有5种基础数据结构,分别为:string(字符串)、list(列表)、set(集合)、hash(哈希)和zset(有序集合)
# 二:全局命令
## 查看所有键 O(n)
> keys *
1) "java"
2) "hello"
## 键总数 O(1) 直接获取 Redis 内置的键总数变量
> dbsize
(integer) 4
## 检查键是否存在【exists key】,存在返回1,不存在返回0
> exists key
(integer) 1
## 删除键【del key [key ...]】无论什么类型,结果为成功删除键的个数,删除不存在的值,会返回0
> del java
(integer) 1
## 键过期【expire key seconds】,秒为单位
## 【expireat key timestamp】:键在秒级时间戳timestamp后过期
## 【pexpire key milliseconds】:键在milliseconds毫秒后过期
## 【pexpireat key milliseconds-timestamp】:键在毫秒级时间戳timestamp后过期
## 但无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最终使用的都是pexpireat。
## 如果键不存在,返回结果为0
## 如果时间为负值,键会立即被删除,犹如使用del命令一样
> expire hello 10
(integer) 1
> expire hello -2
(integer) 1
> get hello
(nil)
### 设置键在2016-08-01 00:00:00(秒级时间戳为1469980800)
> expireat hello 1469980800
(integer) 1
## tip
## setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间
## ttl 命令会返回键的剩余过期时间
## pttl 返回毫秒级剩余过期时间
## 他们有3种返回值:
## 大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)
## -1:键没设置过期时间
## -2:键不存在
> ttl hello
(integer) 7
## persist 可以将键的过期时间清除
## tip
## 字符串类型键值,执行set命令会去掉过期时间,Redis源码中,set命令的函数setKey,最后执行removeExpire(db, key)函数去掉过期时间
## Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置
> persist key
(integer) 1
> ttl key
(integer) -1
## 键的数据结构结构 type key
> type a
string
> type mylist
list
> type not_exsit_key
none
## 查询内部编码
> object encoding hello
"embstr"
> object encoding mylist
"ziplist"
## 键重命名 rename key newkey
> rename python java
OK
## 为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖
## 返回0表示没有完成重命名
## tip
## -由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不要忽视。
## -如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之前版本返回结果略有不同。
> renamenx java python
(integer) 0
# Redis 3.2
> rename key key
OK
# Redis3.2 之前
> rename key key
(error) ERR source and destination objects are the same
## 随机返回一个键
> randomkey
"jedis"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 三:string(字符串)
Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。
字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体使用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。同样,取用户信息会经过一次反序列化的过程。
字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字 (整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能 超过512MB。
# 3.1 常用命令
## 设置值 set key value [ex seconds] [px milliseconds] [nx|xx]
## ex seconds:为键设置秒级过期时间
## px milliseconds:为键设置毫秒级过期时间
## nx:键必须不存在,才可以设置成功,用于添加
## xx:与nx相反,键必须存在,才可以设置成功,用于更新
## 与之对应命令有:
## 作用与ex选项一样:setex key seconds value
## 作用与nx选项一样:setnx key value
> exists hello
(integer) 0
> set hello world
OK
> set name 'ccj' ex 100
OK
> set name 'ccj' px 100
OK
> set name 'ccj' ex 100 px 100 # 报错,只能选择一种
ERROR syntax error
## setnx 失败,返回结果为0
> setnx hello redis
(integer) 0
## set xx 成功,返回结果为OK:
> set hello jedis xx
OK
## 获取键hello的值 get key
> get hello
"world"
> get not_exist_key
(nil)
## 批量设置值 mset key value [key value ...]
> mset a 1 b 2 c 3 d 4
OK
## 批量获取值 mget key [key ...]
> mget a b c e
1) "1"
2) "2"
3) "3"
4) (nil)
## 计数自增 incr key,返回三种情况
## 值不是整数,返回错误
## 值是整数,返回自增后的结果
## 键不存在,按照值为0自增,返回结果为1
> incr key
(integer) 2
> incr hello
(error) ERR value is not an integer or out of range
## 还提供:decr(自减)
## incrby(自增指定数字)、decrby(自减指定数字)【increment、decrement】
## incrbyfloat(自增浮点数)
## 存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程结构,任何命令到了Redis服务器都要顺序执行。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 3.2 不常用命令
## 追加值 append key value
> get key
"redis"
> append key world
(integer) 10
> get key
"redisworld"
## 字符串长度 strlen key
> strlen key
(integer) 10
## 每个中文占用3个字节
> set hello "世界"
OK
> strlen hello
(integer) 6
## 设置并返回原值 getset key value
> getset hello world
(nil)
> getset hello redis
"world"
## 设置指定位置开始的字符 setrange key offeset value
> set redis pest
OK
> setrange redis 0 b
(integer) 4
> get redis
"best"
## 获取部分字符串 getrange key start end
> getrange redis 0 1
”be"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 3.3 时间复杂度
命 令 | 时间复杂度 |
---|---|
set key value | O(1) |
get key | O(1) |
del key [key ...] | O(k),k是键的个数 |
mset key value [key value ...] | O(k),k是键的个数 |
mget key [key ...] | O(k),k是键的个数 |
incr key | O(1) |
decr key | O(1) |
incrby key increment | O(1) |
decrby key decrement | O(1) |
incrbyfloat key increment | O(1) |
append key value | O(1) |
strlen key | O(1) |
setrange key offset value | O(1) |
getrange key start end | O(n),n是字符串长度,由于获取字符串非常快,所以如果字符串不是很长,可以视为O(1) |
# 四:hash(哈希)
叫法可能是哈希、字典、关联数组。
Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同Java 的 HashMap 也是一致的,都是 "数组 + 链表" 二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个hash 结构,然后在后续的定时任务中以及 hash 的子指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。
当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
同字符串一样,hash 结构中的单个子 key 也可以进行计数,它对应的指令是 hincrby, 和 incr 使用基本一样。
# 4.1 常用命令
## 设置值 hset key field value
## 成功:1,失败:0
## 提供了hsetnx命令,和setnx命令一样,只不过作用域由键变为field
> hset user:1 name tom
(integer) 1
> hset books java "think in java" # 命令行的字符串如果包含空格,要用引号括起来
(integer) 1
## 获取值 hget key field,如果键或field不存在,会返回nil
> hget user:1 name
"tom"
## 删除field
## hdel key field [field ...]
## 返回结果为成功删除field的个数
> hdel user:1 name
(integer) 1
## 计算field个数 hlen key
> hlen user:1
(integer) 3
## 批量设置或获取field-value
## hmget key field [field ...]
## hmset key field value [field value ...]
> hmset user:1 name mike age 12 city tianjin
OK
> hmget user:1 name city
1) "mike"
2) "tianjin"
## 判断field是否存在 hexists key field
## 包含结果返回1,不包含返回0
> hexists user:1 name
(integer) 1
## 获取指定hash下的所有field
## hkeys key
> hkeys user:1
1) "name"
2) "age"
3) "city"
## 获取所有value
## hvals key
> hvals user:1
1) "mike"
2) "12"
3) "tianjin"
## 获取所有的field-value
## hgetall key
## entries(),key 和 value 间隔出现
> hgetall user:1
1) "name"
2) "mike"
3) "age"
4) "12"
5) "city"
6) "tianjin"
## tip
## 使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。
## 如果开发人员只需要获取部分field,可以使用hmget,
## 如果一定要获取全部 field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型
## hincrby key field
## hincrbyfloat key field
## 和 incrby、incrbyfloat 命令一样,不过作用域是field
> hincrby user-laoqian age 1
(integer) 30
## 计算value的字符串长度
## hstrlen key field
> hstrlen user:1 name
(integer) 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 4.2 时间复杂度
命 令 | 时间复杂度 |
---|---|
hset key field value | O(1) |
hget key field | O(1) |
hdel key field [field ...] | O(k),k是field个数 |
hlen key | O(1) |
hgetall key | O(n),n是field总数 |
hmget field [field ...] | O(k),k是field个数 |
hmset field value [field value ...] | O(k),k是field个数 |
hexists key field | O(1) |
hkeys key | O(n),n是field总数 |
hvals key | O(n),n是field总数 |
hsetnx key field value | O(1) |
hincrby key field value | O(1) |
hincrbyfloat key field increment | O(1) |
hstrlen key field | O(1) |
# 4.3 缓存用户信息
三种常用方式
- 原生字符串类型:每个属性一个键。
> set user:1:name tome
> set user:1:age 23
> set user:1:city beijing
2
3
优化:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差, 所以此种方案一般不会在生产环境使用。
- 序列化字符串类型:将用户信息序列化后用一个键保存。
> set user:1 serialize(userInfo)
优化:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
- 哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。
> hmset user:1 name tom age 23 city beijing
优化:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会消耗跟多内存。
# 五:list(列表)
列表(list) 类型是用来存储多个有序的字符串,一个列表最多可以存储 232 - 1 个元素。在 Redis 中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用 场景。
Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n)。
当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
# 5.1 操作类型
命 令 | 操 作 |
---|---|
添加 | rpush 、lpush 、linsert |
查 | lrange 、lindex 、llen |
删除 | lpop 、rpop 、lrem 、ltrim |
修改 | lset |
阻塞操作 | blpop 、brpop |
# 5.2 常用命令
## ----------添 加----------
## 从右边插入元素 rpush key value [value ...]
> rpush listkey c b a
(integer) 3
## 从左边插入元素 lpush key value [value ...]
> lpush listkey c b a
## 向某个元素前或者后插入元素 linsert key before|after pivot value
> linsert listkey before b java
(integer) 4
## ----------查 找----------
## 获取指定范围内的元素列表 lrange key start end
## 从左到右遇到的第一个插入
## 索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N
## lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同
> lrange listkey 1 3
1) "java"
2) "b"
3) "c"
## 获取列表指定索引下标的元素 lindex key index
> lindex listkey -1
"a"
## 获取列表长度 llen
> llen listkey
(integer) 4
## ----------删 除----------
## 从列表左侧弹出元素 lpop key
> lpop listkey
"a"
## 从列表右侧弹出元素 rpop key
> rpop listkey
## 删除指定元素 lrem key count value
## 根据count的不同分为三种情况:
## count>0,从左到右,删除最多count个元素。
## count<0,从右到左,删除最多count绝对值个元素
## count=0,删除所有
> lrem listkey 2 b
(integer) 2
## 按照索引范围修剪列表 ltrim key start end
> ltrim listkey 1 3
OK
## ----------修 改----------
## 修改指定索引下标的元素:lset key index newValue
> lset listkey 2 python
OK
## ----------阻 塞----------
## 阻塞式弹出
## blpop key [key ...] timeout
## brpop key [key ...] timeout
## key [key...]:多个列表的键
## timeout:阻塞时间(单位:秒)
## 列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果timeout=0,那么客户端一直阻塞等下去:
> brpop list:test 3
(nil)
(3.00s)
## 期间添加数据element1,客户端立即返回:(只返回第一个)
> brpop list:test 10
1) "list:test"
2) "element1"
## 列表不为空:客户端立即返回
> brpop list:test 2
1) "list:test"
2) "element1"
## brpop有两点需要注意:
## 1. 如果是多个键,那么brpop会从左到右遍历键,一旦有一直键能弹出元素,客户端立即返回
> brpop list:1 list:2 list:3 0
...阻塞...
## 在另外一个客户端向list:2 和 list:3 插入元素
> lpush list:2 element2
(integer) 1
> lpush list:3 element3
(integer) 1
## 多个客户端对同一键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 5.3 时间复杂度
操作类型 | 命 令 | 时间复杂度 |
---|---|---|
添加 | rpush key value [value ...] | O(k),k 是元素个数 |
lpush key value [value ...] | O(k),k 是元素个数 | |
linsert key before|after pivot value | O(n),n 是 pivot 距离列表头或尾的距离 | |
查找 | lrange key start end | O(s+n),s 是 start 偏移量,n 是 start 到 end 的范围 |
lindex key index | O(n),n 是索引的偏移量 | |
llen key | O(1) | |
删除 | lpop key | O(1) |
rpop key | O(1) | |
lrem key count value | O(n),n 是列表长度 | |
ltrim key start end | O(n),n 是要裁剪的元素总数 | |
修改 | lset key index value | O(n),n 是索引的偏移量 |
阻塞操作 | blpop brpop | O(1) |
# 5.4 开发提示
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
# 六:set(集合)
Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。一个集合最多可以存储 232-1 个元素。Redis 除了支持集合内的增删改查,同时还支持多个集合取交集、并 集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。set 结构可以用来存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。
相比列表,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
# 6.1 集合内操作
## 添加元素 sadd key element [element ...]
## 返回结果为添加成功的元素个数
> sadd myset a b c
(integer) 3
## 删除元素 srem key element [element ...]
## 返回成功删除元素个数
> srem myset a b
(integer) 2
## 计算元素个数 scard key
## 不会遍历集合所有元素,直接用Redis内部变量
> scard myset
(integer) 1
## 判断元素是否在集合中 sismember key element
## 在集合内返回1,反之返回0
> sismember myset c
## 随机从集合返回指定个数元素 srandmember key [count]
## count 可选参数,如果不写默认为1
> srandmember myset 2
1) "a"
2) "c"
## 从集合随机弹出元素 spop key
## Redis 3.2版开始,spop 也支持[count] 参数
> spop myset
"c"
## tip
## srandmember 和 spop 都是随机从集合选出元素
## spop 命令执行后,元素会从集合中删除
## srandmember 不会
## 获取所有元素 smembers key
## 返回结果是无序的
> smembers myset
1) "d"
2) "b"
3) "a"
## tip
## smembers、lrange和hgetall 都属于比较重的命令
## 如果元素过多存在阻塞Redis可能,这时候可以使用sscan完成
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 6.2 集合间操作
## 加入两个测试集合
> sadd user:1:follow it music his sports
(integer) 4
> sadd user:2:follow it news ent sports
(integer) 4
## 求多个集合的交集 sinter key [key ...]
> sinter user:1:follow user:2:follow
1) "sports"
2) "it"
## 求多个集合的并集 suinon key [key ...]
> sunion user:1:follow user:2:follow
1) "sports"
2) "it"
3) "his"
4) "news"
5) "music"
6) "ent"
## 求多个集合的差集 sdiff key [key ...]
> sdiff user:1:follow user:2:follow
1) "music"
2) "his"
## 将交集、并集、差集结果保存
## sinterstore destination key [key ...]
## suionstore destination key [key ...]
## sdiffstore destination key [key ...]
## 集合间的运算在元素较多的情况下会比较耗时
## 所以Redis提供了上面三个命令(原命令+store)
## 将集合间交集、并集、差集的结果保存在 destination key中
> sinterstore user:1_2:inter user:1:follow user:2:follow
(integer) 2
> type user:1_2:inter
set
> smembers user:1_2:inter
1) "it"
2) "sports"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 6.3 时间复杂度
命 令 | 时间复杂度 |
---|---|
sadd key elements [element ...] | O(k), k 是元素个数 |
srem key elements [element ...] | O(k), k 是元素个数 |
scard key | O(1) |
sismember key element | O(1) |
srandmember key [count] | O(count) |
spop key | O(1) |
smembers key | O(n), n 是元素总数 |
sinter key [key ...] 或者 sinterstore | O(m * k), k 是多个集合中元素最少的个数,m 是键个数 |
sunion key [key ...] 或者 suionstore | O(k), k 是多个集合元素个数和 |
sdiff key [key ...] 或者 sdiffstore | O(k), k 是多个集合元素个数和 |
# 6.4 开发提示
- sadd = Tagging(标签)
- spop/srandmember = Random item(生成随机数,比如抽奖)
- sadd + sinter = Social Graph(社交需求)
# 七:zset(有序集合)
它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着 「跳跃列表」 的数据结构。zset 中最后一个value被移除后,数据结构自动删除,内存被回收。
# 7.1 异同点
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 时间轴、消息队列等 |
集合 | 否 | 否 | 无 | 标签、社交等 |
有序集合 | 否 | 是 | 分值 | 排行榜系统、社交等 |
# 7.2 集合内操作
## 添加成员 zadd key score member [score member ...]
## 代表成功添加的成员个数
## Redis3.2 为zadd 命令添加了nx、xx、ch、incr四个选项:
## -nx:member必须不存在,才可以设置成功,用于添加
## -xx:member必须存在,才可以设置成功,用于更新。
## -ch:返回此次操作后,有序集合元素和分数发生变化的个数
## -incr:对score做增加,相当于zincrby
> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
(integer) 5
## 计算成员个数 zcard key
> zcard user:ranking
(integer) 5
## 计算某个成员的分数 zscore key member
> zscore user:ranking tom
"251"
> zscore user:ranking test
(nil)
## 计算成员的排名
## 分数从低到高返回,zrank key member
## 反之,zrevrank key member
> zrank user:ranking tom
(integer) 5
> zrevrank user:ranking tom
(integer) 0
## 删除成员 zrem key member [member ...]
> zrem user:ranking mike
(integer) 1
## 增加成员的分数 zincrby key increment member
> zincrby user:ranking 9 tom
"260"
## 返回指定排名范围的成员
## 分数从低到高:zrange key start end [withscores]
## 从高到底:zrevrange key start end [withscores]
## 如果加上withscores选项,同时会返回成员的分数
> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
## 返回指定分数范围的成员
## 分数从低到高返回:zrangebyscore key min max [withscores] [limit offset count]
## 反之:zrevrangebyscore key max min [withscores] [limit offset count]
> zrangebyscore user:ranking 200 tinf withscores
1) "frank"
2) "200"
3) "tim"
4) "220"
> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"
## 同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和 +inf分别代表无限小和无限大:
> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "260"
## 返回指定分数范围成员个数 zcount key min max
> zcount user:ranking 200 221
(integer) 2
## 删除指定排名内的升序元素 zremrangebyrank key start end
> zremrangebyrank user:ranking 0 2
(integer) 3
## 删除指定分数范围的成员 zremrangebyscore key min max
> zremrangebyscore user:ranking (250 +inf
(integer) 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 7.3 集合间操作
> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom
(integer) 6
> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
(integer) 4
## 交集 zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
## -destination:交集计算结果保存到这个键
## -numkeys:需要做交集计算键的个数
## -key[key...]:需要做交集计算的键
## -weights weight[weight...]:每个键的权重,在做交集计算时,每个键中 的每个member会将自己分数乘以这个权重,每个键的权重默认是1。
## -aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、 min(最小值)、max(最大值)做汇总,默认值是sum。
### 对user:ranking:1和user:ranking:2做交集,weights和aggregate使用了默认配置,可以看到目标键user:ranking:1_inter_2对分值做了sum操作
> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139
### 如果想让user:ranking:2的权重变为0.5,并且聚合效果使用max,可以执行如下操作:
> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
## 并集 zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
## 是计算user:ranking:1和user:ranking:2的并集,weights和aggregate使用了默认配置,可以看到目标键user:ranking:1_union_2对分值做了sum操作:
> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
(integer) 7
> zrange user:ranking:1_union_2 0 -1 withscores
1) "kris"
2) "1"
3) "james"
4) "8"
5) "mike"
6) "168"
7) "frank"
8) "200"
9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 7.4 时间复杂度
命 令 | 时间复杂度 |
---|---|
zadd key score member [score member ...] | O(k * log(n)),k 是添加成员的个数,n 是当前有序集合成员个数 |
zcard key | O(1) |
zscore key member | O(1) |
zrank key member zrevrank key member | O(log(n)),n 是当前有序集合成员个数 |
zrem key member [member ...] | O(k * log(n)),k 是删除成员的个数,n 是当前有序集合成员个数 |
zincrby key increment member | O(log(n)),n 是当前有序集合成员个数 |
zrange key start end [withscores] zrevrange key start end [withscores | O(log(n) + k),k 是要获取的成员个数,n 是当前有序集合成员个数 |
zrangebyscore key min max [withscores] zrevrangebyscore key max min [withscores] | O(log(n) + k),k 是要获取的成员个数,n 是当前有序集合成员个数 |
zcount | O(log(n)), n 是当前有序集合成员个数 |
zremrangebyrank key start end | O(log(n) + k),k 是要删除的成员个数,n 是当前有序集合成员个数 |
zremrangebyscore key min max | O(log(n) + k), k 是要删除的成员个数,n 是当前有序集合成员个数 |
zinterstore destination numkeys key [key ...] | O(n * k) + O(m * log(m)),n 是成员数最小的有序集合成员个数,k 是有序集合的个数,m 是结果集中成员个数 |
zunionstore destination numkeys key [key ...] | O(n) + O(m * log(m)),n 是所有有序集合成员个数和,m 是结果集中成员个数 |
# 7.5 跳跃表
zset 内部的排序功能是通过「跳跃列表」数据结构来实现的,它的结构非常特殊,也比较复杂。 因为 zset 要支持随机的插入和删除,所以它不好使用数组来表示。 我们需要这个链表按照 score 值进行排序。这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到,那该怎么办? 跳跃列表就是类似于这种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构。想想你老家在世界地图中的位置:亚洲->中国->安徽省->安庆市->枞阳县->汤沟镇->田间村->xxxx 号,也是这样一个类似的结构。
「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的这个元素,同时处于 L0、L1 和 L2 层,可以快速在不同层次之间进行「跳跃」。 定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢? 跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。 首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3 层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。 这还挺公平的,能不能进入中央不是靠拼爹,而是看运气。
# 八:容器型结构通用规则
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- create if not exists,如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的,Redis 就会自动创建一个,然后再 rpush 进去新元素。
- drop if no elements,如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一个元素,列表就消失了。
# 九:参考文献
- 《Redis深度历险:核心原理和应用实践 - 钱文品》
- 《Redis 开发与运维 - 付磊、张益军》
- 官方文档 (opens new window)