01-Redis 五大数据类型

五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如

Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis 键(key)的一些操作

注:字母大写小写都一样

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
# 查看所有的key
127.0.0.1:6379> keys *
1) "name"


# 判断某个key是否存在
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0


# 移动key到别的库
127.0.0.1:6379> move name 1 # 移动到1库
(integer) 1
127.0.0.1:6379> keys * # 在本库查不到name
(empty array)
127.0.0.1:6379> select 1 # 选择1库
OK
127.0.0.1:6379[1]> keys * # 查询到name
1) "name"


# 删除key
127.0.0.1:6379[1]> del name
(integer) 1
127.0.0.1:6379[1]> keys *
(empty array)


# 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
127.0.0.1:6379> EXPIRE name 10
(integer) 1

# 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
(empty list or set)


# 查看你的key是什么类型
127.0.0.1:6379> get name
"zhiyuan"
127.0.0.1:6379> type name
string

字符串 String

String 类型是 Redis 最基本的数据类型,你可以理解成 Memcached 一模一样的类型,一个 key 对应一个 value,value 其实不仅可以是 String,也可以是数字。

String 类型是二进制安全的,意思是 Redis 的 String 可以包含任何数据,比如jpg图片或者序列化的对象。

常规计数:微博数,粉丝数等。

常用指令

  • set、get、del、exists(是否存在)、append(追加)、strlen(获取长度)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> set key1 value1 # 设置值
OK
127.0.0.1:6379> get key1 # 获得key
"value1"
127.0.0.1:6379> del key1 # 删除key
(integer) 1
127.0.0.1:6379> keys * # 查看全部的key
(empty list or set)
127.0.0.1:6379> exists key1 # 确保 key1 不存在
(integer) 0
127.0.0.1:6379> append key1 "hello" # 对不存在的 key进行APPEND,等同于SET key1 "hello"
(integer) 5 # 字符长度
127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND
(integer) 10 # 长度从 5 个字符增加到 10 个字符
127.0.0.1:6379> get key1
"hello-2333"
127.0.0.1:6379> STRLEN key1 # # 获取字符串的长度
(integer) 10
  • incr、decr 一定要是数字才能进行加减,+1 和 -1。
  • incrby、decrby 将 key 中储存的数字加上或减去指定的数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> set views 0 # 设置浏览量为0
OK
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 1
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 2
127.0.0.1:6379> decr views # 浏览 - 1
(integer) 1

127.0.0.1:6379> incrby views 10 # +10
(integer) 11
127.0.0.1:6379> decrby views 10 # -10
(integer) 1
  • range [范围]
  • getrange 获取指定区间范围内的值,类似between…and的关系,从零到负一表示全部
1
2
3
4
5
6
127.0.0.1:6379> set key2 abcd123456 # 设置key2的值
OK
127.0.0.1:6379> getrange key2 0 -1 # 获得全部的值
"abcd123456"
127.0.0.1:6379> getrange key2 0 2 # 截取部分字符串
"abc"
  • setrange 设置指定区间范围内的值,格式是setrange key值 具体值
1
2
3
4
5
6
127.0.0.1:6379> get key2
"abcd123456"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换值
(integer) 10
127.0.0.1:6379> get key2
"axxd123456"
  • setex(set with expire) 设置过期时间
  • setnx(set if not exist) 如何 key 存在则不覆盖值,还是原来的值(分布式中常用)
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> setex key3 60 expire # 设置过期时间
OK
127.0.0.1:6379> ttl key3 # 查看剩余的时间
(integer) 55

127.0.0.1:6379> setnx mykey "redis" # 如果不存在就设置,成功返回1
(integer) 1
127.0.0.1:6379> setnx mykey "mongodb" # 如果值存在则不覆盖值,返回0
(integer) 0
127.0.0.1:6379> get mykey
"redis"
  • mset:同时设置一个或多个 key-value 对。
  • mget:返回所有(一个或多个) key 的值。 如果给定的 key 里面,有某个 key 不存在,则此 key 返回特殊值nil
  • msetnx:当所有 key 都成功设置,返回 1 。如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。相当于原子性操作,要么都成功,要么都不成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12
OK
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k10"

127.0.0.1:6379> mget k10 k11 k12 k13
1) "v10"
2) "v11"
3) "v12"
4) (nil)

127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作!
(integer) 0
127.0.0.1:6379> get key15
(nil)
  • 存储对象:set user:1 value(json数据)
1
2
3
4
5
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
  • getset:先get再set
1
2
3
4
5
6
7
8
127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis # 返回旧值 mongodb
"mongodb"
127.0.0.1:6379> get db
"redis"

列表 List

Redis 列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

  • 如果键不存在,创建新的链表
  • 如果键已存在,新增内容
  • 如果值全移除,对应的键也就消失了
  • 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。

list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。

常用指令

  • Lpush:将一个或多个值插入到列表头部。(LeftPush左)
  • Rpush:将一个或多个值插入到列表尾部。(RightPush右)
  • lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
    • 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
    • 使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> LPUSH list "one"
(integer) 1
127.0.0.1:6379> LPUSH list "two"
(integer) 2
127.0.0.1:6379> RPUSH list "right"
(integer) 3

127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> Lrange list 0 1
1) "two"
2) "one"
  • lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
  • rpop 移除列表的最后一个元素,返回值为移除的元素。
1
2
3
4
5
6
127.0.0.1:6379> Lpop list
"two"
127.0.0.1:6379> Rpop list
"right"
127.0.0.1:6379> Lrange list 0 -1
1) "one"
  • Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)
1
2
3
4
5
6
127.0.0.1:6379> Lindex list 1
(nil)
127.0.0.1:6379> Lindex list 0
"one"
127.0.0.1:6379> Lindex list -1
"one"
  • llen 用于返回列表的长度。
1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> Lpush list "one"
(integer) 1
127.0.0.1:6379> Lpush list "two"
(integer) 2
127.0.0.1:6379> Lpush list "three"
(integer) 3
127.0.0.1:6379> Llen list # 返回列表的长度
(integer) 3
  • lrem (lrem key count element)根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。如果有多个一样的lement,则删除列表最前面的的
1
2
3
4
5
127.0.0.1:6379> lrem list 1 "two"
(integer) 1
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "one"
  • ltrim key 对一个列表进行修剪(trim),只保留指定列表中区间内的元素,不在指定区间之内的元素都将被删除。
1
2
3
4
5
6
7
127.0.0.1:6379> RPUSH mylist "hello" "hello" "hello2" "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello2"
  • rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"
  • lset key index value : 将列表 key 下标为 index 的元素的值设置为 value 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key

127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 "new" # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
(error) ERR index out of range
  • linsert key before/after pivot value: 用于在列表的元素前或者后插入元素。将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。如果pivot有多个,则插入最前面的那个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> RPUSH mylist "Hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "world"
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "Hello"
2) "world"

127.0.0.1:6379> LINSERT mylist BEFORE "world" "There"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "Hello"
2) "There"
3) "world"

集合Set

Redis的Set是String类型的无序集合,它是通过HashTable实现的 !

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。

Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

常用指令

  • sadd 将一个或多个成员元素加入到集合中,不能重复
  • smembers 返回集合中的所有的成员。
  • sismember 命令判断成员元素是否是集合的成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "zhiyuan"
(integer) 1
127.0.0.1:6379> sadd myset "zhiyuan" # 重复值不插入 返回0
(integer) 0
127.0.0.1:6379> SMEMBERS myset #查看集合中所有成员
1) "zhiyuan"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello" #是否是此集合的成员 是反正1
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0
  • scard,获取集合里面的元素个数
1
2
127.0.0.1:6379> scard myset
(integer) 2
  • srem key value 用于移除集合中的一个或多个成员元素
1
2
3
4
127.0.0.1:6379> srem myset "zhiyuan"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
  • srandmember key 用于返回集合中随机元素。后面加上数字,则随机返回对应数量的成员,默认一个
1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> SMEMBERS myset
1) "zhiyuan"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "zhiyuan"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "zhiyuan"
2) "hello"
  • spop key [count] 用于移除指定 key 集合的随机元素,不填则默认一个。
1
2
3
4
5
6
7
8
9
127.0.0.1:6379> SMEMBERS myset
1) "zhiyuan"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset 2
1) "zhiyuan"
2) "hello"
  • smove SOURCE DESTINATION MEMBER, 将指定成员 member 元素从 source 集合移动到 destination 集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> sadd myset "hello" #myset 添加元素
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "zhiyuan"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2" #myset2 添加元素
(integer) 1
127.0.0.1:6379> smove myset myset2 "zhiyuan"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "zhiyuan"
2) "set2"
  • 数字集合类:
    • 差集: sdiff
    • 交集: sinter
    • 并集: sunion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6379> sadd key1 "a" # key1
(integer) 1
127.0.0.1:6379> sadd key1 "b"
(integer) 1
127.0.0.1:6379> sadd key1 "c"
(integer) 1
127.0.0.1:6379> sadd key2 "c" # key2
(integer) 1
127.0.0.1:6379> sadd key2 "d"
(integer) 1
127.0.0.1:6379> sadd key2 "e"
(integer) 1
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"

哈希Hash

Redis hash 是一个String类型的field和value的映射表,hash特别适合用于存储对象。类似Java里面的Map

常用指令

  • hset、hget 命令用于为哈希表中的字段赋值

  • hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段

    • Redis 4.0.0开始弃用HMSET,请使用HSET
  • hgetall 用于返回哈希表中,所有的字段和值。

  • hdel 用于删除哈希表 key 中的一个或多个指定字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
127.0.0.1:6379>  hset myhash field1 "zhiyuan"
(integer) 1
127.0.0.1:6379> hget myhash field1
"zhiyuan"

127.0.0.1:6379> HSET myhash field1 "Hello" field2 "World"
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"

127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
  • hlen 获取哈希表中字段的数量
1
2
3
4
5
6
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hlen myhash
(integer) 2
  • hexists 查看哈希表的指定字段是否存在
1
2
3
4
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
  • hkeys 获取哈希表中的所有域(field)
  • hvals 返回哈希表所有域(field)的值
1
2
3
4
5
6
127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello"
  • hincrby 为哈希表中的字段值加上指定增量值
1
2
3
4
5
6
7
8
127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
  • hsetnx 为哈希表中不存在的的字段赋值 ,存在则不赋值
1
2
3
4
5
6
127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1 # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0 # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"

有序集合Zset

Redis zset 和 set 一样,也是 String 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。之前 set 是 k1 v1 v2 v3,现在 zset 是 k1 score1 v1 score2 v2

Redis 正是通过分数来为集合中的成员进行从小到大的排序,zset 的成员是唯一的,但是分数(Score)却可以重复。

和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,比如一个存储全班同学成绩的 sorted set,其集合value可以是同学的学号,而 score 就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。

学生存储值.png

可以用 sorted set 来做带权重的队列,比如普通消息的 score 为 1,重要消息的 score 为 2,然后工作线程可以选择按 score 的倒序来获取工作任务。让重要的任务优先执行。

常用指令

  • zadd 将一个或多个成员元素及其分数值加入到有序集当中。
  • zrange 返回有序集中,指定区间内的成员
1
2
3
4
5
6
7
8
127.0.0.1:6379> zadd myset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myset 2 "two" 3 "three"
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
  • zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大) 次序排列。
  • ZREVRANGE 从大到小
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
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1

# Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "zhiyuan"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
1) "zhiyuan"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
1) "xiaohong"
2) "5000"
3) "xiaoming"
4) "2500"
5) "zhiyuan"
6) "500"
# 显示工资 <=2500的所有成员
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES
1) "zhiyuan"
2) "500"
3) "xiaoming"
4) "2500"
  • zrem 移除有序集中的一个或多个成员
1
2
3
4
5
6
7
8
9
127.0.0.1:6379> ZRANGE salary 0 -1
1) "zhiyuan"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> zrem salary zhiyuan
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"
  • zcard 命令用于计算集合中元素的数量
1
2
3
127.0.0.1:6379> zcard salary
(integer) 2
OK
  • zcount 计算有序集合中指定分数区间的成员数量。
1
2
3
4
5
6
7
8
127.0.0.1:6379> zadd myset 1 "hello"
(integer) 1
127.0.0.1:6379> zadd myset 2 "world" 3 "zhiyuan"
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
  • zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少
(integer) 0
127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三
(integer) 2
  • zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序
1
2
3
4
127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
(integer) 2
127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一
(integer) 0

代码实战

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
from redis import StrictRedis

redis_cli = StrictRedis(host="xx", port=3306, password="xx", db=0, decode_responses=True)

# 1)添加一个或多个元素
redis_cli.zadd("zset", {"mem1": 10, "mem2": 6, "mem3": 7})

# 2)查询所有元素列表
print(redis_cli.zrange("zset", 0, -1)) # ['mem2', 'mem3', 'mem1']
# 语法:redis_cli.zrange(name, start, end, desc=False, withscores=False, score_cast_func=float)
# start:起始位置,可从0开始;
# end:结束位置,为-1时可以取到结尾位置;
# desc:排序规则,默认按照score从小到大排序;
# withscores:是否获取元素的分数,默认只获取元素的值;
# score_cast_func:对分数进行数据转换的函数;

# 3)查询有序集合的总个数
num = redis_cli.zcard("zset")
print(num)

# 4)删除一个或多个元素(根据元素值) (删除命令中如果包含了不存在的元素,并不会影响命令的正常执行,不存在的元素将会被忽略。)
redis_cli.zrem("zset", "mem3", "mem1")
print(redis_cli.zrange("zset", 0, -1))

# 5)查询某元素的 score 值
redis_cli.zadd("zset1", {"m3": 5, "m1": 10, "m2": 3, "m4": 7})
score = redis_cli.zscore("zset1", "m1")
print(score)

# 6)按照分数范围获取name对应的有序集合的元素
mems = redis_cli.zrangebyscore("zset1", 2, 5)
print(mems)

# 语法:redis_db.zrangebyscore( name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
# min:score下限;
# max:score上限;

# 7)查询 score 区间内的元素个数
zcount1 = redis_cli.zcount("zset1", 2, 7)
print(zcount1)

# 8)给元素的 score 值增加值
m1_score = redis_cli.zscore("zset1", "m1")
redis_cli.zincrby("zset1", 10, "m1")
m1_score_after = redis_cli.zscore("zset1", "m1")
print(m1_score, m1_score_after)

# 9)查询某元素排名
redis_cli.zadd("zset2", {"me1": 3, "me2": 1, "me3": 0, "me4": 2})
rank = redis_cli.zrank("zset2", "me4")
print(rank) # 可以看出,排名是从 0 开始的,排名可以理解为元素排序后的下标值。

# 10)查询某元素倒序排名
des_rank = redis_cli.zrevrank("zset2", "me4")
print(des_rank)

# 11)根据排名删除元素
print(redis_cli.zrange("zset2", 0, -1))
redis_cli.zremrangebyrank("zset2", 1, 2)
print(redis_cli.zrange("zset2", 0, -1))

# 12)删除 score 区间内的元素
redis_cli.zadd("zset3", {"mm1": 100, "mm2": 80, "mm3": 90, "mm4": 70})
print(redis_cli.zrange("zset3", 0, -1))
redis_cli.zremrangebyscore("zset3", 20, 70)
print(redis_cli.zrange("zset3", 0, -1))

# 13)复制交集元素到新集合(''' 获取两个有序集合的交集并放入dest集合,如果遇到相同值不同分数,则按照aggregate进行操作 aggregate的值为: SUM MIN MAX''')
redis_cli.zadd("zset4", {"mm1": 100, "mm2": 80, "mm3": 90, "mm4": 70})
redis_cli.zadd("zset5", {"mm5": 100, "mm7": 80, "mm3": 90, "mm2": 70})

redis_cli.zinterstore("dest", ("zset4", "zset5"), aggregate="MIN")
print(redis_cli.zrange("dest", 0, -1))

# 14)复制并集元素到新集合
redis_cli.zunionstore("uni", ("zset4", "zset5"))
print(redis_cli.zrange("uni", 0, -1))

内部实现

有序集合是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。

ziplist

当数据比较少时,有序集合使用的是 ziplist 存储的,如下代码所示:

1
2
3
4
127.0.0.1:6379> zadd myzset 1 db 2 redis 3 mysql
(integer) 3
127.0.0.1:6379> object encoding myzset
"ziplist"

从结果可以看出,有序集合把 myset 键值对存储在 ziplist 结构中了。 有序集合使用 ziplist 格式存储必须满足以下两个条件:

  • 有序集合保存的元素个数要小于 128 个;
  • 有序集合保存的所有元素成员的长度都必须小于 64 字节。

如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。 接下来我们来测试以下,当有序集合中某个元素长度大于 64 字节时会发生什么情况? 代码如下:

1
2
3
4
5
6
7
8
127.0.0.1:6379> zadd zmaxleng 1.0 redis
(integer) 1
127.0.0.1:6379> object encoding zmaxleng
"ziplist"
127.0.0.1:6379> zadd zmaxleng 2.0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> object encoding zmaxleng
"skiplist"

通过以上代码可以看出,当有序集合保存的所有元素成员的长度大于 64 字节时,有序集合就会从 ziplist 转换成为 skiplist。

小贴士:可以通过配置文件中的 zset-max-ziplist-entries(默认 128)和 zset-max-ziplist-value(默认 64)来设置有序集合使用 ziplist 存储的临界值。

skiplist

skiplist 数据编码底层是使用 zset 结构实现的,而 zset 结构中包含了一个字典和一个跳跃表,源码如下:

1
2
3
4
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;

跳跃表的结构如下图所示:

有序集合-跳跃表.png

根据以上图片展示,当我们在跳跃表中查询值 32 时,执行流程如下:

  • 从最上层开始找,1 比 32 小,在当前层移动到下一个节点进行比较;
  • 7 比 32 小,当前层移动下一个节点比较,由于下一个节点指向 Null,所以以 7 为目标,移动到下一层继续向后比较;
  • 18 小于 32,继续向后移动查找,对比 77 大于 32,以 18 为目标,移动到下一层继续向后比较;
  • 对比 32 等于 32,值被顺利找到。

从上面的流程可以看出,跳跃表会想从最上层开始找起,依次向后查找,如果本层的节点大于要找的值,或者本层的节点为 Null 时,以上一个节点为目标,往下移一层继续向后查找并循环此流程,直到找到该节点并返回,如果对比到最后一个元素仍未找到,则返回 Null。

为什么是跳跃表?而非红黑树?

因为跳跃表的性能和红黑树基本相近,但却比红黑树更好实现,所有 Redis 的有序集合会选用跳跃表来实现存储。

再来一个解释

所有的元素都会在L0层的链表中,根据分数进行排序,同时会有一部分节点有机会被抽取到L1层中,作为一个稀疏索引,同样L1层中的索引也有一定机会被抽取到L2层中,组成一个更稀疏的索引列表。

这种跳跃表的实现,其实和二分查找的思路有点接近,只是一方面因为二分查找只能适用于数组,而无法适用于链表,所以为了让链表有二分查找类似的效率,就以空间换时间来达到目的。


01-Redis 五大数据类型
https://flepeng.github.io/041-Redis-41-核心概念-01-Redis-五大数据类型/
作者
Lepeng
发布于
2021年1月1日
许可协议