Redis 基础数据结构

7/15/2021 Redis

摘要

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"

1
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 来缓存。同样,取用户信息会经过一次反序列化的过程。

String类型

字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如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服务器都要顺序执行。
1
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"
1
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(哈希)

叫法可能是哈希、字典、关联数组。

hash字典

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同Java 的 HashMap 也是一致的,都是 "数组 + 链表" 二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。

hash字典

不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。

hash字典

渐进式 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
1
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 缓存用户信息

三种常用方式

  1. 原生字符串类型:每个属性一个键。
> set user:1:name tome
> set user:1:age 23
> set user:1:city beijing
1
2
3

优化:简单直观,每个属性都支持更新操作。

缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差, 所以此种方案一般不会在生产环境使用。

  1. 序列化字符串类型:将用户信息序列化后用一个键保存。
> set user:1 serialize(userInfo)
1

优化:简化编程,如果合理的使用序列化可以提高内存的使用效率。

缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。

  1. 哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。
> hmset user:1 name tom age 23 city beijing
1

优化:简单直观,如果使用合理可以减少内存空间的使用。

缺点:要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会消耗跟多内存。

# 五:list(列表)

列表 列表

列表(list) 类型是用来存储多个有序的字符串,一个列表最多可以存储 232 - 1 个元素。在 Redis 中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用 场景。

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n)。

当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

# 5.1 操作类型

命 令 操 作
添加 rpushlpushlinsert
lrangelindexllen
删除 lpoprpoplremltrim
修改 lset
阻塞操作 blpopbrpop

# 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命令的客户端可以获取到弹出的值
1
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完成
1
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"
1
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
1
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
1
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 号,也是这样一个类似的结构。

zset

「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的这个元素,同时处于 L0、L1 和 L2 层,可以快速在不同层次之间进行「跳跃」。 定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢? 跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。 首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3 层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。 这还挺公平的,能不能进入中央不是靠拼爹,而是看运气。

# 八:容器型结构通用规则

list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:

  1. create if not exists,如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的,Redis 就会自动创建一个,然后再 rpush 进去新元素。
  2. drop if no elements,如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一个元素,列表就消失了。

# 九:参考文献

最后更新: 12/18/2023, 3:49:59 PM