Redis五种值类型

  • 对于Redis来说,每一种数据结构都有着自己的内部编码,而且是多种实现的,这样Redis会在合适的场景选择合适的内部编码,通过OBJECT ENCODING [key]可以参看指定key的内部编码。

  • **这样做的好处: **

    • 改进内部编码,对外的数据结构和命令没有影响,对用户提供黑箱模型。
    • 多种内部编码可在不同场景下发挥各自的优势。如: ziplist比较节约内存,但是元素比较多的时候,性能会有所下降,此时Redis会将编码自动转换为linkedlist,性能会有所改善。

string类型

  • String类型是我们最常用的一种value数据类型,在上文中我们看到string的encoding有三种类型,即对应的三种数据结构实现(int,embstr,raw)。

  • embstr和raw都是SDS存储(上文已介绍),但embstr只需要一次内存分配空间,而raw需要两次。

  • int编码字符串对象和embstr编码字符串对象在一定条件下会转化为raw编码字符串对象。

  • 如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    127.0.0.1:6379> set test 1
    OK
    127.0.0.1:6379> object encoding test
    "int"
    127.0.0.1:6379> append test 1
    (integer) 2
    127.0.0.1:6379> object encoding test
    "raw"

    127.0.0.1:6379> set test1 aa
    OK
    127.0.0.1:6379> object encoding test1
    "embstr"
    127.0.0.1:6379> append test1 aa
    (integer) 4
    127.0.0.1:6379> object encoding test1
    "raw"
  • 当你存储的是中文时,由于redis是二进制安全的,所以在不同编码下数据的长度是不同的,有兴趣的可以看看这篇文章redis是二进制安全

命令 描述
set key value 赋值
get key 取值
getset key value 取值并赋值
mset k1 v1 k2 v2 赋值多个值
mget k1 k2 获取多个值
del key 删除
incr key 数值增1
incrby key increment 设置增长数
decr key 减少1
decrby key increment 减少整数值
append key value 追加数值
strlen key 获取长度

注意

  • incr 将指定的 key 的 value 值递增 1,如果这个值不存在,就将这个值设置初始值为 0,并且 +1.

  • decr 是将指定的 key 的 value 值递减 1,如果这个值不存在,就将这个值设置初始化为 0,并且 -1.

  • incrby是将指定的 key 的 value 值递增 指定的大学,如果值不存在,则设置为 0,并且加指定的值

  • decrby将指定的 key 的值 减去 指定的值 如果值不存在,则设置为 0,并且减指定的值

  • append指定的 key 的 value 后面拼接给定的值,如果给定的 key 不存在,则创建这个 key 并且这个key 的值为后面给定的值

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> get num
    (nil)
    127.0.0.1:6379> incr num
    (integer) 1
    127.0.0.1:6379> get num
    "1"
  • 如果这个值不能转成数值,不能增减,则抛出异常

    1
    2
    3
    4
    127.0.0.1:6379> get num
    "ab"
    127.0.0.1:6379> incr num
    (error) ERR value is not an integer or out of range

字符串命令

  • String数据类型中比较常用的就是作为字符串使用,可以作为缓存存储一些信息,比如登录用户的一些信息等。如果对命令不太熟悉的前提下我们在Linux系统中可以在连接redis客户端的前提下使用help @String 来获取各个命令的作用和语法或者在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
    ## ex(秒) 和 px(毫秒) 表示key的过期时间  nx 表示不存在才操作 xx表示存在才操作
    SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
    summary: Set the string value of a key
    since: 1.0.0

    ## set 多个 key value
    MSET key value [key value ...]
    summary: Set multiple keys to multiple values
    since: 1.0.1

    ## 在原value后追加字符串
    APPEND key value
    summary: Append a value to a key
    since: 2.0.0

    ## 在value的某个偏移量上重写新的value值
    SETRANGE key offset value
    summary: Overwrite part of a string at key starting at the specified offset
    since: 2.2.0

    ## 获取value的值长度
    STRLEN key
    summary: Get the length of the value stored in a key
    since: 2.2.0

    ## 获取旧值并设置新值
    GETSET key value
    summary: Set the string value of a key and return its old value
    since: 1.0.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
    ## value + 1
    INCR key
    summary: Increment the integer value of a key by one
    since: 1.0.0

    ## value - 1
    DECR key
    summary: Decrement the integer value of a key by one
    since: 1.0.0

    ## value + increment
    INCRBY key increment
    summary: Increment the integer value of a key by the given amount
    since: 1.0.0

    ## value - increment
    DECRBY key decrement
    summary: Decrement the integer value of a key by the given number
    since: 1.0.0

    ## value + increment (浮点型)
    INCRBYFLOAT key increment
    summary: Increment the float value of a key by the given amount
    since: 2.6.0

bitmap

  • bitmap不是实际的数据类型,而是在String类型上定义的一组面向位的操作。根据官方给出的文档提示,位图最大的优势就是在存储时能节省大量空间。其常用的应用场景包括统计活跃用户或用户登录天数等。

    1
    2
    3
    4
    5
    6
    7
    8
    127.0.0.1:6379> set hello big
    OK
    127.0.0.1:6379> getbit hello 0
    (integer) 0
    127.0.0.1:6379> getbit hello 1
    (integer) 1
    127.0.0.1:6379> getbit hello 2
    (integer) 1

命令

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
## 统计key从start到end的被设置的为1的bit数  start end 是字节索引
BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0

## 设置或者清空key的value(字符串)在offset处的bit值 offset 为二进制的索引
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0

## 返回位图中第一个值为 bit(0或1) 的二进制位的位置 start end 是字节索引
BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7

## 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上
## operation 可以是 AND(并)、OR(或)、NOT(非)、XOR(异或)这四种操作中的任意一种
## 当key不同长度 较短的那个key默认以0补位
BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0

## 参考 http://redis.cn/commands/bitfield.html
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
summary: Perform arbitrary bitfield integer operations on strings
since: 3.2.0

简单演示

  • 这里对bitmap进行简单演示:

    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
    redis> BITCOUNT bits
    (integer) 0

    redis> SETBIT bits 0 1 # 0001
    (integer) 0

    redis> BITCOUNT bits
    (integer) 1

    redis> SETBIT bits 3 1 # 1001
    (integer) 0

    redis> BITCOUNT bits
    (integer) 2

    127.0.0.1:6379> setbit bit 1 1
    (integer) 0
    127.0.0.1:6379> setbit bit 7 1
    (integer) 0

    # 分别在key为bit的二进制索引位置1和二进制位置7设置为1,这样实际上就是1000 0010 根据ASCII编码该值就是A。
    127.0.0.1:6379> get bit
    "A"

    # 表示有2个位被设置为1
    127.0.0.1:6379> bitcount bit
    (integer) 2
  • 当添加start 和 end参数时 该值表示字节索引,从右到左

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    127.0.0.1:6379> setbit bit 16 1
    (integer) 0
    127.0.0.1:6379> get bit
    "A\x00\x80"
    # 在第二进制索引位置16设置为1 即 10000000 01000001
    # 当统计第1个字节到被设置为1的数量时则是2个
    127.0.0.1:6379> bitcount bit 0 1
    (integer) 2
    # 当不添加则表示所有字节上述实例就是3个。
    127.0.0.1:6379> bitcount bit
    (integer) 3
  • 首先在二进制索引5设置为1 即 0001 0000 ,

    • 然后分别获取第一个为0和第一个为1的索引位置;
    • 然后再二进制索引8设置1 即 10010000 ,
    • 当设置start 和 end时 该值表示字节索引
    • 结果即为 5 和 8。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 1 0000
    127.0.0.1:6379> setbit test 5 1
    (integer) 0
    # 获取第一个为1的索引位置;
    127.0.0.1:6379> bitpos test 1
    (integer) 5
    # 获取第一个为0的索引位置;这里显示的都是0)
    127.0.0.1:6379> bitpos test 0
    (integer) 0
    127.0.0.1:6379> setbit test 8 1
    (integer) 0
    # 开始索引分别从第二个字节开始
    127.0.0.1:6379> bitpos test 1 1
    (integer) 8
    # 开始索引分别从第一个字节 10010000
    127.0.0.1:6379> bitpos test 1 0
    (integer) 5

应用场景

  • 用途我们在上面介绍时分别都有提及到,不过作为字符串和数值的用途比较常见,此处不做撰述。我们来仔细看看作为bitmap时的应用场景如何使用。

  • 首先就是统计用户登录天数。假设你用关系型数据库去处理,每个用户登录一天你有相关的登录记录表去录,如果你有1千万的活跃用户每天都在登录你的系统那么你每个人都去记录一条数据,一年365天光数据存储都占用了非常多的资源。如果是使用bitmap你可以这样做,登录人的id作为key,每一位表示一天,即如下表。(1表示登录过 ,0表示未登录)

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> setbit user01 1 1
    127.0.0.1:6379> setbit user01 2 1
    127.0.0.1:6379> setbit user01 16 1
    # 出用户登录天数
    127.0.0.1:6379> bitcount user01
    (integer) 3
用户\天数 1 2 3 4 5 364 365
userId1 0 1 1 0 1 0 1
userId2 1 1 0 1 1 1 0
userId99 1 1 1 1 0 1 1
  • 当用户登录使用setbit userId offset(哪一天)1 就可以记录该用户当天登录,然后使用bitcount 即可以得出用户登录天数,或者你需要获取某个时间周期内的登录天数只要在bitcount加上startend 即可,最最重要的是它占用的空间非常少比起关系型数据库少太多。

  • 其次就是活跃用户统计,比如你要统计日🔥用户,那么你也可以使用bitmap来做,思路和上面差不多,只不过现在是把日期作为key,用户的id映射为位,即如下表。(1表示活跃,0表示不活跃)

日期\用户 1 2 3 4 5 364 365
20200818 0 1 1 0 1 0 1
20200819 1 1 0 1 0 1 1
  • 当用户活跃时,使用setbit 20200818 offset(用户id映射的位)1 即表示用户活跃,然后使用bitcount即可以得出当天的活跃用户;如果你需要对某个时间周期内进行统计且需要去重则可使用bitop or destkey 时间key1 时间key... 然后再对destKey进行bitcount 即可。

假设每个用户存储需要20B空间, 那么1亿个用户所占用的空间就是 20 * 100000000 = 20000000000B ≈ 18G,看起来也是个不小的内存消耗;如果你对精确度不是很高的要求那么可以考虑Redis提供的Hypeloglog,可以参考下 Redis HyperLogLog介绍及应用

hash类型

  • 散列类型,支持扩展key,提供字段属性,字段和字段值之间实现映射。

  • 使用场景:存储对象信息(用户,商品信息等)

  • 字段值只支持string类型。

    命令 描述
    HSET key field value 设置值,,0代表更新,1代表插入
    HMSET key field value [field value …] 一次设置多个值
    HSETNX key field value 设置字段的值,如果字段已存在,该操作无效果。
    hget key field 获取值
    hmget key field [field …] 获取多个值
    hgetall key 获取所有的值
    hdel key field [field …] 删除一个属性或多个属性
    del key 删除整个 key
    HINCRBY key field increment 增加数字,不存在给默认值0
    hexists key field 返回hash里面field是否存在
    hlen key 获取某个 key 存在的属性个数
    hkeys key 获取所有属性名称
    hvals key 获取属性所有的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    192.168.2.101:6379> HSET user:001 name zs
    (integer) 1
    192.168.2.101:6379> HSET user:001 name ls
    (integer) 0
    192.168.2.101:6379> HGET user:001 name
    "ls"
    192.168.2.101:6379> HMSET user:001 name zs age 12
    OK
    192.168.2.101:6379> HMGET user:001 name age
    1) "zs"
    2) "12"
    有该key则不赋值,没有则赋值
    192.168.2.101:6379> HSETNX user:001 name ww
    • Hash数据类型就是 key-value 键值对,对应的encoding可以是ziplist或者hashtable。 Hash对象只有同时满足下面两个条件时,才会使用ziplist(压缩列表):
      1. 哈希中元素数量小于512个;
      2. 哈希中所有键值对的键和值字符串长度都小于64字节。这样会非常节约内存。
      3. 其应用场景包括点赞,收藏等
    • 对其数据结构有兴趣的仍然可以了解Redis为何这么快–数据存储角度的Hash篇。

常用的命令如下:

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
## 设置多个键值对
HSET key field value [field value ...]
summary: Set the string value of a hash field
since: 2.0.0

## 设置键值对 不存在才设置
HSETNX key field value
summary: Set the value of a hash field, only if the field does not exist
since: 2.0.0

## 设置多个键值对 4.0被弃用
HMSET key field value [field value ...]
summary: Set multiple hash fields to multiple values
since: 2.0.0

## 获取key下某个键对应的值
HGET key field
summary: Get the value of a hash field
since: 2.0.0

## 获取key下多个键对应的值
HMGET key field [field ...]
summary: Get the values of all the given hash fields
since: 2.0.0

## 获取所有key-value键值对
HGETALL key
summary: Get all the fields and values in a hash
since: 2.0.0

## 获取所有的键
HKEYS key
summary: Get all the fields in a hash
since: 2.0.0

## 获取所有的值
HVALS key
summary: Get all the values in a hash
since: 2.0.0

## hash下键所对应的值加 increment值
HINCRBY key field increment
summary: Increment the integer value of a hash field by the given number
since: 2.0.0

## hash下键所对应的值加 increment值(浮点)
HINCRBYFLOAT key field increment
summary: Increment the float value of a hash field by the given amount
since: 2.6.0

## 迭代hash下匹配pattern的键 count指定个数 cursor表示从第几个开始
HSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate hash fields and associated values
since: 2.8.0

应用场景

Hash的数据类型可以用作点赞,收藏,阅读数等。比如你有一个帖子,你需要为它统计点赞,收藏,阅读。如果你使用string类型的话 你肯定是要设置3个key分别对应点赞,收藏,阅读,这样在无形中也浪费了存储空间;但是如果你使用hash的话 一个key就搞定,使用hset 帖子id 点赞数 0 收藏数 0 阅读 0 ,当每增加一个变化时 使用HINCRBY key field increment 对field进行数值计算就可以解决。

其次就是当你缓存一些数据时,比如说用户如果你使用string缓存,那么必然是个json对象,当你只需要某个值时你需要把整个对象取出来然后处理,但hash就可以直接取出对应的值。

list类型

  • 在 redis 中。list 是按照插入排序的一个字符串的链表,和数据结构中链表是一样的,

  • 可以再头部(左侧)或者尾部(右侧)增加或者删除元素。

  • 按照插入顺序排序

    • ArrayList 使用数组方式
    • LinkdList 使用双向链接方式
    • 双向链表增加数据
    • 双向链表删除数据
  • 使用场景:用于商品评论

常用命令

命令 描述
lpush key value [value …] 左端添加,如果不存在,则创建一个 list,如果存在,则添加进 key 的 list
LPUSHX key value 只有当 key 已经存在并且存着一个 list 的时候,在这个 key 下面的 list 的头部插入 value。 与 LPUSH 相反,当 key 不存在的时候不会进行任何操作。
rpush key value 右端添加
rpushx key value 将一个值插入到已存在的列表w尾部,如果不存在,则不会插入,返回 (integer) 0
lrange key [start] [end] 查看列表,index:从0开始,-1代表最后一位
lpop key 左边弹出, 如果存在,返回头部的第一个元素,如果不存在,返回 nil.弹出以后就没有这个元素
rpop key 右边弹出
llen key 获取列表元素个数
lrem key count value 从列表中删除元素
LINDEX key index 查看指定索引的值
ltrim key start stop 修剪片段,list 就会只包含指定范围的指定元素,start 和 stop 都是由0开始计数的
LINSERT key BEFORE/AFTER pivot value 把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。当 key 不存在时,这个list会被看作是空list,任何操作都不会发生.当 key 存在,但保存的不是一个list的时候,会返回error。
RPOPLPUSH source destination timeout 原子性地返回并移除存储在 source 的列表的最后一个元素(列表尾部元素), 并把该元素放入存储在 destination 的列表的第一个元素位置(列表头部)
BRPOPLPUSH source destination timeout BRPOPLPUSHRPOPLPUSH 的阻塞版本。当 source 包含元素的时候,这个命令表现得跟 RPOPLPUSH 一模一样。 当 source 是空的时候,Redis将会阻塞这个连接,直到另一个客户端 push 元素进入或者达到 timeout 时限。 timeout 为 0 能用于无限期阻塞客户端。
  • lrem 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:

    • count > 0: 从头往尾移除值为 value 的元素。

    • count < 0: 从尾往头移除值为 value 的元素。

    • count = 0: 移除所有值为 value 的元素。

    • 比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。

    • 需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 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
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> lpush mylist 1 2 3
(integer) 6
127.0.0.1:6379> lrange 0 5
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange mylist 0 -1
1) "3"
2) "2"
3) "1"
4) "c"
5) "b"
6) "a"
127.0.0.1:6379> lpop mylist
"3"
127.0.0.1:6379> lrange mylist 0 5
1) "2"
2) "1"
3) "c"
4) "b"
5) "a"
127.0.0.1:6379> rpop mylist
"a"
127.0.0.1:6379> lrange mylist 0 5
1) "2"
2) "1"
3) "c"
4) "b"
127.0.0.1:6379> llen mylist
(integer) 4
127.0.0.1:6379> lrem mylist 2 c
(integer) 1
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "1"
3) "b"
127.0.0.1:6379> lpush mylist 1
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "1"
4) "b"
127.0.0.1:6379> lrem mylist 2 1
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "b"
127.0.0.1:6379> lindex mylist 1
"b"
# 裁剪片段
127.0.0.1:6379> ltrim mylist 0 0
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
127.0.0.1:6379> rpush mylist a b c d
# 插入指定位置
127.0.0.1:6379> linsert mylist after b 3
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "a"
3) "b"
4) "3"
5) "c"
6) "d"
127.0.0.1:6379> rpoplpush mylist list2
"d"
127.0.0.1:6379> lrange list2 0 -1
1) "d"
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "a"
3) "b"
4) "3"
5) "c"

ListAPI

List数据类型就是有序(插入顺序)元素的序列,其对应的encoding为上文表格中的ziplist和linkelist。Redis中的列表支持两端插入和弹出,并且可指定位置获取元素,可以充当数组,队列,栈等。因此,其应用场景包括微博的时间轴列表,阻塞队列等。

这里不对底层数据结构进行分析,若想了解zipList和linkedList可参考 Redis为何这么快–数据存储角度的List篇。

  • 常用的命令如下:
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
## 获取列表index的值(从左开始)
LINDEX key index
summary: Get an element from a list by its index
since: 1.0.0

## 列表插入某个元素
LINSERT key BEFORE|AFTER pivot value
summary: Insert an element before or after another element in a list
since: 2.2.0

## 获取列表长度
LLEN key
summary: Get the length of a list
since: 1.0.0

## 从列表左侧弹出(删除并获取)
LPOP key
summary: Remove and get the first element in a list
since: 1.0.0

## 从列表左侧插入多个元素
LPUSH key value [value ...]
summary: Prepend one or multiple values to a list
since: 1.0.0

## 当列表存在时 插入元素
LPUSHX key value
summary: Prepend a value to a list, only if the list exists
since: 2.2.0

## 获取列表指定范围的元素值 start和end为元素索引
LRANGE key start stop
summary: Get a range of elements from a list
since: 1.0.0

## 移除列表count数量的value元素 (从左开始) count为0表示全删
LREM key count value
summary: Remove elements from a list
since: 1.0.0

## 设置列表index下的值(从左开始)
LSET key index value
summary: Set the value of an element in a list by its index
since: 1.0.0

## 截取列表指定范围的list start和end为元素索引
LTRIM key start stop
summary: Trim a list to the specified range
since: 1.0.0

## 右侧弹出 左侧插入 source 和 destination 都为列表 可为同一个
RPOPLPUSH source destination
summary: Remove the last element in a list, prepend it to another list and return it
since: 1.2.0
  • 以上罗列的是从list的左侧开始,同样的redis也提供了从list的右侧开始。redis同样提供了阻塞式的一些命令。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 阻塞获取列表元素并删除(左侧) 可指定多个列表 同时可设置timeout时间(单位秒)
BLPOP key [key ...] timeout
summary: Remove and get the first element in a list, or block until one is available
since: 2.0.0

## 阻塞获取列表元素并删除(右侧) 可指定多个列表 同时可设置timeout时间(单位秒)
BRPOP key [key ...] timeout
summary: Remove and get the last element in a list, or block until one is available
since: 2.0.0

## RPOPLPUSH source destination 阻塞版本 timeout时间(单位秒)
BRPOPLPUSH source destination timeout
summary: Pop a value from a list, push it to another list and return it; or block until one is available
since: 2.2.0

这里要注意的是当有多个连接对同一个list进行阻塞监听时,redis的处理方法是维护了一个阻塞队列,提供先阻塞先服务,当多个阻塞同时满足唤醒条件时,先阻塞的优先唤醒。

应用场景

  • 上面我们说List可以充当栈,队列。栈的特征是先进后出,那么在使用Redis的同一个方向命令(lpush lpop)时就可以实现栈的特点;队列的特征是先进先出,那么同样的在使用Redis的反向命令(lpush rpoprpush lpop )时就可以实现队列的特点。

  • 微博的时间轴列表也比较简单,每次新增一个微博时只需要lpush 进去即可,通过lrange 来获取最新的微博消息。

  • 阻塞队列的实现就需要使用blpopbrpop ,首先一定要先调用blpop 监听,然后再另外一个客户端进行lpush 操作,如果先lpushblpop 那么先lpush 的数据就不会被监听到。

set

  • 无序,不重复
  • Set是String的无序去重排列,对应的encoding是intset(整数集合)或hashtable。intset(整数集合)当一个集合只含有整数,并且元素不多时会使用intset(整数集合)作为Set集合对象的底层实现。其应用场景包括共同关注,随机事件等

常用的命令

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
# 集合内操作
## 添加元素
SADD key member [member ...]
summary: Add one or more members to a set
since: 1.0.0

## 集合大小
SCARD key
summary: Get the number of members in a set
since: 1.0.0

## 随机获取集合内count个元素
## count为正 取出一个去重的结果集
## count为负 取出一个不去重的结果集
##
SRANDMEMBER key [count]
summary: Get one or multiple random members from a set
since: 1.0.0

## 随机弹出(获取并删除)集合内count个元素
SPOP key [count]
summary: Remove and return one or multiple random members from a set
since: 1.0.0

## 取出所有元素
SMEMBERS key
summary: Get all the members in a set
since: 1.0.0

## 判断member是否在集合中
SISMEMBER key member
summary: Determine if a given value is a member of a set
since: 1.0.0

## 移除元素
SREM key member [member ...]
summary: Remove one or more members from a set
since: 1.0.0

# 集合间操作
## 集合间差集
SDIFF key [key ...]
summary: Subtract multiple sets
since: 1.0.0

## 集合间差集并存储到destination
SDIFFSTORE destination key [key ...]
summary: Subtract multiple sets and store the resulting set in a key
since: 1.0.0

## 迭代集合下匹配pattern的键 count指定个数 cursor表示从第几个开始
SSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate Set elements
since: 2.8.0

## 集合间交集
SINTER key [key ...]
summary: Intersect multiple sets
since: 1.0.0

## 集合间交集并存储到destination
SINTERSTORE destination key [key ...]
summary: Intersect multiple sets and store the resulting set in a key
since: 1.0.0

## 集合间并集
SUNION key [key ...]
summary: Add multiple sets
since: 1.0.0

## 集合间并集并存储到destination
SUNIONSTORE destination key [key ...]
summary: Add multiple sets and store the resulting set in a key
since: 1.0.0

## 把member从source集合移动到destination集合
SMOVE source destination member
summary: Move a member from one set to another
since: 1.0.0

应用场景

  • Set的应用场景包括随机事件,公共好友。这其实就对应了Set的集合内命令和集合间命令。集合内命令的spop keysrandmember key count 就可以应对随机事件,比如抽奖系统,key为奖品,member为用户id,如果希望中奖的人不会再中就可以使用spop,如果希望重复中奖就可以使用srandmember ;集合间命令获取交集sinter 就可以应对共同好友,共同关注,key为用户,member分别为用户的关注。
命令 描述
SADD key member [member …] 添加一个或多个指定的member元素到集合的 key中
spop key 删除元素
SMEMBERS key 查看所有元素
SMEMBER key member 判断是否存在,是集合key的成员,则返回1,集合key不存在,则返回0
scard key 长度
SUNION k1 k2 并集
SINTER k1 k2 交集
SDIFF k1 k2 差集
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
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> spop set1
"a"

127.0.0.1:6379> sadd s1 a b c
(integer) 3
127.0.0.1:6379> sadd s2 d b c
(integer) 3
# 并集
127.0.0.1:6379> sunion s1 s2
1) "a"
2) "b"
3) "c"
4) "d"

# 交集
127.0.0.1:6379> sinter s1 s2
1) "b"
2) "c"

# 差集
127.0.0.1:6379> sdiff s1 s2
1) "a"
127.0.0.1:6379> sdiff s2 s1
1) "d"

# 长度
127.0.0.1:6379> scard s1
(integer) 3

sortedset

  • 又名zset,唯一且可排序。

  • 为每个元素设置分数,根据分数实现排序。

  • 应用场景: 排行榜

  • Sorted Set是在Set上添加一个分数(score)维度的集合,通过分数来为集合中的元素进行排序。相比于Set来说Sorted Set可理解为一个有序无重复的序列,其encoding为ziplist或skiplist。当一个有序集合的元素数量比较多或者成员是比较长的字符串时,Redis就使用skiplist(跳跃表)作为ZSet对象的底层实现。其应用场景主要包括排行榜等。

常用命令如下

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
## 添加集合元素 NX 不更新 只添加 XX只更新 不添加 CH 返回值为修改总数(默认是新增总数) INCR 分值添加
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
summary: Add one or more members to a sorted set, or update its score if it already exists
since: 1.2.0

## 删除集合元素
ZREM key member [member ...]
summary: Remove one or more members from a sorted set
since: 1.2.0

## 指定分数区间删除集合元素
ZREMRANGEBYSCORE key min max
summary: Remove all members in a sorted set within the given scores
since: 1.2.0

## 指定成员区间删除集合元素
ZREMRANGEBYLEX key min max
summary: Remove all members in a sorted set between the given lexicographical range
since: 2.8.9

## 指定排名区间删除集合元素
ZREMRANGEBYRANK key start stop
summary: Remove all members in a sorted set within the given indexes
since: 2.0.0

## 获取集合元素分数
ZSCORE key member
summary: Get the score associated with the given member in a sorted set
since: 1.2.0

## 增加集合元素分数
ZINCRBY key increment member
summary: Increment the score of a member in a sorted set
since: 1.2.0

## 返回集合个数
ZCARD key
summary: Get the number of members in a sorted set
since: 1.2.0

## 返回元素排名 默认升序
ZRANK key member
summary: Determine the index of a member in a sorted set
since: 2.0.0

## 返回元素排名 降序
ZREVRANK key member
summary: Determine the index of a member in a sorted set, with scores ordered from high to low
since: 2.0.0

## 指定元素索引返回集合值 WITHSCORES表示是否带分值 升序
ZRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index
since: 1.2.0

## 根据元素分值大小返回集合值 WITHSCORES表示是否带分值 LIMIT 指定索引和数量 升序
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score
since: 1.0.5

## 指定元素索引返回集合值 WITHSCORES表示是否带分值 降序
ZREVRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index, with scores ordered from high to low
since: 1.2.0

## 指定元素分值大小返回集合值 WITHSCORES表示是否带分值 LIMIT 指定索引和数量 降序
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score, with scores ordered from high to low
since: 2.2.0

## 指定元素区间返回集合值 LIMIT 指定索引和数量 降序
ZREVRANGEBYLEX key max min [LIMIT offset count]
summary: Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
since: 2.8.9

## 根据分数区间返回集合元素数量
ZCOUNT key min max
summary: Count the members in a sorted set with scores within the given values
since: 2.0.0

## 返回成员区间返回集合元素数量
ZLEXCOUNT key min max
summary: Count the number of members in a sorted set between a given lexicographical range
since: 2.8.9

## 返回集合下成员在min到max的数量 Limit 指定索引和数量
ZRANGEBYLEX key min max [LIMIT offset count]
summary: Return a range of members in a sorted set, by lexicographical range
since: 2.8.9

## 弹出集合中分数最低元素
ZPOPMIN key [count]
summary: Remove and return members with the lowest scores in a sorted set
since: 5.0.0

## 弹出集合中分数最高元素
ZPOPMAX key [count]
summary: Remove and return members with the highest scores in a sorted set
since: 5.0.0

## 集合间并集 WEIGHTS 使用聚合函数前的乘法因子 AGGREGATE 指定聚合方式
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Add multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0

## 集合间交集 WEIGHTS 使用聚合函数前的乘法因子 AGGREGATE 指定聚合方式
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Intersect multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0

## 按照参数中key的顺序,返回第一个非空key中分数最大的成员和对应的分数 timeout为阻塞时间 单位秒
BZPOPMAX key [key ...] timeout
summary: Remove and return the member with the highest score from one or more sorted sets, or block until one is available
since: 5.0.0

## 按照参数中key的顺序,返回第一个非空key中分数最小的成员和对应的分数 timeout为阻塞时间 单位秒
BZPOPMIN key [key ...] timeout
summary: Remove and return the member with the lowest score from one or more sorted sets, or block until one is available
since: 5.0.0

## 迭代集合下匹配pattern的键 count指定个数 cursor表示从第几个开始
ZSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate sorted sets elements and associated scores
since: 2.8.0

应用场景

  • Sorted Set最常见的应用场景便是排行榜,使用zadd key score member 就可以实现自动排序,默认按照从小到大排序,如果你希望倒序那就使用zrevrangebyscore 根据分数倒序即可,想获取某一个元素的排名使用zrank ,但要记住这仍是正序的,因为sorted set物理内存不随命令变化,想要倒序的排名使用zrevrank
命令 描述
zadd key score member [score member … ] 添加元素
ZSCORE key member 查看排序的分数
ZREM key score member [score member … ] 删除元素
ZRANGE key start stop [WITHSCORES] startstop都是基于零的索引,顺序输出[start,stop]包含的区间
ZREVRANGE key start stop [WITHSCORES] 倒序输出
ZRANK key member 返回有序集key中成员member的排名,其中有序集成员按score值递增(从小到大)顺序排列。排名以0为底,也就是说,score值最小的成员排名为0
ZREVRANK key member
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 如果M是常量(比如,用limit总是请求前10个元素),你可以认为是O(log(N))。返回key的有序集合中的分数在min和max之间的所有元素(包括分数等于max或者min的元素)。元素被认为是从低分到高分排序的。min和max可以是-inf和+inf,这样一来,你就可以在不知道有序集的最低和最高score值的情况下,使用ZRANGEBYSCORE这类命令。
ZINCRBY key increment member 增加某个成员分数
zcard key 查看元素个数
ZCOUNT key min max 统计范围内的元素个数
ZREMRANGEBYRANK key start stop 根据排名删除范围内元素
ZREMRANGEBYSCORE key start stop 根据分数删除范围内元素
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
# 添加元素
127.0.0.1:6379> ZADD zset1 80 zs 90 ls 70 ww
(integer) 3
# 添加元素和分数,如果该元素存在则修改分数并返回0;如果不存在返回1.
127.0.0.1:6379> zadd zset1 75 ww
(integer) 0
# 查看排序的分数
127.0.0.1:6379> zscore zset1 ww
"75"
127.0.0.1:6379> zrange zset1 0 -1 withscores
1) "ww"
2) "75"
3) "zs"
4) "80"
5) "ls"
6) "90"
127.0.0.1:6379> zrevrange zset1 0 -1
1) "ww"
2) "zs"
3) "ls"
# 查询排名
127.0.0.1:6379> ZRANK zset1 ww
(integer) 0
# 倒序查询
127.0.0.1:6379> zrevrange zset1 0 -1 withscores
1) "ls"
2) "90"
3) "zs"
4) "80"
5) "ww"
6) "75"
127.0.0.1:6379> ZrevRANK zset1 ww
(integer) 2
# 指定分数范围查找,默认闭区间
127.0.0.1:6379> ZRANGEBYSCORE zset1 80 90 withscores
1) "zs"
2) "80"
3) "ls"
4) "90"
# 通过给参数前增加(符号来使用可选的开区间
# 返回所有符合条件80 < score <= 90的成员
127.0.0.1:6379> ZRANGEBYSCORE zset1 (80 90 withscores
1) "ls"
2) "90"
# min和max可以是-inf和+inf
127.0.0.1:6379> ZRANGEBYSCORE zset1 -inf +inf withscores
1) "ww"
2) "75"
3) "zs"
4) "80"
5) "ls"
6) "90"
# limit 1 1 从1开始,查找一个
127.0.0.1:6379> ZRANGEBYSCORE zset1 80 90 withscores limit 1 1
1) "ls"
2) "90"

#增加某个成员分数
127.0.0.1:6379> ZINCRBY zset1 5 ww
"80"

# 查看元素个数
127.0.0.1:6379> zcard zset1
(integer) 3
# 统计范围内的元素个数 闭区间
127.0.0.1:6379> ZCOUNT zset1 85 90
(integer) 1

RedisObject

  • redisObject是redis中封装value对象的数据结构。任何一个value都会被包装成一个redisObject。redisObject能指定value的类型,编码格式,内存回收,数据指针等

  • 这样设计的好处是在5种常用类型设置多种不同的的数据结构实现,优化对象在不同场景下的效率。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    typedef struct redisObject {
    // 刚刚好32 bits
    // 对象的类型,字符串/列表/集合/哈希表
    unsigned type:4;
    // 未使用的两个位
    unsigned notused:2; /* Not used */
    // 编码的方式,Redis 为了节省空间,提供多种方式来保存一个数据
    // 譬如: “123456789” 会被存储为整数123456789
    unsigned encoding:4;
    // 当内存紧张,淘汰数据的时候用到
    unsigned lru:22; /* lru time (relative to server.lruclock) */
    // 引用计数
    int refcount;
    // 数据指针
    void *ptr;
    } robj
  • 这里介绍几个比较关键的属性:

    • type: 标记了value对象的数据类型,使用type 命令来获取。所有数据类型如下所示:

      1
      2
      3
      4
      5
      6
      /* Object types */
      #define REDIS_STRING 0
      #define REDIS_LIST 1
      #define REDIS_SET 2
      #define REDIS_ZSET 3
      #define REDIS_HASH 4
    • encoding: 标记了value对象的编码,也就是使用了什么数据结构,使用 object encoding 命令来获取。编码如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /* Objects encoding. Some kind of objects like Strings and Hashes can be
      * internally represented in multiple ways. The 'encoding' field of the object
      * is set to one of this fields for this object. */
      #define REDIS_ENCODING_INT /* Encoded as integer */
      #define REDIS_ENCODING_EMBSTR /* Encoded as embstr */
      #define REDIS_ENCODING_RAW /* Raw representation */
      #define REDIS_ENCODING_HT /* Encoded as hash table */
      #define REDIS_ENCODING_LINKEDLIST /* Encoded as regular linked list */
      #define REDIS_ENCODING_ZIPLIST /* Encoded as ziplist */
      #define REDIS_ENCODING_INTSET /* Encoded as intset */
      #define REDIS_ENCODING_SKIPLIST /* Encoded as skiplist */
    • redis为优化内存,对数据类型提供了多种底层实现方式,type和encoding对应关系如下表格所示:

      类型(type属性) 编码(encoding属性) 注释
      REDIS_STRING REDIS_ENCODING_INT 整数值实现的字符串
      REDIS_STRING REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串实现的字符串
      REDIS_STRING REDIS_ENCODING_RAW 简单动态字符串实现的字符串
      REDIS_LIST REDIS_ENCODING_ZIPLIST 压缩列表实现的列表
      REDIS_LIST REDIS_ENCODING_LINKEDLIST 双向链表实现的列表
      REDIS_HASH REDIS_ENCODING_ZIPLIST 压缩列表实现的哈希表
      REDIS_HASH REDIS_ENCODING_HT 字典实现的哈希表
      REDIS_SET REDIS_ENCODING_INTSET 整数集合实现的集合
      REDIS_SET REDIS_ENCODING_HT 字典实现的集合
      REDIS_ZSET REDIS_ENCODING_ZIPLIST 压缩列表实现的有序集合
      REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃表+字典实现的有序集合
    • lru: redis对数据集占用内存的大小由周期性的计算,当超出限制时,会淘汰超时的数据。即淘汰的标准为: oversize & overtime。

  • 举个栗子,当set hello world时会有以下数据模型:

    • dictEntry: Redis给每个key-value键值对分配一个dictEntry,里面有着key和val的指针,next指向下一个dictEntry形成链表,这个指针可以将多个哈希值相同的键值对链接在一起,由此来解决哈希冲突问题(链地址法)。
    • **sds: **键key “hello” 是以SDS(简单动态字符串)存储。
    • redisObject: 值val是存储在redisObject。
  • 简单动态字符串: 长度动态可变,可类比为Java中的ArrayLIst。Redis中不仅Key是以SDS形式存储,String类型value对象也有以SDS存储。

    1
    2
    3
    4
    5
    struct sdshdr {
    int len; // buf数组中已经使用的字节的数量,也就是SDS字符串长度
    int free; // buf数组中未使用的字节的数量
    char buf[]; // 字节数组,字符串就保存在这里面
    };
    • **常数复杂度获取字符串长度: **len字段存储字符串长度
    • **预空间分配: **
      • SDS长度(len的值)小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时free和len属性值相同。
      • SDS长度(len的值)大于等于1MB,程序会分配1MB的未使用空间。
    • 惰性释放空间: 当执行sdstrim(截取字符串)之后,SDS不会立马释放多出来的空间,如果下次再进行拼接字符串操作,且拼接的没有刚才释放的空间大,则那些未使用的空间就会排上用场。通过惰性释放空间避免了特定情况下操作字符串的内存重新分配操作。
    • 杜绝缓冲区溢出: 使用C字符串的操作时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的操作在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。