Redis命令

Redis相关API

Redis 命令参考:http://redisdoc.com

一、Redis底层

1、redis数据类型

类型底层应用场景编码类型(数据量由大到小)
StringSDS数组帖子、评论、热点数据、输入缓冲RAW << EMBSTR << INT
ListQuickList评论列表、商品列表、发布与订阅、慢查询、监视器LINKEDLIST << ZIPLIST
SetintSet适合交集、并集、查集操作,例如朋友关系HT << INSET
Zset跳跃表去重后排序,适合排名场景SKIPLIST << ZIPLIST
Hash哈希结构化数据,比如存储对象HT << ZIPLIST
Stream紧凑列表消息队列

2、redis底层数据结构

1)SDS数组结构(简单动态字符串),用于存储字符串和整型数据及输入缓冲。

struct sdshdr{ 
  int len;//记录buf数组中已使用字节的数量 
  int free; //记录 buf 数组中未使用字节的数量 
  char buf[];//字符数组,用于保存字符串
}

2)跳跃表:将有序链表中的部分节点分层,每一层都是一个有序链表。

​ 1、可以快速查找到需要的节点 O(logn) ,额外存储了一倍的空间

​ 2、可以在O(1)的时间复杂度下,快速获得跳跃表的头节点、尾结点、长度和高度。

3)字典dict: 又称散列表(hash),是用来存储键值对的一种数据结构。

​ Redis整个数据库是用字典来存储的(K-V结构) —Hash+数组+链表

​ Redis字典实现包括:字典(dict)、Hash表(dictht)、Hash表节点(dictEntry)

​ 字典达到存储上限(阈值 0.75),需要rehash(扩容)

​ 1、初次申请默认容量为4个dictEntry,非初次申请为当前hash表容量的一倍。

​ 2、rehashidx=0表示要进行rehash操作。

​ 3、新增加的数据在新的hash表h[1] 、修改、删除、查询在老hash表h[0]

​ 4、将老的hash表h[0]的数据重新计算索引值后全部迁移到新的hash表h[1]中,这个过程称为 rehash。

渐进式rehash

由于当数据量巨大时rehash的过程是非常缓慢的,所以需要进行优化。 可根据服务器空闲程度批量rehash部分节点

4)压缩列表zipList

​ 压缩列表(ziplist)是由一系列特殊编码的连续内存块组成的顺序型数据结构,节省内容

sorted-set和hash元素个数少且是小整数或短字符串(直接使用)

​ list用快速链表(quicklist)数据结构存储,而快速链表是双向列表与压缩列表的组合。(间接使用)

5)整数集合intSet

​ 整数集合(intset)是一个有序的(整数升序)、存储整数的连续存储结构。

​ 当Redis集合类型的元素都是整数并且都处在64位有符号整数范围内(2^64),使用该结构体存储。

6)快速列表quickList

​ 快速列表(quicklist)是Redis底层重要的数据结构。是Redis3.2列表的底层实现。

​ (在Redis3.2之前,Redis采用双向链表(adlist)和压缩列表(ziplist)实现)

7)Redis Stream的底层主要使用了listpack(紧凑列表)和Rax树(基数树)。

listpack表示一个字符串列表的序列化,listpack可用于存储字符串或整数。用于存储stream的消息内容。

Rax树是一个有序字典树 (基数树 Radix Tree),按照 key 的字典序排列,支持快速地定位、插入和删除操作。

3、Zset底层实现

​ 跳表(skip List)是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为O(logN)。简单说来跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能,正是这个跳跃的功能,使得在查找元素时,跳表能够提供O(logN)的时间复杂度

​ Zset数据量少的时候使用压缩链表ziplist实现,有序集合使用紧挨在一起的压缩列表节点来保存,第一个节点保存member,第二个保存score。ziplist内的集合元素按score从小到大排序,score较小的排在表头位置。 数据量大的时候使用跳跃列表skiplist和哈希表hash_map结合实现,查找删除插入的时间复杂度都是O(longN)

​ Redis使用跳表而不使用红黑树,是因为跳表的索引结构序列化和反序列化更加快速,方便持久化。

搜索

​ 跳跃表按 score 从小到大保存所有集合元素,查找时间复杂度为平均 O(logN),最坏 O(N) 。

插入

选用链表作为底层结构支持,为了高效地动态增删。因为跳表底层的单链表是有序的,为了维护这种有序性,在插入前需要遍历链表,找到该插入的位置,单链表遍历查找的时间复杂度是O(n),同理可得,跳表的遍历也是需要遍历索引数,所以是O(logn)。

删除

如果该节点还在索引中,删除时不仅要删除单链表中的节点,还要删除索引中的节点;单链表在知道删除的节点是谁时,时间复杂度为O(1),但针对单链表来说,删除时都需要拿到前驱节点O(logN)才可改变引用关系从而删除目标节点。

4、编码类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jQVD8kGn-1625484805976)(C:\Users\AdministratorGUET\AppData\Roaming\Typora\typora-user-images\image-20210503133758497.png)]

  1. 字符串对象

    1. 字符串编码

      字符串对象的编码可以是int、raw或者embstr。

      1)如果一个字符串对象保存的是整数值,并且这个整数值可以用lon类型表示,那么字符串对象将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码方式设置为int。

      2)如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串保存这个值,并将对象的编码设置为raw。

      3)如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。

    2. 编码的转换

      int编码和embstr编码的字符串对象在满足条件的情况下,会转换为raw编码的字符串对象。

         1)int编码转为raw编码:原对象保存的值不再是整数值,而是一个字符串值,那么会发生编码从int变为raw
      
         2)redis没有为embstr编码的字符串对象编写任何相应的修改程序(只有int转为raw),所以,embstr编码字符串实际上是只读的,当对embstr编码的字符从执行修改命令时,
      
         程序会先将对象的embstr转换成raw,然后再执行修改命令。(embstr编码的字符串对象执行APPEND命令后,对象的编码会从embstr变为raw)。
      
  2. 列表对象

    1. 列表对象的编码

      列表对象的编码可以是ziplist或者linkedlistziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存一个列表节点。linkedlist编码的列表对象使用双端链表作为底层

      实现。每个双端链表节点(node)都保存一个字符串对象,而每个字符串对象都保存了一个列表元素。

    2. 编码的转换

      **当列表对象可以同时满足一下两个条件时,列表对象使用ziplist编码,**不能满足这两个条件的列表对象需要使用linkedlist编码。

      1)列表对象保存的所有字符串元素的长度都小于64字节

      2)列表对象保存的元素数量小于512个,

  3. 哈希对象

    1. 哈希对象的编码

      哈希对象的编码可以是ziplist或者hashtable。

        1)ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入压缩列表表尾。因此
      

      ​ 1、保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;

      ​ 2、先添加到哈希对象中的键值对会放在压缩列表的表头方向,而后添加的哈希对象中的键值对会被放在压缩列表的链表方向。

      ​ 2)hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存

      ​ 1、字典的每个键都是一个字符串对象,对象中保存了键值对的键。

      ​ 2、字典中每个值都是一个字符串对象,对象中保存了键值对的值。

    2. 编码的转换

      当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码,否则使用hashtable编码

      1)哈希对象保存的所有键值对的键和值的字符串长度都小于64个字节

      2)哈希对象保存的键值对数量小于512个

  4. 集合对象

    1. 集合对象的编码

      集合对象的编码可以时intset或者hashtable

      1)intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。

      2)hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象都包含一个集合元素,而字典的值则被全部设置为NULL。

    2. 编码的转换

      同时满足两个条件使用intset,否则使用hashtable

         1)集合对象保存的所有值都是整数值
      
         2)集合对象保存的元素数量不超过512个
      
  5. 有序集合对象

    1. 有序集合对象的编码

      有序集合对象的编码可以时ziplist或者skiplist

      1)ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二个元素保存元素的分值(score)

      压缩列表内的集合元素按照分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则放置在靠近表尾的方向。

      2)skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。

      ​ 1、zset结构中的zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存一个集合元素:跳跃表即诶单 的object属性保存了元素成员,而跳跃表节点的score属性则保存了元素的分值。

      通过跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等命令就是基于跳跃表API来实现的

      2、 zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素: 字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过字典,程序

      可以用O(1)复杂度查找给定成员的分值,ZSCORE命令就是根据这一特性是实现的。

    2. 编码的转换

      同时满足一下两个条件使用ziplist,否则使用skiplist

         1)有序集合所保存的所有元素成员的长度都小于64字节
       
         2)有序集合保存的元素数量小于128个
      

5、管道(Pipeline)

​ 管道字面意思就是流水线,它能将一组Redis命令进行组装,通过一次传输给Redis并返回结果集。类似于mget、mset这样的批处理命令,用于解决某些操作根本就不支持或没有批量操作的情况。

对比

​ 由于只有一次传输,Pipeline的性能比非Pipeline要好,尤其是网络延迟高的情况下。

​ 批量命令、Pipeline 对比

  1. 原生批量命令是原子的,Pipeline 是非原子的。
  2. 原生批量命令是一个命令对应多个 key,Pipeline 支持多个命令。
  3. 原生批量命令是 Redis 服务端支持实现的,而 Pipeline 需要服务端和客户端的共同实现

适用场景

Peline是 Redis 的一个提高吞吐量的机制,适用于多 key 读写场景,比如同时读取多个keyvalue,或者更新多个keyvalue,并且允许一定比例的写入失败实时性也没那么高,那么这种场景就可以使用了。比如 10000 条一下进入 redis,可能失败了 2 条无所谓,后期有补偿机制就行了,像短信群发这种场景,这时候用 pipeline 最好了。

注意点

  1. Pipeline是非原子的,在上面原理解析那里已经说了就是 Redis 实际上还是一条一条的执行的,而执行命令是需要排队执行的,所以就会出现原子性问题。
  2. Pipeline中包含的命令不要包含过多。
  3. Pipeline每次只能作用在一个 Redis 节点上。
  4. Pipeline 不支持事务,因为命令是一条一条执行的。

二、Redis命令详解

相关API

http://redisdoc.com

类型
StringSETSETNXSETEXGETGETSETINCRDECRMSETMGET
HashHSETHSETNXHGETHDELHLENHMSETHMGETHKEYSHGETALL
LISTLPUSHLPOPRPUSHRPOPLINDEXLREMLRANGELLENRPOPLPUSH
ZSETZADDZREMZSCOREZCARDZRANGEZRANKZREVRANKZREVRANGE
SETSADDSREMSISMEMBERSCARDSINTERSUNIONSDIFFSPOPSMEMBERS
事务MULTIEXECDISCARDWATCHUNWATCH

1、字符串(String)

SET
  • 语法:SET key value [EX seconds] [PX milliseconds] [NX|XX]

    将字符串值 value 关联到 key

    如果 key 已经持有其他值, SET 就覆写旧值, 无视类型。

    SET 命令对一个带有生存时间(TTL)的键进行设置之后, 该键原有的 TTL 将被清除。

  • 可选参数

    • EX seconds : 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value
    • PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value
    • NX只在键不存在时, 才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value
    • XX只在键已经存在时, 才对键进行设置操作。
  • 返回值

    • 在 Redis 2.6.12 版本以前, SET 命令总是返回 OK
    • 从 Redis 2.6.12 版本开始, SET 命令只在设置操作成功完成时才返回 OK ; 如果命令使用了 NX 或者 XX 选项, 但是因为条件没达到而造成设置操作未执行, 那么命令将返回空批量回复(NULL Bulk Reply)。
  • 代码示例

    对不存在的键进行设置:

    redis> SET key "value"
    OK
    
    redis> GET key
    "value"
    

    对已存在的键进行设置:

    redis> SET key "new-value"
    OK
    
    redis> GET key
    "new-value"
    

    使用 EX 选项:

    redis> SET key-with-expire-time "hello" EX 10086 // 单位是秒
    OK
    
    redis> GET key-with-expire-time
    "hello"
    
    redis> TTL key-with-expire-time//查看剩余过期时间
    (integer) 10069
    

    使用 PX 选项:

    redis> SET key-with-pexpire-time "moto" PX 123321 // 单位是毫秒
    OK
    
    redis> GET key-with-pexpire-time
    "moto"
    
    redis> PTTL key-with-pexpire-time
    (integer) 111939
    

    使用 NX 选项:

    redis> SET not-exists-key "value" NX
    OK      # 键不存在,设置成功
    
    redis> GET not-exists-key
    "value"
    
    redis> SET not-exists-key "new-value" NX
    (nil)   # 键已经存在,设置失败
    
    redis> GET not-exists-key
    "value" # 维持原值不变
    

    使用 XX 选项:

    redis> EXISTS exists-key
    (integer) 0
    
    redis> SET exists-key "value" XX
    (nil)   # 因为键不存在,设置失败
    
    redis> SET exists-key "value"
    OK      # 先给键设置一个值
    
    redis> SET exists-key "new-value" XX
    OK      # 设置新值成功
    
    redis> GET exists-key
    "new-value"
    
SETNX
  • 语法:SETNX key value

    只在键 key 不存在的情况下, 将键 key 的值设置为 value

    若键 key 已经存在, 则 SETNX 命令不做任何动作。

    SETNX 是『SET if Not EXists』(如果不存在,则 SET)的简写。

  • 可选参数

  • 返回值

    命令在设置成功时返回 1 , 设置失败时返回 0

  • 代码示例

    redis> EXISTS job                # job 不存在
    (integer) 0
    
    redis> SETNX job "programmer"    # job 设置成功
    (integer) 1
    
    redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
    (integer) 0
    
    redis> GET job                   # 没有被覆盖
    "programmer"
    
SETEX
  • 语法:SETEX key seconds value

    将键 key 的值设置为 value , 并将键 key 的生存时间设置为 seconds 秒钟。

    如果键 key 已经存在, 那么 SETEX 命令将覆盖已有的值。

    SETEX 命令的效果和以下两个命令的效果类似:

    SET key value
    EXPIRE key seconds  # 设置生存时间
    

    SETEX 和这两个命令的不同之处在于 SETEX 是一个原子(atomic)操作, 它可以在同一时间内完成设置值和设置过期时间这两个操作, 因此 SETEX 命令在储存缓存的时候非常实用。

  • 可选参数

  • 返回值

    命令在设置成功时返回 OK 。 当 seconds 参数不合法时, 命令将返回一个错误。

  • 代码示例

    在键 key 不存在的情况下执行 SETEX

    redis> SETEX cache_user_id 60 10086
    OK
    
    redis> GET cache_user_id  # 值
    "10086"
    
    redis> TTL cache_user_id  # 剩余生存时间
    (integer) 49
    

    key 已经存在, 使用 SETEX 覆盖旧值:

    redis> SET cd "timeless"
    OK
    
    redis> SETEX cd 3000 "goodbye my love"
    OK
    
    redis> GET cd
    "goodbye my love"
    
    redis> TTL cd
    (integer) 2997
    
PSETEX
  • 语法:PSETEX key milliseconds value

    可用版本: >= 2.6.0

    时间复杂度: O(1)

    这个命令和 SETEX 命令相似, 但它以毫秒为单位设置 key 的生存时间, 而不是像 SETEX 命令那样以为单位进行设置。

  • 返回值

    命令在设置成功时返回 OK

  • 代码示例

    redis> PSETEX mykey 1000 "Hello"
    OK
    
    redis> PTTL mykey //获取毫秒级过期时间
    (integer) 999
    
    redis> GET mykey
    "Hello"
    
GET
  • 语法:GET key

    返回与key 相关联的字符串值。

  • 可选参数

  • 返回值

    • 如果键 key 不存在, 那么返回特殊值 nil ; 否则, 返回键 key 的值。
    • 如果键 key 的值并非字符串类型, 那么返回一个错误, 因为 GET 命令只能用于字符串值。
  • 代码示例

    对不存在的键 key 或是字符串类型的键 key 执行 GET 命令:

    redis> GET db
    (nil)
    
    redis> SET db redis
    OK
    
    redis> GET db
    "redis"
    

    对不是字符串类型的键 key 执行 GET 命令:

    redis> DEL db
    (integer) 1
    
    redis> LPUSH db redis mongodb mysql
    (integer) 3
    
    redis> GET db
    (error) ERR Operation against a key holding the wrong kind of value
    
GETSET
  • 语法:GETSET key value

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    将键 key 的值设为 value , 并返回键 key 在被设置之前的旧值。

  • 返回值

    返回给定键 key 的旧值。

    如果键 key 没有旧值, 也即是说, 键 key 在被设置之前并不存在, 那么命令返回 nil

    当键 key 存在但不是字符串类型时, 命令返回一个错误。

  • 代码示例

    redis> GETSET db mongodb    # 没有旧值,返回 nil
    (nil)
    
    redis> GET db
    "mongodb"
    
    redis> GETSET db redis      # 返回旧值 mongodb
    "mongodb"
    
    redis> GET db
    "redis"
    
STRLEN
  • 语法:STRLEN key

    可用版本: >= 2.2.0

    复杂度: O(1)

    返回键 key 储存的字符串值的长度。

  • 返回值

    STRLEN 命令返回字符串值的长度。

    当键 key 不存在时, 命令返回 0

    key 储存的不是字符串值时, 返回一个错误。

  • 代码示例

    获取字符串值的长度:

    redis> SET mykey "Hello world"
    OK
    
    redis> STRLEN mykey
    (integer) 11
    

    不存在的键的长度为 0

    redis> STRLEN nonexisting
    (integer) 0
    
APPEND
  • 语法:APPEND key value

    可用版本: >= 2.0.0

    时间复杂度: 平摊O(1)

    如果键 key 已经存在并且它的值是一个字符串, APPEND 命令将把 value 追加到键 key 现有值的末尾。

    如果 key 不存在, APPEND 就简单地将键 key 的值设为 value , 就像执行 SET key value 一样。

  • 返回值

    追加 value 之后, 键 key 的值的长度。

  • 代码示例

    对不存在的 key 执行 APPEND

    redis> EXISTS myphone               # 确保 myphone 不存在
    (integer) 0
    
    redis> APPEND myphone "nokia"       # 对不存在的 key 进行 APPEND ,等同于 SET myphone "nokia"
    (integer) 5                         # 字符长度
    

    对已存在的字符串进行 APPEND

    redis> APPEND myphone " - 1110"     # 长度从 5 个字符增加到 12 个字符
    (integer) 12
    
    redis> GET myphone
    "nokia - 1110"
    
SETRANGE
  • 语法:SETRANGE key offset value

    可用版本: >= 2.2.0

    时间复杂度:对于长度较短的字符串,命令的平摊复杂度O(1);对于长度较大的字符串,命令的复杂度为 O(M) ,其中 M 为 value 的长度。

    从偏移量 offset 开始, 用 value 参数覆写(overwrite)键 key 储存的字符串值。

    不存在的键 key 当作空白字符串处理。

    SETRANGE 命令会确保字符串足够长以便将 value 设置到指定的偏移量上, 如果键 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的 offset10 ), 那么原字符和偏移量之间的空白将用零字节(zerobytes, "\x00" )进行填充

    因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内, 所以用户能够使用的最大偏移量为 2^29-1(536870911) , 如果你需要使用比这更大的空间, 请使用多个 key

    Warning
    当生成一个很长的字符串时, Redis 需要分配内存空间, 该操作有时候可能会造成服务器阻塞(block)。 在2010年出产的Macbook Pro上, 设置偏移量为 536870911(512MB 内存分配)将耗费约 300 毫秒, 设置偏移量为 134217728(128MB 内存分配)将耗费约 80 毫秒, 设置偏移量 33554432(32MB 内存分配)将耗费约 30 毫秒, 设置偏移量为 8388608(8MB 内存分配)将耗费约 8 毫秒。
    
  • 返回值

    SETRANGE 命令会返回被修改之后, 字符串值的长度。

  • 代码示例

    对非空字符串执行 SETRANGE 命令:

    redis> SET greeting "hello world"
    OK
    
    redis> SETRANGE greeting 6 "Redis"
    (integer) 11
    
    redis> GET greeting
    "hello Redis"
    

    对空字符串/不存在的键执行 SETRANGE 命令:

    redis> EXISTS empty_string
    (integer) 0
    
    redis> SETRANGE empty_string 5 "Redis!"   # 对不存在的 key 使用 SETRANGE
    (integer) 11
    
    redis> GET empty_string                   # 空白处被"\x00"填充
    "\x00\x00\x00\x00\x00Redis!"
    
GETRANGE
  • 语法:GETRANGE key start end

    可用版本: >= 2.4.0

    时间复杂度: O(N),其中 N 为被返回的字符串的长度。

    返回键 key 储存的字符串值的指定部分, 字符串的截取范围由 startend 两个偏移量决定 (包括 startend 在内)。

    负数偏移量表示从字符串的末尾开始计数, -1 表示最后一个字符, -2 表示倒数第二个字符, 以此类推。

    GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。

    GETRANGE 命令在 Redis 2.0 之前的版本里面被称为 SUBSTR 命令。Python客户端至今仍然使用substr()方法获取字串,但是在使用2.0以上版本的Redis,那么最好使用getrange()方法来获取字串。

  • 返回值

    GETRANGE 命令会返回字符串值的指定部分。

  • 代码示例

    redis> SET greeting "hello, my friend"
    OK
    
    redis> GETRANGE greeting 0 4          # 返回索引0-4的字符,包括4。
    "hello"
    
    redis> GETRANGE greeting -1 -5        # 不支持回绕操作
    ""
    
    redis> GETRANGE greeting -3 -1        # 负数索引
    "end"
    
    redis> GETRANGE greeting 0 -1         # 从第一个到最后一个
    "hello, my friend"
    
    redis> GETRANGE greeting 0 1008611    # 值域范围不超过实际字符串,超过部分自动被符略
    "hello, my friend"
    
INCR
  • 语法:INCR key

    为键 key 储存的数字值加上一。

    如果键 key 不存在, 那么它的值会先被初始化为 0 , 然后再执行 INCR 命令。

    如果键 key 储存的值不能被解释为数字, 那么 INCR 命令将返回一个错误。

    本操作的值限制在 64 位(bit)有符号数字表示之内。

    INCR命令是一个针对字符串的操作。因为Redis并没有专用的整数类型,所以键key存储的值在执行INCR命令时会被解释为十进制64位有符号整数。
    
  • 返回值

    INCR 命令会返回键 key 在执行加一操作之后的值。

  • 示例

    redis> SET page_view 20
    OK
    
    redis> INCR page_view
    (integer) 21
    
    redis> GET page_view    # 数字值在 Redis 中以字符串的形式保存
    "21"
    
INCRBY
  • 语法:INCRBY key increment

    为键 key 储存的数字值加上增量 increment

    如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 INCRBY 命令。

    如果键 key 储存的值不能被解释为数字, 那么 INCRBY 命令将返回一个错误。

    本操作的值限制在 64 位(bit)有符号数字表示之内。

    关于递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR 命令的文档。

  • 返回值

    在加上增量 increment 之后, 键 key 当前的值。

  • 示例

    键存在,并且值为数字:

    redis> SET rank 50
    OK
    
    redis> INCRBY rank 20
    (integer) 70
    
    redis> GET rank
    "70"
    

    键不存在:

    redis> EXISTS counter
    (integer) 0
    
    redis> INCRBY counter 30
    (integer) 30
    
    redis> GET counter
    "30"
    

    键存在,但值无法被解释为数字:

    redis> SET book "long long ago..."
    OK
    
    redis> INCRBY book 200
    (error) ERR value is not an integer or out of range
    
INCRBYFLOAT
  • 语法:

    可用版本: >= 2.6.0

    时间复杂度: O(1)

    为键 key 储存的值加上浮点数增量 increment

    如果键 key 不存在, 那么 INCRBYFLOAT 会先将键 key 的值设为 0 , 然后再执行加法操作。

    如果命令执行成功, 那么键 key 的值会被更新为执行加法计算之后的新值, 并且新值会以字符串的形式返回给调用者。

    无论是键 key 的值还是增量 increment , 都可以使用像 2.0e73e590e-2 那样的指数符号(exponential notation)来表示, 但是, 执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存, 也即是, 它们总是由一个数字, 一个(可选的)小数点和一个任意长度的小数部分组成(比如 3.1469.768 ,诸如此类), 小数部分尾随的 0 会被移除, 如果可能的话, 命令还会将浮点数转换为整数(比如 3.0 会被保存成 3 )。

    此外, 无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。

    当以下任意一个条件发生时, 命令返回一个错误:

    • key 的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型);
    • key 当前的值或者给定的增量 increment 不能被解释(parse)为双精度浮点数。
  • 返回值

    在加上增量 increment 之后, 键 key 的值。

  • 代码示例

    redis> GET decimal
    "3.0"
    
    redis> INCRBYFLOAT decimal 2.56
    "5.56"
    
    redis> GET decimal
    "5.56"
    
DECR
  • 语法:DECR key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    为键 key 储存的数字值减去一。

    如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECR 操作。

    如果键 key 储存的值不能被解释为数字, 那么 DECR 命令将返回一个错误。

    本操作的值限制在 64 位(bit)有符号数字表示之内。

    关于递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR 命令的文档。

  • 返回值

    DECR 命令会返回键 key 在执行减一操作之后的值。

  • 代码示例

    对储存数字值的键 key 执行 DECR 命令:

    redis> SET failure_times 10
    OK
    
    redis> DECR failure_times
    (integer) 9
    

    对不存在的键执行 DECR 命令:

    redis> EXISTS count
    (integer) 0
    
    redis> DECR count
    (integer) -1
    
DECRBY
  • 语法:DECRBY key decrement

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    将键 key 储存的整数值减去减量 decrement

    如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECRBY 命令。

    如果键 key 储存的值不能被解释为数字, 那么 DECRBY 命令将返回一个错误。

    本操作的值限制在 64 位(bit)有符号数字表示之内。

    关于更多递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR 命令的文档。

  • 返回值

    DECRBY 命令会返回键在执行减法操作之后的值。

  • 代码示例

    对已经存在的键执行 DECRBY 命令:

    redis> SET count 100
    OK
    
    redis> DECRBY count 20
    (integer) 80
    

    对不存在的键执行 DECRBY 命令:

    redis> EXISTS pages
    (integer) 0
    
    redis> DECRBY pages 10
    (integer) -10
    
MSET
  • 语法:MSET key value [key value …]

    可用版本: >= 1.0.1

    时间复杂度: O(N),其中 N 为被设置的键数量。

    同时为多个键设置值。

    如果某个给定键已经存在, 那么 MSET 将使用新值去覆盖旧值, 如果这不是你所希望的效果, 请考虑使用 MSETNX 命令, 这个命令只会在所有给定键都不存在的情况下进行设置。

    MSET 是一个原子性(atomic)操作, 所有给定键都会在同一时间内被设置, 不会出现某些键被设置了但是另一些键没有被设置的情况。

  • 返回值

    MSET 命令总是返回 OK

  • 代码示例

    同时对多个键进行设置:

    redis> MSET date "2012.3.30" time "11:00 a.m." weather "sunny"
    OK
    
    redis> MGET date time weather
    1) "2012.3.30"
    2) "11:00 a.m."
    3) "sunny"
    

    覆盖已有的值:

    redis> MGET k1 k2
    1) "hello"
    2) "world"
    
    redis> MSET k1 "good" k2 "bye"
    OK
    
    redis> MGET k1 k2
    1) "good"
    2) "bye"
    
MSETNX
  • 语法:MSETNX key value [key value …]

    可用版本: >= 1.0.1

    时间复杂度: O(N), 其中 N 为被设置的键数量。

    当且仅当所有给定键都不存在时, 为所有给定键设置值。

    即使只有一个给定键已经存在, MSETNX 命令也会拒绝执行对所有键的设置操作。

    MSETNX 是一个原子性(atomic)操作, 所有给定键要么就全部都被设置, 要么就全部都不设置, 不可能出现第三种状态。

  • 返回值

    当所有给定键都设置成功时, 命令返回 1 ; 如果因为某个给定键已经存在而导致设置未能成功执行, 那么命令返回 0

  • 代码示例

    对不存在的键执行 MSETNX 命令:

    redis> MSETNX rmdbs "MySQL" nosql "MongoDB" key-value-store "redis"
    (integer) 1
    
    redis> MGET rmdbs nosql key-value-store
    1) "MySQL"
    2) "MongoDB"
    3) "redis"
    

    对某个已经存在的键进行设置:

    redis> MSETNX rmdbs "Sqlite" language "python"  # rmdbs 键已经存在,操作失败
    (integer) 0
    
    redis> EXISTS language                          # 因为 MSETNX 命令没有成功执行
    (integer) 0                                     # 所以 language 键没有被设置
    
    redis> GET rmdbs                                # rmdbs 键也没有被修改
    "MySQL"
    
MGET
  • 语法:MGET key [key …]

    可用版本: >= 1.0.0

    时间复杂度: O(N) ,其中 N 为给定键的数量。

    返回给定的一个或多个字符串键的值。

    如果给定的字符串键里面, 有某个键不存在, 那么这个键的值将以特殊值 nil 表示。

  • 返回值

    MGET 命令将返回一个列表, 列表中包含了所有给定键的值。

  • 代码示例

    redis> SET redis redis.com
    OK
    
    redis> SET mongodb mongodb.org
    OK
    
    redis> MGET redis mongodb
    1) "redis.com"
    2) "mongodb.org"
    
    redis> MGET redis mongodb mysql     # 不存在的 mysql 返回 nil
    1) "redis.com"
    2) "mongodb.org"
    3) (nil)
    

因为 SET 命令可以通过参数来实现 SETNXSETEX 以及 PSETEX 命令的效果, 所以 Redis 将来的版本可能会移除并废弃 SETNXSETEXPSETEX 这三个命令。

2、哈希表(HASH)

HSET
  • 语法:HSET hash field value

    可用版本: >= 2.0.0

    时间复杂度: O(1)

    将哈希表 hash 中域 field 的值设置为 value

    如果给定的哈希表并不存在, 那么一个新的哈希表将被创建并执行 HSET 操作。

    如果域 field 已经存在于哈希表中, 那么它的旧值将被新值 value 覆盖。

  • 返回值

    HSET 命令在哈希表中新创建 field 域并成功为它设置值时, 命令返回 1 ; 如果域 field 已经存在于哈希表, 并且 HSET 命令成功使用新值覆盖了它的旧值, 那么命令返回 0

  • 代码示例

    设置一个新域:

    redis> HSET website google "www.g.cn"
    (integer) 1
    
    redis> HGET website google
    "www.g.cn"
    

    对一个已存在的域进行更新:

    redis> HSET website google "www.google.com"
    (integer) 0
    
    redis> HGET website google
    "www.google.com"
    
HSETNX
  • 语法:HSETNX hash field value

    可用版本: >= 2.0.0

    时间复杂度: O(1)

    当且仅当域 field 尚未存在于哈希表的情况下, 将它的值设置为 value

    如果给定域已经存在于哈希表当中, 那么命令将放弃执行设置操作。

    如果哈希表 hash 不存在, 那么一个新的哈希表将被创建并执行 HSETNX 命令。

  • 返回值

    HSETNX 命令在设置成功时返回 1 , 在给定域已经存在而放弃执行设置操作时返回 0

  • 代码示例

    域尚未存在, 设置成功:

    redis> HSETNX database key-value-store Redis
    (integer) 1
    
    redis> HGET database key-value-store
    "Redis"
    

    域已经存在, 设置未成功, 域原有的值未被改变:

    redis> HSETNX database key-value-store Riak
    (integer) 0
    
    redis> HGET database key-value-store
    "Redis"
    
HGET
  • 语法:HGET hash field

    可用版本: >= 2.0.0

    时间复杂度: O(1)

    返回哈希表中给定域的值。

  • 返回值

    HGET 命令在默认情况下返回给定域的值。

    如果给定域不存在于哈希表中, 又或者给定的哈希表并不存在, 那么命令返回 nil

  • 代码示例

    域存在的情况:

    redis> HSET homepage redis redis.com
    (integer) 1
    
    redis> HGET homepage redis
    "redis.com"
    

    域不存在的情况:

    redis> HGET site mysql
    (nil)
    
HEXISTS
  • 语法:HEXISTS hash field

    可用版本: >= 2.0.0

    时间复杂度: O(1)

    检查给定域 field 是否存在于哈希表 hash 当中。

  • 返回值

    HEXISTS 命令在给定域存在时返回 1 , 在给定域不存在时返回 0

  • 示例

    给定域不存在:

    redis> HEXISTS phone myphone
    (integer) 0
    

    给定域存在:

    redis> HSET phone myphone nokia-1110
    (integer) 1
    
    redis> HEXISTS phone myphone
    (integer) 1
    
HDEL
  • 语法:HDEL key field [field …]

    可用版本: >= 2.0.0

    时间复杂度: O(N),N为要删除的域的数量。

    删除哈希表key中的一个或者多个给定域,不存在的域将被忽略。

  • 返回值

    被成功移除的域的数量,不包括被忽略的域。

  • 代码示例

    # 测试数据
    redis> HGETALL abbr
    1) "a"
    2) "apple"
    3) "b"
    4) "banana"
    5) "c"
    6) "cat"
    7) "d"
    8) "dog"
    
    # 删除单个域
    redis> HDEL abbr a
    (integer) 1
    
    
    # 删除不存在的域
    redis> HDEL abbr not-exists-field
    (integer) 0
    
    # 删除多个域
    redis> HDEL abbr b c
    (integer) 2
    
    redis> HGETALL abbr
    1) "d"
    2) "dog"
    
HLEN
  • 语法:HLEN key

    可用版本: >= 2.0.0

    时间复杂度: O(1)。

    返回哈希表 key 中域的数量。

  • 返回值

    哈希表中域的数量。

    当key不存在的时候返回0。

  • 代码示例

    redis> HSET db redis redis.com
    (integer) 1
    
    redis> HSET db mysql mysql.com
    (integer) 1
    
    redis> HLEN db
    (integer) 2
    
    redis> HSET db mongodb mongodb.org
    (integer) 1
    
    redis> HLEN db
    (integer) 3
    
HSTRLEN
  • 语法:HSTRLEN key field

    返回哈希表 key 中,与给定域 field 相关联的值的字符串长度(string length)。

  • 返回值

    返回哈希表 key 中,与给定域 field 相关联的值的字符串长度(string length)。

    如果给定的键或者域不存在, 那么命令返回 0

  • 代码示例

    redis> HMSET myhash f1 "HelloWorld" f2 "99" f3 "-256"
    OK
    
    redis> HSTRLEN myhash f1
    (integer) 10
    
    redis> HSTRLEN myhash f2
    (integer) 2
    
    redis> HSTRLEN myhash f3
    (integer) 4
    
HINCRBY
  • 语法:HINCRBY key field increment

    可用版本:>= 2.6.0

    时间复杂度:O(1)

    为哈希表key中的域field的值增加增量 increment

    增量也可以为负数,相当于对给定域进行减法操作。

    如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。

    如果域 field 不存在,那么在执行命令前,域的值被初始化为 0

    对一个储存不是字符串值的域 field 执行 HINCRBY 命令将造成一个错误。

    本操作的值被限制在 64 位(bit)有符号数字表示之内。

  • 返回值

    执行 HINCRBY 命令之后,哈希表 key 中域 field 的值。

  • 代码示例

    # increment 为正数
    redis> HEXISTS counter page_view    # 对空域进行设置
    (integer) 0
    
    redis> HINCRBY counter page_view 200
    (integer) 200
    
    redis> HGET counter page_view
    "200"
    
    
    # increment 为负数
    redis> HGET counter page_view
    "200"
    
    redis> HINCRBY counter page_view -50
    (integer) 150
    
    redis> HGET counter page_view
    "150"
    
    
    # 尝试对字符串值的域执行HINCRBY命令
    redis> HSET myhash string hello,world       # 设定一个字符串值
    (integer) 1
    
    redis> HGET myhash string
    "hello,world"
    
    redis> HINCRBY myhash string 1              # 命令执行失败,错误。
    (error) ERR hash value is not an integer
    
    redis> HGET myhash string                   # 原值不变
    "hello,world"
    
HINCRBYFLOAT
  • 语法:HINCRBYFLOAT key field increment

    可用版本:>= 2.6.0

    时间复杂度:O(1)

    为哈希表 key 中的域 field 加上浮点数增量 increment

    如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。

    如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。

    当以下任意一个条件发生时,返回一个错误:

    • field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型)
    • field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number)

    HINCRBYFLOAT 命令的详细功能和 INCRBYFLOAT key increment 命令类似,请查看 INCRBYFLOAT key increment 命令获取更多相关信息。

  • 返回值

    执行加法操作之后 field 域的值。

  • 代码示例

    # 值和增量都是普通小数
    redis> HSET mykey field 10.50
    (integer) 1
    redis> HINCRBYFLOAT mykey field 0.1
    "10.6"
    
    
    # 值和增量都是指数符号
    redis> HSET mykey field 5.0e3
    (integer) 0
    redis> HINCRBYFLOAT mykey field 2.0e2
    "5200"
    
    
    # 对不存在的键执行 HINCRBYFLOAT
    redis> EXISTS price
    (integer) 0
    redis> HINCRBYFLOAT price milk 3.5
    "3.5"
    redis> HGETALL price
    1) "milk"
    2) "3.5"
    
    
    # 对不存在的域进行 HINCRBYFLOAT
    redis> HGETALL price
    1) "milk"
    2) "3.5"
    redis> HINCRBYFLOAT price coffee 4.5   # 新增 coffee 域
    "4.5"
    redis> HGETALL price
    1) "milk"
    2) "3.5"
    3) "coffee"
    4) "4.5"
    
HMSET
  • 语法:HMSET key field value [field value …]

    可用版本:>= 2.0.0

    时间复杂度:O(N), Nfield-value 对的数量。

    同时将多个 field-value (域-值)对设置到哈希表 key 中。

    此命令会覆盖哈希表中已存在的域。

    如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。

  • 返回值

    如果命令执行成功,返回 OK

    key 不是哈希表(hash)类型时,返回一个错误。

  • 代码示例

    redis> HMSET website google www.google.com yahoo www.yahoo.com
    OK
    
    redis> HGET website google
    "www.google.com"
    
    redis> HGET website yahoo
    "www.yahoo.com"
    
HMGET
  • 语法:HMGET key field [field …]

    可用版本:>= 2.0.0

    时间复杂度:O(N), Nfield-value 对的数量。

    返回哈希表 key 中,一个或多个给定域的值。

    如果给定的域不存在于哈希表,那么返回一个 nil 值。

    因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。

  • 返回值

    一个包含多个给定域的关联值的表,表值的排列顺序和给定域参数的请求顺序一样。

  • 代码示例

    redis> HMSET pet dog "doudou" cat "nounou"    # 一次设置多个域
    OK
    
    redis> HMGET pet dog cat fake_pet             # 返回值的顺序和传入参数的顺序一样
    1) "doudou"
    2) "nounou"
    3) (nil)                                      # 不存在的域返回nil值
    
HKEYS
  • 语法:HKEYS key

    可用版本:>= 2.0.0

    时间复杂度:O(N), N 为 哈希表的大小。

    返回哈希表 key 中的所有域。

  • 返回值

    一个包含哈希表中所有域的表。

    key 不存在时,返回一个空表。

  • 代码示例

    # 哈希表非空
    redis> HMSET website google www.google.com yahoo www.yahoo.com
    OK
    
    redis> HKEYS website
    1) "google"
    2) "yahoo"
    
    # 空哈希表/key不存在
    redis> EXISTS fake_key
    (integer) 0
    
    redis> HKEYS fake_key
    (empty list or set)
    
HVALS
  • 语法:HVALS key

    可用版本:>= 2.0.0

    时间复杂度:O(N), N 为 哈希表的大小。

    返回哈希表 key 中所有域的值。

  • 返回值

    一个包含哈希表中所有值的表。

    key 不存在时,返回一个空表。

  • 代码示例

    # 非空哈希表
    redis> HMSET website google www.google.com yahoo www.yahoo.com
    OK
    
    redis> HVALS website
    1) "www.google.com"
    2) "www.yahoo.com"
    
    # 空哈希表/不存在的key
    redis> EXISTS not_exists
    (integer) 0
    
    redis> HVALS not_exists
    (empty list or set)
    
HGETALL
  • 语法:HGETALL key

    可用版本:>= 2.0.0

    时间复杂度:O(N), N 为 哈希表的大小。

    返回哈希表 key 中,所有的域和值。

    在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。

  • 返回值

    以列表形式返回哈希表的域和域的值。

    key 不存在,返回空列表。

  • 代码示例

    redis> HSET people jack "Jack Sparrow"
    (integer) 1
    
    redis> HSET people gump "Forrest Gump"
    (integer) 1
    
    redis> HGETALL people
    1) "jack"          # 域
    2) "Jack Sparrow"  # 值
    3) "gump"
    4) "Forrest Gump"
    
HSCAN
  • 语法:HSCAN key cursor [MATCH pattern] [COUNT count]

    具体信息请参考 [SCAN cursor MATCH pattern] [COUNT count] 命令。

  • 可选参数

  • 返回值

  • 示例

3、列表(LIST)

LPUSH
  • 语法:LPUSH key value [value …]

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    将一个或多个值 value 插入到列表 key表头

    如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,这等同于原子性地执行 LPUSH mylist aLPUSH mylist bLPUSH mylist c 三个命令。

    如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。

    key 存在但不是列表类型时,返回一个错误。

    Note
    在Redis 2.4版本以前的 LPUSH 命令,都只接受单个 value 值。
    
  • 返回值

    执行 LPUSH 命令后,列表的长度。

  • 代码示例

    # 加入单个元素
    redis> LPUSH languages python
    (integer) 1
    
    # 加入重复元素
    redis> LPUSH languages python
    (integer) 2
    
    redis> LRANGE languages 0 -1     # 列表允许重复元素
    1) "python"
    2) "python"
    
    
    # 加入多个元素
    redis> LPUSH mylist a b c
    (integer) 3
    
    redis> LRANGE mylist 0 -1
    1) "c"
    2) "b"
    3) "a"
    
LPUSHX
  • 语法:LPUSHX key value

    可用版本: >= 2.2.0

    时间复杂度: O(1)

    将值 value 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表。

    和 [LPUSH key value value …] 命令相反,key 不存在时, LPUSHX 命令什么也不做。

  • 返回值

    LPUSHX 命令执行之后,表的长度。

  • 代码示例

    # 对空列表执行 LPUSHX
    redis> LLEN greet                   # greet 是一个空列表
    (integer) 0
    
    redis> LPUSHX greet "hello"        # 尝试 LPUSHX,失败,因为列表为空
    (integer) 0
    
    
    # 对非空列表执行 LPUSHX
    redis> LPUSH greet "hello"              # 先用 LPUSH 创建一个有一个元素的列表
    (integer) 1
    
    redis> LPUSHX greet "good morning"      # 这次 LPUSHX 执行成功
    (integer) 2
    
    redis> LRANGE greet 0 -1
    1) "good morning"
    2) "hello"
    
RPUSH
  • 语法:RPUSH key value [value …]

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    将一个或多个值 value 插入到列表 key表尾(最右边)。

    如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,等同于执行命令 RPUSH mylist aRPUSH mylist bRPUSH mylist c

    如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。

    key 存在但不是列表类型时,返回一个错误。

    Note
    在 Redis 2.4 版本以前的 [RPUSH](http://redisdoc.com/list/rpush.html#rpush) 命令,都只接受单个 `value` 值。
    
  • 返回值

    执行 RPUSH 操作后,表的长度。

  • 示例

    # 添加单个元素
    redis> RPUSH languages c
    (integer) 1
    
    
    # 添加重复元素
    redis> RPUSH languages c
    (integer) 2
    
    redis> LRANGE languages 0 -1 # 列表允许重复元素
    1) "c"
    2) "c"
    
    
    # 添加多个元素
    redis> RPUSH mylist a b c
    (integer) 3
    
    redis> LRANGE mylist 0 -1
    1) "a"
    2) "b"
    3) "c"
    
RPUSHX
  • 语法:RPUSHX key value

    可用版本: >= 2.2.0

    时间复杂度: O(1)

    将值 value 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表。

    和 [RPUSH key value value …] 命令相反,当 key 不存在时, RPUSHX 命令什么也不做。

  • 返回值

    RPUSHX 命令执行之后,表的长度。

  • 代码示例

    # key不存在
    redis> LLEN greet
    (integer) 0
    
    redis> RPUSHX greet "hello"     # 对不存在的 key 进行 RPUSHX,PUSH 失败。
    (integer) 0
    
    
    # key 存在且是一个非空列表
    redis> RPUSH greet "hi"         # 先用 RPUSH 插入一个元素
    (integer) 1
    
    redis> RPUSHX greet "hello"     # greet 现在是一个列表类型,RPUSHX 操作成功。
    (integer) 2
    
    redis> LRANGE greet 0 -1
    1) "hi"
    2) "hello"
    
LPOP
  • 语法:LPOP key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    移除并返回列表 key 的头元素。

  • 返回值

    返回移除的列表的头元素。 当 key 不存在时,返回 nil

  • 代码示例

    redis> LLEN course
    (integer) 0
    
    redis> RPUSH course algorithm001
    (integer) 1
    
    redis> RPUSH course c++101
    (integer) 2
    
    redis> LPOP course  # 移除头元素
    "algorithm001"
    
RPOP
  • 语法:RPOP key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    移除并返回列表 key 的尾元素。

  • 返回值

    返回移除的列表的尾元素。 当 key 不存在时,返回 nil

  • 代码示例

    redis> RPUSH mylist "one"
    (integer) 1
    
    redis> RPUSH mylist "two"
    (integer) 2
    
    redis> RPUSH mylist "three"
    (integer) 3
    
    redis> RPOP mylist           # 返回被弹出的元素
    "three"
    
    redis> LRANGE mylist 0 -1    # 列表剩下的元素
    1) "one"
    2) "two"
    
RPOPLPUSH
  • 语法:RPOPLPUSH source destination

    可用版本: >= 1.2.0

    时间复杂度: O(1)

    命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:

    • 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
    • source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。

    举个例子,你有两个列表 sourcedestinationsource 列表有元素 a, b, cdestination 列表有元素 x, y, z ,执行 RPOPLPUSH source destination 之后, source 列表包含元素 a, bdestination 列表包含元素 c, x, y, z ,并且元素 c 会被返回给客户端。

    如果 source 不存在,值 nil 被返回,并且不执行其他动作。

    如果 sourcedestination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。

  • 返回值

    被弹出的元素。

  • 代码示例

    # source 和 destination 不同
    
    redis> LRANGE alpha 0 -1         # 查看所有元素
    1) "a"
    2) "b"
    3) "c"
    4) "d"
    
    redis> RPOPLPUSH alpha reciver   # 执行一次 RPOPLPUSH 看看
    "d"
    
    redis> LRANGE alpha 0 -1
    1) "a"
    2) "b"
    3) "c"
    
    redis> LRANGE reciver 0 -1
    1) "d"
    
    redis> RPOPLPUSH alpha reciver   # 再执行一次,证实 RPOP 和 LPUSH 的位置正确
    "c"
    
    redis> LRANGE alpha 0 -1
    1) "a"
    2) "b"
    
    redis> LRANGE reciver 0 -1
    1) "c"
    2) "d"
    
    
    # source 和 destination 相同
    
    redis> LRANGE number 0 -1
    1) "1"
    2) "2"
    3) "3"
    4) "4"
    
    redis> RPOPLPUSH number number
    "4"
    
    redis> LRANGE number 0 -1           # 4 被旋转到了表头
    1) "4"
    2) "1"
    3) "2"
    4) "3"
    
    redis> RPOPLPUSH number number
    "3"
    
    redis> LRANGE number 0 -1           # 这次是 3 被旋转到了表头
    1) "3"
    2) "4"
    3) "1"
    4) "2" 
    

模式: 安全的队列

Redis的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。一个客户端通过 [LPUSH key value value …] 命令将消息放入队列中,而另一个客户端通过 RPOP key 或者 [BRPOP key key …] timeout 命令取出队列中等待时间最长的消息。

不幸的是,上面的队列方法是**『不安全』**的,因为在这个过程中,一个客户端可能在取出一个消息之后崩溃,而未处理完的消息也就因此丢失。

使用 RPOPLPUSH 命令(或者它的阻塞版本 BRPOPLPUSH source destination timeout )可以解决这个问题:因为它不仅返回一个消息,同时还将这个消息添加到另一个备份列表当中,如果一切正常的话,当一个客户端完成某个消息的处理之后,可以用 LREM key count value 命令将这个消息从备份表删除。

最后,还可以添加一个客户端专门用于监视备份表,它自动地将超过一定处理时限的消息重新放入队列中去(负责处理该消息的客户端可能已经崩溃),这样就不会丢失任何消息了。

模式:循环列表

通过使用相同的 key 作为 RPOPLPUSH 命令的两个参数,客户端可以用一个接一个地获取列表元素的方式,取得列表的所有元素,而不必像 LRANGE key start stop 命令那样一下子将所有列表元素都从服务器传送到客户端中(两种方式的总复杂度都是 O(N))。

以上的模式甚至在以下的两个情况下也能正常工作:

  • 有多个客户端同时对同一个列表进行旋转(rotating),它们获取不同的元素,直到所有元素都被读取完,之后又从头开始。
  • 有客户端在向列表尾部(右边)添加新元素。

这个模式使得我们可以很容易实现这样一类系统:有 N 个客户端,需要连续不断地对一些元素进行处理,而且处理的过程必须尽可能地快。一个典型的例子就是服务器的监控程序:它们需要在尽可能短的时间内,并行地检查一组网站,确保它们的可访问性。

注意,使用这个模式的客户端是易于扩展(scala)且安全(reliable)的,因为就算接收到元素的客户端失败,元素还是保存在列表里面,不会丢失,等到下个迭代来临的时候,别的客户端又可以继续处理这些元素了。

LREM
  • 语法:LREM key count value

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为列表的长度。

    根据参数 count 的值,移除列表中与参数 value 相等的元素。

    count 的值可以是以下几种:

    • count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count
    • count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
    • count = 0 : 移除表中所有与 value 相等的值。
  • 返回值

    被移除元素的数量。 因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0

  • 代码示例

    # 先创建一个表,内容排列是
    # morning hello morning helllo morning
    
    redis> LPUSH greet "morning"
    (integer) 1
    redis> LPUSH greet "hello"
    (integer) 2
    redis> LPUSH greet "morning"
    (integer) 3
    redis> LPUSH greet "hello"
    (integer) 4
    redis> LPUSH greet "morning"
    (integer) 5
    
    redis> LRANGE greet 0 4         # 查看所有元素
    1) "morning"
    2) "hello"
    3) "morning"
    4) "hello"
    5) "morning"
    
    redis> LREM greet 2 morning     # 移除从表头到表尾,最先发现的两个 morning
    (integer) 2                     # 两个元素被移除
    
    redis> LLEN greet               # 还剩 3 个元素
    (integer) 3
    
    redis> LRANGE greet 0 2
    1) "hello"
    2) "hello"
    3) "morning"
    
    redis> LREM greet -1 morning    # 移除从表尾到表头,第一个 morning
    (integer) 1
    
    redis> LLEN greet               # 剩下两个元素
    (integer) 2
    
    redis> LRANGE greet 0 1
    1) "hello"
    2) "hello"
    
    redis> LREM greet 0 hello      # 移除表中所有 hello
    (integer) 2                    # 两个 hello 被移除
    
    redis> LLEN greet
    (integer) 0
    
LLEN
  • 语法:LLEN key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    返回列表 key 的长度。

    如果 key 不存在,则 key 被解释为一个空列表,返回 0 .

    如果 key 不是列表类型,返回一个错误。

  • 返回值

    列表 key 的长度。

  • 代码示例

    # 空列表
    redis> LLEN job
    (integer) 0
    
    
    # 非空列表
    redis> LPUSH job "cook food"
    (integer) 1
    
    redis> LPUSH job "have lunch"
    (integer) 2
    
    redis> LLEN job
    (integer) 2
    
LINDEX
  • 语法:LINDEX key index

    可用版本: >= 1.0.0

    时间复杂度:O(N), N 为到达下标 index 过程中经过的元素数量。因此,对列表的头元素和尾元素执行 LINDEX 命令,复杂度为O(1)。

    返回列表 key 中,下标为 index 的元素。

    下标(index)参数 startstop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。

    你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

    如果 key 不是列表类型,返回一个错误。

  • 返回值

    列表中下标为 index 的元素。 如果 index 参数的值不在列表的区间范围内(out of range),返回 nil

  • 代码示例

    redis> LPUSH mylist "World"
    (integer) 1
    
    redis> LPUSH mylist "Hello"
    (integer) 2
    
    redis> LINDEX mylist 0
    "Hello"
    
    redis> LINDEX mylist -1
    "World"
    
    redis> LINDEX mylist 3        # index不在 mylist 的区间范围内
    (nil)
    
LINSERT
  • 语法:LINSERT key BEFORE|AFTER pivot value

    可用版本: >= 2.2.0

    时间复杂度: O(N), N 为寻找 pivot 过程中经过的元素数量。

    将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。

    pivot 不存在于列表 key 时,不执行任何操作。

    key 不存在时, key 被视为空列表,不执行任何操作。

    如果 key 不是列表类型,返回一个错误。

  • 返回值

    如果命令执行成功,返回插入操作完成之后,列表的长度。 如果没有找到 pivot ,返回 -1 。 如果 key 不存在或为空列表,返回 0

  • 代码示例

    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"
    
    
    # 对一个非空列表插入,查找一个不存在的 pivot
    redis> LINSERT mylist BEFORE "go" "let's"
    (integer) -1                                    # 失败
    
    
    # 对一个空列表执行 LINSERT 命令
    redis> EXISTS fake_list
    (integer) 0
    
    redis> LINSERT fake_list BEFORE "nono" "gogogog"
    (integer) 0                                      # 失败
    
LSET
  • 语法:LSET key index value

    可用版本: >= 1.0.0

    时间复杂度:对头元素或尾元素进行 LSET 操作,复杂度为 O(1)。其他情况下,为 O(N), N 为列表的长度。

    将列表 key 下标为 index 的元素的值设置为 value

    index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。

    关于列表下标的更多信息,请参考 LINDEX key index 命令。

  • 返回值

    操作成功返回 ok ,否则返回错误信息。

  • 代码示例

    # 对空列表(key 不存在)进行 LSET
    redis> EXISTS list
    (integer) 0
    
    redis> LSET list 0 item
    (error) ERR no such key
    
    
    # 对非空列表进行 LSET
    redis> LPUSH job "cook food"
    (integer) 1
    
    redis> LRANGE job 0 0
    1) "cook food"
    
    redis> LSET job 0 "play game"
    OK
    
    redis> LRANGE job  0 0
    1) "play game"
    
    
    # index 超出范围
    redis> LLEN list                    # 列表长度为 1
    (integer) 1
    
    redis> LSET list 3 'out of range'
    (error) ERR index out of range
    
LRANGE
  • 语法:LRANGE key start stop

    可用版本: >= 1.0.0

    时间复杂度: O(S+N), S 为偏移量 startN 为指定区间内元素的数量。

    返回列表 key 中指定区间内的元素,区间以偏移量 startstop 指定。

    下标(index)参数 startstop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。

    你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

    注意LRANGE命令和编程语言区间函数的区别

    假如你有一个包含一百个元素的列表,对该列表执行 LRANGE list 0 10 ,结果是一个包含11个元素的列表,这表明 stop 下标也在 LRANGE 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的 Range.newArray#slice 和Python的 range() 函数。

    超出范围的下标

    超出范围的下标值不会引起错误。

    如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,那么 LRANGE 返回一个空列表

    如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end

  • 返回值

    一个列表,包含指定区间内的元素。

  • 代码示例

    redis> RPUSH fp-language lisp
    (integer) 1
    
    redis> LRANGE fp-language 0 0
    1) "lisp"
    
    redis> RPUSH fp-language scheme
    (integer) 2
    
    redis> LRANGE fp-language 0 1
    1) "lisp"
    2) "scheme"
    
LTRIM
  • 语法:LTRIM key start stop

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为被移除的元素的数量。

    对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

    举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。

    下标(index)参数 startstop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。

    你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

    key 不是列表类型时,返回一个错误。

    LTRIM 命令通常和 [LPUSH key value value …] 命令或 [RPUSH key value value …] 命令配合使用,举个例子:

    LPUSH log newest_log
    LTRIM log 0 99
    

    这个例子模拟了一个日志程序,每次将最新日志 newest_log 放到 log 列表中,并且只保留最新的 100 项。注意当这样使用 LTRIM 命令时,时间复杂度是O(1),因为平均情况下,每次只有一个元素被移除。

    注意LTRIM命令和编程语言区间函数的区别

    假如你有一个包含一百个元素的列表 list ,对该列表执行 LTRIM list 0 10 ,结果是一个包含11个元素的列表,这表明 stop 下标也在 LTRIM 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的 Range.newArray#slice 和Python的 range() 函数。

    超出范围的下标

    超出范围的下标值不会引起错误。

    如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,或者 start > stopLTRIM 返回一个空列表(因为 LTRIM 已经将整个列表清空)。

    如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end

  • 返回值

    命令执行成功时,返回 ok

  • 代码示例

    # 情况 1: 常见情况, start 和 stop 都在列表的索引范围之内
    
    redis> LRANGE alpha 0 -1       # alpha 是一个包含 5 个字符串的列表
    1) "h"
    2) "e"
    3) "l"
    4) "l"
    5) "o"
    
    redis> LTRIM alpha 1 -1        # 删除 alpha 列表索引为 0 的元素
    OK
    
    redis> LRANGE alpha 0 -1       # "h" 被删除了
    1) "e"
    2) "l"
    3) "l"
    4) "o"
    
    
    # 情况 2: stop 比列表的最大下标还要大
    
    
    redis> LTRIM alpha 1 10086     # 保留 alpha 列表索引 1 至索引 10086 上的元素
    OK
    
    redis> LRANGE alpha 0 -1       # 只有索引 0 上的元素 "e" 被删除了,其他元素还在
    1) "l"
    2) "l"
    3) "o"
    
    
    # 情况 3: start 和 stop 都比列表的最大下标要大,并且 start < stop
    
    redis> LTRIM alpha 10086 123321
    OK
    
    redis> LRANGE alpha 0 -1        # 列表被清空
    (empty list or set)
    
    
    # 情况 4: start 和 stop 都比列表的最大下标要大,并且 start > stop
    
    redis> RPUSH new-alpha "h" "e" "l" "l" "o"     # 重新建立一个新列表
    (integer) 5
    
    redis> LRANGE new-alpha 0 -1
    1) "h"
    2) "e"
    3) "l"
    4) "l"
    5) "o"
    
    redis> LTRIM new-alpha 123321 10086    # 执行 LTRIM
    OK
    
    redis> LRANGE new-alpha 0 -1           # 同样被清空
    (empty list or set)
    
BLPOP
  • 语法:BLPOP key [key …] timeout

    可用版本: >= 2.0.0

    时间复杂度: O(1)

    BLPOP 是列表的阻塞式(blocking)弹出原语。

    它是 LPOP key 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。

    当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。

    非阻塞行为

    BLPOP 被调用时,如果给定 key 内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。

    当存在多个给定 key 时, BLPOP 按给定 key 参数排列的先后顺序,依次检查各个列表。

    假设现在有 jobcommandrequest 三个列表,其中 job 不存在, commandrequest 都持有非空列表。考虑以下命令:

    BLPOP job command request 0
    

    BLPOP 保证返回的元素来自 command ,因为它是按”查找 job -> 查找 command -> 查找 request “这样的顺序,第一个找到的非空列表。

    redis> DEL job command request           # 确保key都被删除
    (integer) 0
    
    redis> LPUSH command "update system..."  # 为command列表增加一个值
    (integer) 1
    
    redis> LPUSH request "visit page"        # 为request列表增加一个值
    (integer) 1
    
    redis> BLPOP job command request 0       # job 列表为空,被跳过,紧接着 command 列表的第一个元素被弹出。
    1) "command"                             # 弹出元素所属的列表
    2) "update system..."                    # 弹出元素所属的值
    
    阻塞行为

    如果所有给定 key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接,直到等待超时,或有另一个客户端对给定 key 的任意一个执行 [LPUSH key value value …] 或 [RPUSH key value value …] 命令为止。

    超时参数 timeout 接受一个以为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。

    redis> EXISTS job                # 确保两个 key 都不存在
    (integer) 0
    redis> EXISTS command
    (integer) 0
    
    redis> BLPOP job command 300     # 因为key一开始不存在,所以操作会被阻塞,直到另一客户端对 job 或者 command 列表进行 PUSH 操作。
    1) "job"                         # 这里被 push 的是 job
    2) "do my home work"             # 被弹出的值
    (26.26s)                         # 等待的秒数
    
    redis> BLPOP job command 5       # 等待超时的情况
    (nil)
    (5.66s)                          # 等待的秒数
    
    相同的key被多个客户端同时阻塞

    相同的 key 可以被多个客户端同时阻塞。

    不同的客户端被放进一个队列中,按『先阻塞先服务』(first-BLPOP,first-served)的顺序为 key 执行 BLPOP 命令。

    在MULTI/EXEC事务中的BLPOP

    BLPOP 可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在 MULTI / EXEC 块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行 [LPUSH key value value …] 或 [RPUSH key value value …] 命令。

    因此,一个被包裹在 MULTI / EXEC 块内的 BLPOP 命令,行为表现得就像 LPOP key 一样,对空列表返回 nil ,对非空列表弹出列表元素,不进行任何阻塞操作。

    # 对非空列表进行操作
    redis> RPUSH job programming
    (integer) 1
    
    redis> MULTI
    OK
    
    redis> BLPOP job 30
    QUEUED
    
    redis> EXEC           # 不阻塞,立即返回
    1) 1) "job"
       2) "programming"
    
    
    # 对空列表进行操作
    
    redis> LLEN job      # 空列表
    (integer) 0
    
    redis> MULTI
    OK
    
    redis> BLPOP job 30
    QUEUED
    
    redis> EXEC         # 不阻塞,立即返回
    1) (nil)
    
  • 返回值

    假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 如果列表为空,返回一个 nil 。 否则,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。

BRPOP
  • 语法:BRPOP key [key …] timeout

    可用版本: >= 2.0.0

    时间复杂度: O(1)

    BRPOP 是列表的阻塞式(blocking)弹出原语。

    它是 RPOP key 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。

    当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的尾部元素。

    关于阻塞操作的更多信息,请查看 [BLPOP key key …] timeout 命令, BRPOP 除了弹出元素的位置和 [BLPOP key key …] timeout 不同之外,其他表现一致。

  • 返回值

    假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 反之,如果列表为空,返回一个 nil 。返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。

  • 示例

    redis> LLEN course
    (integer) 0
    
    redis> RPUSH course algorithm001
    (integer) 1
    
    redis> RPUSH course c++101
    (integer) 2
    
    redis> BRPOP course 30
    1) "course"             # 被弹出元素所属的列表键
    2) "c++101"             # 被弹出的元素
    
BRPOPLPUSH
  • 语法:BRPOPLPUSH source destination timeout

    可用版本: >= 2.2.0

    时间复杂度: O(1)

    BRPOPLPUSHRPOPLPUSH source destination 的阻塞版本,当给定列表 source 不为空时, BRPOPLPUSH 的表现和 RPOPLPUSH source destination 一样。

    当列表 source 为空时, BRPOPLPUSH 命令将阻塞连接,直到等待超时,或有另一个客户端对 source 执行 [LPUSH key value value …] 或 [RPUSH key value value …] 命令为止。

    超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。

    更多相关信息,请参考 RPOPLPUSH source destination 命令。

  • 返回值

    假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。

  • 代码示例

    # 非空列表
    redis> BRPOPLPUSH msg reciver 500
    "hello moto"                        # 弹出元素的值
    (3.38s)                             # 等待时长
    
    redis> LLEN reciver
    (integer) 1
    
    redis> LRANGE reciver 0 0
    1) "hello moto"
    
    # 空列表
    redis> BRPOPLPUSH msg reciver 1
    (nil)
    (1.34s)
    

4、集合(SET)

SADD
  • 语法:SADD key member [member …]

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 是被添加的元素的数量。

    将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。

    假如 key 不存在,则创建一个只包含 member 元素作成员的集合。

    key 不是集合类型时,返回一个错误。

    Note
    在Redis2.4版本以前, [SADD]只接受单个'member'值。
    
  • 返回值

    被添加到集合中的新元素的数量,不包括被忽略的元素。

  • 代码示例

    # 添加单个元素
    redis> SADD bbs "discuz.net"
    (integer) 1
    
    # 添加重复元素
    redis> SADD bbs "discuz.net"
    (integer) 0
    
    
    # 添加多个元素
    redis> SADD bbs "tianya.cn" "groups.google.com"
    (integer) 2
    
    redis> SMEMBERS bbs
    1) "discuz.net"
    2) "groups.google.com"
    3) "tianya.cn"
    
SISMEMBER
  • 语法:SISMEMBER key member

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    判断 member 元素是否集合 key 的成员。

  • 返回值

    如果 member 元素是集合的成员,返回 1 。 如果 member 元素不是集合的成员,或 key 不存在,返回 0

  • 代码示例

    redis> SMEMBERS joe's_movies
    1) "hi, lady"
    2) "Fast Five"
    3) "2012"
    
    redis> SISMEMBER joe's_movies "bet man"
    (integer) 0
    
    redis> SISMEMBER joe's_movies "Fast Five"
    (integer) 1
    
SPOP
  • 语法:SPOP key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    移除并返回集合中的一个随机元素

    如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 [SRANDMEMBER key count] 命令。

  • 返回值

    被移除的随机元素。 当 key 不存在或 key 是空集时,返回 nil

  • 代码示例

    redis> SMEMBERS db
    1) "MySQL"
    2) "MongoDB"
    3) "Redis"
    
    redis> SPOP db
    "Redis"
    
    redis> SMEMBERS db
    1) "MySQL"
    2) "MongoDB"
    
    redis> SPOP db
    "MySQL"
    
    redis> SMEMBERS db
    1) "MongoDB"
    
SRANDMEMBER
  • 语法:SRANDMEMBER key [count]

    可用版本: >= 1.0.0

    时间复杂度: 只提供 key 参数时为 O(1) 。如果提供了 count 参数,那么为 O(N) ,N 为返回数组的元素个数。

    如果命令执行时,只提供了 key 参数,那么返回集合中的一个随机元素。

    从 Redis 2.6 版本开始, SRANDMEMBER 命令接受可选的 count 参数:

    • 如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
    • 如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。

    该操作和 SPOP key 相似,但 SPOP key 将随机元素从集合中移除并返回,而 SRANDMEMBER 则仅仅返回随机元素,而不对集合进行任何改动。

  • 返回值

    只提供 key 参数时,返回一个元素;如果集合为空,返回 nil 。 如果提供了 count 参数,那么返回一个数组;如果集合为空,返回空数组。

  • 代码示例

    # 添加元素
    redis> SADD fruit apple banana cherry
    (integer) 3
    
    # 只给定 key 参数,返回一个随机元素
    redis> SRANDMEMBER fruit
    "cherry"
    
    redis> SRANDMEMBER fruit
    "apple"
    
    # 给定 3 为 count 参数,返回 3 个随机元素
    # 每个随机元素都不相同
    redis> SRANDMEMBER fruit 3
    1) "apple"
    2) "banana"
    3) "cherry"
    
    # 给定 -3 为 count 参数,返回 3 个随机元素
    # 元素可能会重复出现多次
    redis> SRANDMEMBER fruit -3
    1) "banana"
    2) "cherry"
    3) "apple"
    
    redis> SRANDMEMBER fruit -3
    1) "apple"
    2) "apple"
    3) "cherry"
    
    # 如果 count 是整数,且大于等于集合基数,那么返回整个集合
    redis> SRANDMEMBER fruit 10
    1) "apple"
    2) "banana"
    3) "cherry"
    
    # 如果 count 是负数,且 count 的绝对值大于集合的基数
    # 那么返回的数组的长度为 count 的绝对值
    redis> SRANDMEMBER fruit -10
    1) "banana"
    2) "apple"
    3) "banana"
    4) "cherry"
    5) "apple"
    6) "apple"
    7) "cherry"
    8) "apple"
    9) "apple"
    10) "banana"
    
    # SRANDMEMBER 并不会修改集合内容
    redis> SMEMBERS fruit
    1) "apple"
    2) "cherry"
    3) "banana"
    
    # 集合为空时返回 nil 或者空数组
    redis> SRANDMEMBER not-exists
    (nil)
    
    redis> SRANDMEMBER not-eixsts 10
    (empty list or set)
    
SREM
  • 语法:SREM key member [member …]

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为给定 member 元素的数量。

    移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。

    key 不是集合类型,返回一个错误。

    Note
    在 Redis 2.4 版本以前, [SREM]只接受单个 `member` 值。
    
  • 返回值

    被成功移除的元素的数量,不包括被忽略的元素。

  • 代码示例

    # 测试数据
    redis> SMEMBERS languages
    1) "c"
    2) "lisp"
    3) "python"
    4) "ruby"
    
    
    # 移除单个元素
    redis> SREM languages ruby
    (integer) 1
    
    
    # 移除不存在元素
    redis> SREM languages non-exists-language
    (integer) 0
    
    # 移除多个元素
    redis> SREM languages lisp python c
    (integer) 3
    
    redis> SMEMBERS languages
    (empty list or set)
    
SMOVE
  • 语法:SMOVE source destination member

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    member 元素从 source 集合移动到 destination 集合。

    SMOVE 是原子性操作。

    如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。

    destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。

    sourcedestination 不是集合类型时,返回一个错误。

  • 返回值

    如果 member 元素被成功移除,返回 1 。 如果 member 元素不是 source 集合的成员,并且没有任何操作对 destination 集合执行,那么返回 0

  • 代码示例

    redis> SMEMBERS songs
    1) "Billie Jean"
    2) "Believe Me"
    
    redis> SMEMBERS my_songs
    (empty list or set)
    
    redis> SMOVE songs my_songs "Believe Me"
    (integer) 1
    
    redis> SMEMBERS songs
    1) "Billie Jean"
    
    redis> SMEMBERS my_songs
    1) "Believe Me"
    
SCARD
  • 语法:SCARD key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    返回集合 key 的基数(集合中元素的数量)。

  • 返回值

    集合的基数。 当 key 不存在时,返回 0

  • 代码示例

    redis> SADD tool pc printer phone
    (integer) 3
    
    redis> SCARD tool   # 非空集合
    (integer) 3
    
    redis> DEL tool
    (integer) 1
    
    redis> SCARD tool   # 空集合
    (integer) 0
    
SMEMBERS
  • 语法:SMEMBERS key

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为集合的基数。

    返回集合 key 中的所有成员。

    不存在的 key 被视为空集合。

  • 返回值

    集合中的所有成员。

  • 代码示例

    # key 不存在或集合为空
    redis> EXISTS not_exists_key
    (integer) 0
    
    redis> SMEMBERS not_exists_key
    (empty list or set)
    
    
    # 非空集合
    redis> SADD language Ruby Python Clojure
    (integer) 3
    
    redis> SMEMBERS language
    1) "Python"
    2) "Ruby"
    3) "Clojure"
    
SSCAN
SINTER
  • 语法:SINTER key [key …]

    可用版本: >= 1.0.0

    时间复杂度: O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。

    返回一个集合的全部成员,该集合是所有给定集合的交集。

    不存在的 key 被视为空集。

    当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)。

  • 返回值

    交集成员的列表。

  • 代码示例

    redis> SMEMBERS group_1
    1) "LI LEI"
    2) "TOM"
    3) "JACK"
    
    redis> SMEMBERS group_2
    1) "HAN MEIMEI"
    2) "JACK"
    
    redis> SINTER group_1 group_2
    1) "JACK"
    
SINTERSTORE
  • 语法:可用版本: >= 1.0.0

    时间复杂度: O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。

    这个命令类似于 [SINTER key key …] 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。

    如果 destination 集合已经存在,则将其覆盖。

    destination 可以是 key 本身。

  • 返回值

    结果集中的成员数量。

  • 代码示例

    redis> SMEMBERS songs
    1) "good bye joe"
    2) "hello,peter"
    
    redis> SMEMBERS my_songs
    1) "good bye joe"
    2) "falling"
    
    redis> SINTERSTORE song_interset songs my_songs
    (integer) 1
    
    redis> SMEMBERS song_interset
    1) "good bye joe"
    
SUNION
  • 语法:SUNION key [key …]

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 是所有给定集合的成员数量之和。

    返回一个集合的全部成员,该集合是所有给定集合的并集。

    不存在的 key 被视为空集。

  • 返回值

    并集成员的列表。

  • 代码示例

    redis> SMEMBERS songs
    1) "Billie Jean"
    
    redis> SMEMBERS my_songs
    1) "Believe Me"
    
    redis> SUNION songs my_songs
    1) "Billie Jean"
    2) "Believe Me"
    
SUNIONSTORE
  • 语法:可用版本: >= 1.0.0

    时间复杂度: O(N), N 是所有给定集合的成员数量之和。

    这个命令类似于 [SUNION key key …] 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。

    如果 destination 已经存在,则将其覆盖。

    destination 可以是 key 本身。

  • 返回值

    结果集中的元素数量。

  • 代码示例

    redis> SMEMBERS NoSQL
    1) "MongoDB"
    2) "Redis"
    
    redis> SMEMBERS SQL
    1) "sqlite"
    2) "MySQL"
    
    redis> SUNIONSTORE db NoSQL SQL
    (integer) 4
    
    redis> SMEMBERS db
    1) "MySQL"
    2) "sqlite"
    3) "MongoDB"
    4) "Redis"
    
SDIFF
  • 语法:SDIFF key [key …]

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 是所有给定集合的成员数量之和。

    返回一个集合的全部成员,该集合是所有给定集合之间的差集。

    不存在的 key 被视为空集。

  • 返回值

    一个包含差集成员的列表。

  • 代码示例

    redis> SMEMBERS peter's_movies
    1) "bet man"
    2) "start war"
    3) "2012"
    
    redis> SMEMBERS joe's_movies
    1) "hi, lady"
    2) "Fast Five"
    3) "2012"
    
    redis> SDIFF peter's_movies joe's_movies
    1) "bet man"
    2) "start war"
    
SDIFFSTORE
  • 语法:SDIFFSTORE destination key [key …]

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 是所有给定集合的成员数量之和。

    这个命令的作用和 [SDIFF key key …] 类似,但它将结果保存到 destination 集合,而不是简单地返回结果集。

    如果 destination 集合已经存在,则将其覆盖。

    destination 可以是 key 本身。

  • 返回值

    结果集中的元素数量。

  • 代码示例

    redis> SMEMBERS joe's_movies
    1) "hi, lady"
    2) "Fast Five"
    3) "2012"
    
    redis> SMEMBERS peter's_movies
    1) "bet man"
    2) "start war"
    3) "2012"
    
    redis> SDIFFSTORE joe_diff_peter joe's_movies peter's_movies
    (integer) 2
    
    redis> SMEMBERS joe_diff_peter
    1) "hi, lady"
    2) "Fast Five"
    

5、有序集合(ZSET)

ZADD
  • 语法:ZADD key score member [[score member] [score member] …]

    可用版本: >= 1.2.0

    时间复杂度: O(M*log(N)), N 是有序集的基数, M 为成功添加的新成员的数量。

    将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

    如果某个 member 已经是有序集的成员,那么更新这个 memberscore 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。

    score 值可以是整数值或双精度浮点数。

    如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。

    key 存在但不是有序集类型时,返回一个错误。

    对有序集的更多介绍请参见 sorted set

    Note
    在 Redis 2.4 版本以前, [ZADD](http://redisdoc.com/sorted_set/zadd.html#zadd) 每次只能添加一个元素。
    
  • 返回值

    被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。

  • 代码示例

    # 添加单个元素
    
    redis> ZADD page_rank 10 google.com
    (integer) 1
    
    
    # 添加多个元素
    
    redis> ZADD page_rank 9 baidu.com 8 bing.com
    (integer) 2
    
    redis> ZRANGE page_rank 0 -1 WITHSCORES
    1) "bing.com"
    2) "8"
    3) "baidu.com"
    4) "9"
    5) "google.com"
    6) "10"
    
    
    # 添加已存在元素,且 score 值不变
    
    redis> ZADD page_rank 10 google.com
    (integer) 0
    
    redis> ZRANGE page_rank 0 -1 WITHSCORES  # 没有改变
    1) "bing.com"
    2) "8"
    3) "baidu.com"
    4) "9"
    5) "google.com"
    6) "10"
    
    
    # 添加已存在元素,但是改变 score 值
    
    redis> ZADD page_rank 6 bing.com
    (integer) 0
    
    redis> ZRANGE page_rank 0 -1 WITHSCORES  # bing.com 元素的 score 值被改变
    1) "bing.com"
    2) "6"
    3) "baidu.com"
    4) "9"
    5) "google.com"
    6) "10"
    
ZSCORE
  • 语法:可用版本: >= 1.2.0

    时间复杂度: O(1)

    返回有序集 key 中,成员 memberscore 值。

    如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil

  • 返回值

    member 成员的 score 值,以字符串形式表示。

  • 代码示例

    redis> ZRANGE salary 0 -1 WITHSCORES    # 测试数据
    1) "tom"
    2) "2000"
    3) "peter"
    4) "3500"
    5) "jack"
    6) "5000"
    
    redis> ZSCORE salary peter              # 注意返回值是字符串
    "3500"
    
ZINCRBY
  • 语法:ZINCRBY key increment member

    可用版本: >= 1.2.0

    时间复杂度: O(log(N))

    为有序集 key 的成员 memberscore 值加上增量 increment

    可以通过传递一个负数值 increment ,让 score 减去相应的值,比如 ZINCRBY key -5 member ,就是让 memberscore 值减去 5

    key 不存在,或 member 不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member

    key 不是有序集类型时,返回一个错误。

    score 值可以是整数值或双精度浮点数。

  • 返回值

    member 成员的新 score 值,以字符串形式表示。

  • 代码示例

    redis> ZSCORE salary tom
    "2000"
    
    redis> ZINCRBY salary 2000 tom   # tom 加薪啦!
    "4000"
    
ZCARD
  • 语法:ZCARD key

    可用版本: >= 1.2.0

    时间复杂度: O(1)

    返回有序集 key 的基数,元素的总数。

  • 返回值

    key 存在且是有序集类型时,返回有序集的基数。 当 key 不存在时,返回 0

  • 代码示例

    redis > ZADD salary 2000 tom    # 添加一个成员
    (integer) 1
    
    redis > ZCARD salary
    (integer) 1
    
    redis > ZADD salary 5000 jack   # 再添加一个成员
    (integer) 1
    
    redis > ZCARD salary
    (integer) 2
    
    redis > EXISTS non_exists_key   # 对不存在的 key 进行 ZCARD 操作
    (integer) 0
    
    redis > ZCARD non_exists_key
    (integer) 0
    
ZCOUNT
  • 语法:ZCOUNT key min max

    可用版本: >= 2.0.0

    时间复杂度: O(log(N)), N 为有序集的基数。

    返回有序集 key 中, score 值在 minmax 之间(默认包括 score 值等于 minmax )的成员的数量。

    关于参数 minmax 的详细使用方法,请参考 [ZRANGEBYSCORE key min max WITHSCORES] [LIMIT offset count] 命令。

  • 返回值

    score 值在 minmax 之间的成员的数量。

  • 代码示例

    redis> ZRANGE salary 0 -1 WITHSCORES    # 测试数据
    1) "jack"
    2) "2000"
    3) "peter"
    4) "3500"
    5) "tom"
    6) "5000"
    
    redis> ZCOUNT salary 2000 5000          # 计算薪水在 2000-5000 之间的人数
    (integer) 3
    
    redis> ZCOUNT salary 3000 5000          # 计算薪水在 3000-5000 之间的人数
    (integer) 2
    
ZRANGE
  • 语法:ZRANGE key start stop [WITHSCORES]

    可用版本: >= 1.2.0

    时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。

    返回有序集 key 中,指定区间内的成员。

    其中成员的位置按 score 值递增(从小到大)来排序。

    具有相同 score 值的成员按字典序(lexicographical order )来排列。

    如果你需要成员按 score 值递减(从大到小)来排列,请使用 [ZREVRANGE key start stop WITHSCORES] 命令。

    下标参数 startstop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。 你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。

    超出范围的下标并不会引起错误。 比如说,当 start 的值比有序集的最大下标还要大,或是 start > stop 时, ZRANGE 命令只是简单地返回一个空列表。 另一方面,假如 stop 参数的值比有序集的最大下标还要大,那么 Redis 将 stop 当作最大下标来处理。

    可以通过使用 WITHSCORES 选项,来让成员和它的 score 值一并返回,返回列表以 value1,score1, ..., valueN,scoreN 的格式表示。 客户端库可能会返回一些更复杂的数据类型,比如数组、元组等。

  • 返回值

    指定区间内,带有 score 值(可选)的有序集成员的列表。

  • 代码示例

    redis > ZRANGE salary 0 -1 WITHSCORES             # 显示整个有序集成员
    1) "jack"
    2) "3500"
    3) "tom"
    4) "5000"
    5) "boss"
    6) "10086"
    
    redis > ZRANGE salary 1 2 WITHSCORES              # 显示有序集下标区间 1 至 2 的成员
    1) "tom"
    2) "5000"
    3) "boss"
    4) "10086"
    
    redis > ZRANGE salary 0 200000 WITHSCORES         # 测试 end 下标超出最大下标时的情况
    1) "jack"
    2) "3500"
    3) "tom"
    4) "5000"
    5) "boss"
    6) "10086"
    
    redis > ZRANGE salary 200000 3000000 WITHSCORES   # 测试当给定区间不存在于有序集时的情况
    (empty list or set)
    
ZREVRANGE
  • 语法:ZREVRANGE key start stop [WITHSCORES]

    可用版本: >= 1.2.0

    时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。

    返回有序集 key 中,指定区间内的成员。

    其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。

    除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 [ZRANGE key start stop WITHSCORES] 命令一样。

  • 返回值

    指定区间内,带有 score 值(可选)的有序集成员的列表。

  • 代码示例

    redis> ZRANGE salary 0 -1 WITHSCORES        # 递增排列
    1) "peter"
    2) "3500"
    3) "tom"
    4) "4000"
    5) "jack"
    6) "5000"
    
    redis> ZREVRANGE salary 0 -1 WITHSCORES     # 递减排列
    1) "jack"
    2) "5000"
    3) "tom"
    4) "4000"
    5) "peter"
    6) "3500"
    
ZRANGEBYSCORE
  • 语法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

    可用版本: >= 1.0.5

    时间复杂度: O(log(N)+M), N 为有序集的基数, M 为被结果集的基数。

    返回有序集 key 中,所有 score 值介于 minmax 之间(包括等于 minmax )的成员。有序集成员按 score 值递增(从小到大)次序排列。

    具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的,不需要额外的计算)。

    可选的 LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count ),注意当 offset 很大时,定位 offset 的操作可能需要遍历整个有序集,此过程最坏复杂度为 O(N) 时间。

    可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 score 值一起返回。 该选项自 Redis 2.0 版本起可用。

    区间及无限

    minmax 可以是 -inf+inf ,这样一来,你就可以在不知道有序集的最低和最高 score 值的情况下,使用 ZRANGEBYSCORE 这类命令。

    默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。

    举个例子:

    ZRANGEBYSCORE zset (1 5
    

    返回所有符合条件 1 < score <= 5 的成员,而

    ZRANGEBYSCORE zset (5 (10
    

    则返回所有符合条件 5 < score < 10 的成员。

  • 返回值

    指定区间内,带有 score 值(可选)的有序集成员的列表。

  • 代码示例

    redis> ZADD salary 2500 jack                        # 测试数据
    (integer) 0
    redis> ZADD salary 5000 tom
    (integer) 0
    redis> ZADD salary 12000 peter
    (integer) 0
    
    redis> ZRANGEBYSCORE salary -inf +inf               # 显示整个有序集
    1) "jack"
    2) "tom"
    3) "peter"
    
    redis> ZRANGEBYSCORE salary -inf +inf WITHSCORES    # 显示整个有序集及成员的 score 值
    1) "jack"
    2) "2500"
    3) "tom"
    4) "5000"
    5) "peter"
    6) "12000"
    
    redis> ZRANGEBYSCORE salary -inf 5000 WITHSCORES    # 显示工资 <=5000 的所有成员
    1) "jack"
    2) "2500"
    3) "tom"
    4) "5000"
    
    redis> ZRANGEBYSCORE salary (5000 400000            # 显示工资大于 5000 小于等于 400000 的成员
    1) "peter"
    
ZREVRANGEBYSCORE
  • 语法:ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

    可用版本: >= 2.2.0

    时间复杂度: O(log(N)+M), N 为有序集的基数, M 为结果集的基数。

    返回有序集 key 中, score 值介于 maxmin 之间(默认包括等于 maxmin )的所有的成员。有序集成员按 score 值递减(从大到小)的次序排列。

    具有相同 score 值的成员按字典序的逆序(reverse lexicographical order )排列。

    除了成员按 score 值递减的次序排列这一点外, ZREVRANGEBYSCORE 命令的其他方面和 [ZRANGEBYSCORE key min max WITHSCORES] [LIMIT offset count] 命令一样。

  • 返回值

    指定区间内,带有 score 值(可选)的有序集成员的列表。

  • 代码示例

    redis > ZADD salary 10086 jack
    (integer) 1
    redis > ZADD salary 5000 tom
    (integer) 1
    redis > ZADD salary 7500 peter
    (integer) 1
    redis > ZADD salary 3500 joe
    (integer) 1
    
    redis > ZREVRANGEBYSCORE salary +inf -inf   # 逆序排列所有成员
    1) "jack"
    2) "peter"
    3) "tom"
    4) "joe"
    
    redis > ZREVRANGEBYSCORE salary 10000 2000  # 逆序排列薪水介于 10000 和 2000 之间的成员
    1) "peter"
    2) "tom"
    3) "joe"
    
ZRANK
  • 语法:ZRANK key member

    可用版本: >= 2.0.0

    时间复杂度: O(log(N))

    返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。

    排名以 0 为底,也就是说, score 值最小的成员排名为 0

    使用 ZREVRANK key member 命令可以获得成员按 score 值递减(从大到小)排列的排名。

  • 返回值

    如果 member 是有序集 key 的成员,返回 member 的排名。 如果 member 不是有序集 key 的成员,返回 nil

  • 代码示例

    redis> ZRANGE salary 0 -1 WITHSCORES        # 显示所有成员及其 score 值
    1) "peter"
    2) "3500"
    3) "tom"
    4) "4000"
    5) "jack"
    6) "5000"
    
    redis> ZRANK salary tom                     # 显示 tom 的薪水排名,第二
    (integer) 1
    
ZREVRANK
  • 语法:ZREVRANK key member

    可用版本: >= 2.0.0

    时间复杂度: O(log(N))

    返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。

    排名以 0 为底,也就是说, score 值最大的成员排名为 0

    使用 ZRANK key member 命令可以获得成员按 score 值递增(从小到大)排列的排名。

  • 返回值

    如果 member 是有序集 key 的成员,返回 member 的排名。 如果 member 不是有序集 key 的成员,返回 nil

  • 代码示例

    redis 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES     # 测试数据
    1) "jack"
    2) "2000"
    3) "peter"
    4) "3500"
    5) "tom"
    6) "5000"
    
    redis> ZREVRANK salary peter     # peter 的工资排第二
    (integer) 1
    
    redis> ZREVRANK salary tom       # tom 的工资最高
    (integer) 0
    
ZREM
  • 语法:ZREM key member [member …]

    可用版本: >= 1.2.0

    时间复杂度: O(M*log(N)), N 为有序集的基数, M 为被成功移除的成员的数量。

    移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。

    key 存在但不是有序集类型时,返回一个错误。

    Note

    在 Redis 2.4 版本以前, ZREM 每次只能删除一个元素。

  • 返回值

    被成功移除的成员的数量,不包括被忽略的成员。

  • 代码示例

    # 测试数据
    redis> ZRANGE page_rank 0 -1 WITHSCORES
    1) "bing.com"
    2) "8"
    3) "baidu.com"
    4) "9"
    5) "google.com"
    6) "10"
    
    
    # 移除单个元素
    redis> ZREM page_rank google.com
    (integer) 1
    
    redis> ZRANGE page_rank 0 -1 WITHSCORES
    1) "bing.com"
    2) "8"
    3) "baidu.com"
    4) "9"
    
    
    # 移除多个元素
    redis> ZREM page_rank baidu.com bing.com
    (integer) 2
    
    redis> ZRANGE page_rank 0 -1 WITHSCORES
    (empty list or set)
    
    
    # 移除不存在元素
    redis> ZREM page_rank non-exists-element
    (integer) 0
    
ZREMRANGEBYRANK
  • 语法:ZREMRANGEBYRANK key start stop

    可用版本: >= 2.0.0

    时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为被移除成员的数量。

    移除有序集 key 中,指定排名(rank)区间内的所有成员。

    区间分别以下标参数 startstop 指出,包含 startstop 在内。

    下标参数 startstop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。 你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。

  • 返回值

    被移除成员的数量。

  • 代码示例

    redis> ZADD salary 2000 jack
    (integer) 1
    redis> ZADD salary 5000 tom
    (integer) 1
    redis> ZADD salary 3500 peter
    (integer) 1
    
    redis> ZREMRANGEBYRANK salary 0 1       # 移除下标 0 至 1 区间内的成员
    (integer) 2
    
    redis> ZRANGE salary 0 -1 WITHSCORES    # 有序集只剩下一个成员
    1) "tom"
    2) "5000"
    
ZREMRANGEBYSCORE
  • 语法:ZREMRANGEBYSCORE key min max

    可用版本: >= 1.2.0

    时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为被移除成员的数量。

    移除有序集 key 中,所有 score 值介于 minmax 之间(包括等于 minmax )的成员。

    自版本2.1.6开始, score 值等于 minmax 的成员也可以不包括在内,详情请参见 [ZRANGEBYSCORE key min max WITHSCORES] [LIMIT offset count] 命令。

  • 返回值

    被移除成员的数量。

  • 代码示例

    redis> ZRANGE salary 0 -1 WITHSCORES          # 显示有序集内所有成员及其 score 值
    1) "tom"
    2) "2000"
    3) "peter"
    4) "3500"
    5) "jack"
    6) "5000"
    
    redis> ZREMRANGEBYSCORE salary 1500 3500      # 移除所有薪水在 1500 到 3500 内的员工
    (integer) 2
    
    redis> ZRANGE salary 0 -1 WITHSCORES          # 剩下的有序集成员
    1) "jack"
    2) "5000"
    
ZRANGEBYLEX
  • 语法:ZRANGEBYLEX key min max [LIMIT offset count]

    可用版本: >= 2.8.9

    时间复杂度:O(log(N)+M), 其中 N 为有序集合的元素数量, 而 M 则是命令返回的元素数量。 如果 M 是一个常数(比如说,用户总是使用 LIMIT 参数来返回最先的 10 个元素), 那么命令的复杂度也可以看作是 O(log(N)) 。

    当有序集合的所有成员都具有相同的分值时, 有序集合的元素会根据成员的字典序(lexicographical ordering)来进行排序, 而这个命令则可以返回给定的有序集合键 key 中, 值介于 minmax 之间的成员。

    如果有序集合里面的成员带有不同的分值, 那么命令返回的结果是未指定的(unspecified)。

    命令会使用 C 语言的 memcmp() 函数, 对集合中的每个成员进行逐个字节的对比(byte-by-byte compare), 并按照从低到高的顺序, 返回排序后的集合成员。 如果两个字符串有一部分内容是相同的话, 那么命令会认为较长的字符串比较短的字符串要大。

    可选的 LIMIT offset count 参数用于获取指定范围内的匹配元素 (就像 SQL 中的 SELECT LIMIT offset count 语句)。 需要注意的一点是, 如果 offset 参数的值非常大的话, 那么命令在返回结果之前, 需要先遍历至 offset 所指定的位置, 这个操作会为命令加上最多 O(N) 复杂度。

    如何指定范围区间

    合法的 minmax 参数必须包含 ( 或者 [ , 其中 ( 表示开区间(指定的值不会被包含在范围之内), 而 [ 则表示闭区间(指定的值会被包含在范围之内)。

    特殊值 +-min 参数以及 max 参数中具有特殊的意义, 其中 + 表示正无限, 而 - 表示负无限。 因此, 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX <zset> - + , 命令将返回有序集合中的所有元素。

  • 返回值

    数组回复:一个列表,列表里面包含了有序集合在指定范围内的成员。

  • 代码示例

    redis> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g
    (integer) 7
    
    redis> ZRANGEBYLEX myzset - [c
    1) "a"
    2) "b"
    3) "c"
    
    redis> ZRANGEBYLEX myzset - (c
    1) "a"
    2) "b"
    
    redis> ZRANGEBYLEX myzset [aaa (g
    1) "b"
    2) "c"
    3) "d"
    4) "e"
    5) "f"
    
ZLEXCOUNT
  • 语法:ZLEXCOUNT key min max

    可用版本: >= 2.8.9

    时间复杂度: O(log(N)),其中 N 为有序集合包含的元素数量。

    对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会返回该集合中, 成员介于 minmax 范围内的元素数量。

    这个命令的 min 参数和 max 参数的意义和 [ZRANGEBYLEX key min max LIMIT offset count] 命令的 min 参数和 max 参数的意义一样。

  • 返回值

    整数回复:指定范围内的元素数量。

  • 代码示例

    redis> ZADD myzset 0 a 0 b 0 c 0 d 0 e
    (integer) 5
    
    redis> ZADD myzset 0 f 0 g
    (integer) 2
    
    redis> ZLEXCOUNT myzset - +
    (integer) 7
    
    redis> ZLEXCOUNT myzset [b [f
    (integer) 5
    
ZREMRANGEBYLEX
  • 语法:ZREMRANGEBYLEX key min max

    可用版本: >= 2.8.9

    时间复杂度: O(log(N)+M), 其中 N 为有序集合的元素数量, 而 M 则为被移除的元素数量。

    对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会移除该集合中, 成员介于 minmax 范围内的所有元素。

    这个命令的 min 参数和 max 参数的意义和 [ZRANGEBYLEX key min max LIMIT offset count] 命令的 min 参数和 max 参数的意义一样。

  • 返回值

    整数回复:被移除的元素数量。

  • 代码示例

    redis> ZADD myzset 0 aaaa 0 b 0 c 0 d 0 e
    (integer) 5
    
    redis> ZADD myzset 0 foo 0 zap 0 zip 0 ALPHA 0 alpha
    (integer) 5
    
    redis> ZRANGE myzset 0 -1
    1) "ALPHA"
    2) "aaaa"
    3) "alpha"
    4) "b"
    5) "c"
    6) "d"
    7) "e"
    8) "foo"
    9) "zap"
    10) "zip"
    
    redis> ZREMRANGEBYLEX myzset [alpha [omega
    (integer) 6
    
    redis> ZRANGE myzset 0 -1
    1) "ALPHA"
    2) "aaaa"
    3) "zap"
    4) "zip"
    
ZSCAN
ZUNIONSTORE
  • 语法:ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]

    可用版本:>= 2.0.0

    时间复杂度: O(N)+O(M log(M)), N 为给定有序集基数的总和, M 为结果集的基数。

    计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination

    默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之

    WEIGHTS

    使用 WEIGHTS 选项,你可以为 每个 给定有序集 分别 指定一个乘法因子(multiplication factor),每个给定有序集的所有成员的 score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子。

    如果没有指定 WEIGHTS 选项,乘法因子默认设置为 1

    AGGREGATE

    使用 AGGREGATE 选项,你可以指定并集的结果集的聚合方式。

    默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 作为结果集中该成员的 score 值;使用参数 MIN ,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。

  • 返回值

    保存到 destination 的结果集的基数。

  • 代码示例

    redis> ZRANGE programmer 0 -1 WITHSCORES
    1) "peter"
    2) "2000"
    3) "jack"
    4) "3500"
    5) "tom"
    6) "5000"
    
    redis> ZRANGE manager 0 -1 WITHSCORES
    1) "herry"
    2) "2000"
    3) "mary"
    4) "3500"
    5) "bob"
    6) "4000"
    
    redis> ZUNIONSTORE salary 2 programmer manager WEIGHTS 1 3   # 公司决定加薪。。。除了程序员。。。
    (integer) 6
    
    redis> ZRANGE salary 0 -1 WITHSCORES
    1) "peter"
    2) "2000"
    3) "jack"
    4) "3500"
    5) "tom"
    6) "5000"
    7) "herry"
    8) "6000"
    9) "mary"
    10) "10500"
    11) "bob"
    12) "12000"
    
ZINTERSTORE
  • 语法:ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]

    可用版本: >= 2.0.0

    时间复杂度: O(NK)+O(Mlog(M)), N 为给定 key 中基数最小的有序集, K 为给定有序集的数量, M 为结果集的基数。

    计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination

    默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和.

    关于 WEIGHTSAGGREGATE 选项的描述,参见 [ZUNIONSTORE destination numkeys key key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] 命令。

  • 返回值

    保存到 destination 的结果集的基数。

  • 代码示例

    redis > ZADD mid_test 70 "Li Lei"
    (integer) 1
    redis > ZADD mid_test 70 "Han Meimei"
    (integer) 1
    redis > ZADD mid_test 99.5 "Tom"
    (integer) 1
    
    redis > ZADD fin_test 88 "Li Lei"
    (integer) 1
    redis > ZADD fin_test 75 "Han Meimei"
    (integer) 1
    redis > ZADD fin_test 99.5 "Tom"
    (integer) 1
    
    redis > ZINTERSTORE sum_point 2 mid_test fin_test
    (integer) 3
    
    redis > ZRANGE sum_point 0 -1 WITHSCORES     # 显示有序集内所有成员及其 score 值
    1) "Han Meimei"
    2) "145"
    3) "Li Lei"
    4) "158"
    5) "Tom"
    6) "199"
    

6、数据库命令

EXISTS
  • 语法:EXISTS key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    检查给定 key 是否存在。

  • 返回值

    key 存在,返回 1 ,否则返回 0

  • 代码示例

    redis> SET db "redis"
    OK
    
    redis> EXISTS db
    (integer) 1
    
    redis> DEL db
    (integer) 1
    
    redis> EXISTS db
    (integer) 0
    
TYPE
  • 语法:TYPE key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    返回 key 所储存的值的类型。

  • string (字符串)

  • list (列表)

  • set (集合)

  • zset (有序集)

  • hash (哈希表)

  • stream (流)返回值

  • 返回值

    • none (key不存在)
    • string (字符串)
    • list (列表)
    • set (集合)
    • zset (有序集)
    • hash (哈希表)
    • stream (流)
  • 代码示例

    # 字符串
    redis> SET weather "sunny"
    OK
    
    redis> TYPE weather
    string
    
    # 列表
    redis> LPUSH book_list "programming in scala"
    (integer) 1
    
    redis> TYPE book_list
    list
    
    # 集合
    redis> SADD pat "dog"
    (integer) 1
    
    redis> TYPE pat
    set
    
RENAME
  • 语法:RENAME key newkey

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    key 改名为 newkey

    keynewkey 相同,或者 key 不存在时,返回一个错误。

    newkey 已经存在时, RENAME 命令将覆盖旧值。

  • 返回值

    改名成功时提示 OK ,失败时候返回一个错误。

  • 代码示例

    # key 存在且 newkey 不存在
    
    redis> SET message "hello world"
    OK
    
    redis> RENAME message greeting
    OK
    
    redis> EXISTS message               # message 不复存在
    (integer) 0
    
    redis> EXISTS greeting              # greeting 取而代之
    (integer) 1
    
    
    # 当 key 不存在时,返回错误
    
    redis> RENAME fake_key never_exists
    (error) ERR no such key
    
    
    # newkey 已存在时, RENAME 会覆盖旧 newkey
    
    redis> SET pc "lenovo"
    OK
    
    redis> SET personal_computer "dell"
    OK
    
    redis> RENAME pc personal_computer
    OK
    
    redis> GET pc
    (nil)
    
    redis:1> GET personal_computer      # 原来的值 dell 被覆盖了
    "lenovo"
    
RENAMENX
  • 语法:RENAMENX key newkey

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    当且仅当 newkey 不存在时,将 key 改名为 newkey

    key 不存在时,返回一个错误。

  • 返回值

    修改成功时,返回 1 ; 如果 newkey 已经存在,返回 0

  • 代码示例

    # newkey 不存在,改名成功
    
    redis> SET player "MPlyaer"
    OK
    
    redis> EXISTS best_player
    (integer) 0
    
    redis> RENAMENX player best_player
    (integer) 1
    
    
    # newkey存在时,失败
    
    redis> SET animal "bear"
    OK
    
    redis> SET favorite_animal "butterfly"
    OK
    
    redis> RENAMENX animal favorite_animal
    (integer) 0
    
    redis> get animal
    "bear"
    
    redis> get favorite_animal
    "butterfly"
    
MOVE
  • 语法:MOVE key db

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    将当前数据库的 key 移动到给定的数据库 db 当中。

    如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。

    因此,也可以利用这一特性,MOVE 当作锁(locking)原语(primitive)

  • 返回值

    移动成功返回 1 ,失败则返回 0

  • 代码示例

    # key 存在于当前数据库
    
    redis> SELECT 0                             # redis默认使用数据库 0,为了清晰起见,这里再显式指定一次。
    OK
    
    redis> SET song "secret base - Zone"
    OK
    
    redis> MOVE song 1                          # 将 song 移动到数据库 1
    (integer) 1
    
    redis> EXISTS song                          # song 已经被移走
    (integer) 0
    
    redis> SELECT 1                             # 使用数据库 1
    OK
    
    redis:1> EXISTS song                        # 证实 song 被移到了数据库 1 (注意命令提示符变成了"redis:1",表明正在使用数据库 1)
    (integer) 1
    
    
    # 当 key 不存在的时候
    
    redis:1> EXISTS fake_key
    (integer) 0
    
    redis:1> MOVE fake_key 0                    # 试图从数据库 1 移动一个不存在的 key 到数据库 0,失败
    (integer) 0
    
    redis:1> select 0                           # 使用数据库0
    OK
    
    redis> EXISTS fake_key                      # 证实 fake_key 不存在
    (integer) 0
    
    
    # 当源数据库和目标数据库有相同的 key 时
    
    redis> SELECT 0                             # 使用数据库0
    OK
    redis> SET favorite_fruit "banana"
    OK
    
    redis> SELECT 1                             # 使用数据库1
    OK
    redis:1> SET favorite_fruit "apple"
    OK
    
    redis:1> SELECT 0                           # 使用数据库0,并试图将 favorite_fruit 移动到数据库 1
    OK
    
    redis> MOVE favorite_fruit 1                # 因为两个数据库有相同的 key,MOVE 失败
    (integer) 0
    
    redis> GET favorite_fruit                   # 数据库 0 的 favorite_fruit 没变
    "banana"
    
    redis> SELECT 1
    OK
    
    redis:1> GET favorite_fruit                 # 数据库 1 的 favorite_fruit 也是
    "apple"
    
DEL
  • 语法:DEL key [key …]

    可用版本: >= 1.0.0

    时间复杂度:O(N), N 为被删除的 key 的数量,其中删除单个字符串类型的 key ,时间复杂度为O(1);删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M 为以上数据结构内的元素数量。

    删除给定的一个或多个 key

    不存在的 key 会被忽略。

  • 返回值

    被删除 key 的数量。

  • 代码示例

    #  删除单个 key
    
    redis> SET name huangz
    OK
    
    redis> DEL name
    (integer) 1
    
    
    # 删除一个不存在的 key
    
    redis> EXISTS phone
    (integer) 0
    
    redis> DEL phone # 失败,没有 key 被删除
    (integer) 0
    
    
    # 同时删除多个 key
    
    redis> SET name "redis"
    OK
    
    redis> SET type "key-value store"
    OK
    
    redis> SET website "redis.com"
    OK
    
    redis> DEL name type website
    (integer) 3
    
RANDOMKEY
  • 语法:RANDOMKEY

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    从当前数据库中随机返回(不删除)一个 key

  • 返回值

    当数据库不为空时,返回一个 key 。 当数据库为空时,返回 nil

  • 代码示例

    # 数据库不为空
    
    redis> MSET fruit "apple" drink "beer" food "cookies"   # 设置多个 key
    OK
    
    redis> RANDOMKEY
    "fruit"
    
    redis> RANDOMKEY
    "food"
    
    redis> KEYS *    # 查看数据库内所有key,证明 RANDOMKEY 并不删除 key
    1) "food"
    2) "drink"
    3) "fruit"
    
    
    # 数据库为空
    
    redis> FLUSHDB  # 删除当前数据库所有 key
    OK
    
    redis> RANDOMKEY
    (nil)
    
DBSIZE
  • 语法:DBSIZE

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    返回当前数据库的 key 的数量。

  • 返回值

    当前数据库的 key 的数量。

  • 代码示例

    redis> DBSIZE
    (integer) 5
    
    redis> SET new_key "hello_moto"     # 增加一个 key 试试
    OK
    
    redis> DBSIZE
    (integer) 6
    
KEYS
  • 语法:KEYS pattern

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为数据库中 key 的数量。

    查找所有符合给定模式 patternkey , 比如说:

    • KEYS * 匹配数据库中所有 key
    • KEYS h?llo 匹配 hellohallohxllo 等。
    • KEYS h*llo 匹配 hlloheeeeello 等。
    • KEYS h[ae]llo 匹配 hellohallo ,但不匹配 hillo

    特殊符号用 \ 隔开。

    Warning
    KEYS的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 `key` ,你最好还是用 Redis 的集合结构(set)来代替。
    
  • 返回值

    符合给定模式的 key 列表。

  • 代码示例

    redis> MSET one 1 two 2 three 3 four 4  # 一次设置 4 个 key
    OK
    
    redis> KEYS *o*
    1) "four"
    2) "two"
    3) "one"
    
    redis> KEYS t??
    1) "two"
    
    redis> KEYS t[w]*
    1) "two"
    
    redis> KEYS *  # 匹配数据库内所有 key
    1) "four"
    2) "three"
    3) "two"
    4) "one"
    
SCAN
SORT
FLUSHDB
  • 语法:FLUSHDB

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    清空当前数据库中的所有 key。

    此命令从不失败。

  • 返回值

    总是返回 OK

  • 代码示例

    redis> DBSIZE    # 清空前的 key 数量
    (integer) 4
    
    redis> FLUSHDB
    OK
    
    redis> DBSIZE    # 清空后的 key 数量
    (integer) 0
    
FLUSHALL
  • 语法:FLUSHALL

    可用版本: >= 1.0.0

    时间复杂度: O(N)

    清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。

    此命令从不失败。

  • 返回值

    总是返回 OK

  • 代码示例

    redis> DBSIZE            # 0 号数据库的 key 数量
    (integer) 9
    
    redis> SELECT 1          # 切换到 1 号数据库
    OK
    
    redis[1]> DBSIZE         # 1 号数据库的 key 数量
    (integer) 6
    
    redis[1]> flushall       # 清空所有数据库的所有 key
    OK
    
    redis[1]> DBSIZE         # 不但 1 号数据库被清空了
    (integer) 0
    
    redis[1]> SELECT 0       # 0 号数据库(以及其他所有数据库)也一样
    OK
    
    redis> DBSIZE
    (integer) 0
    
SELECT
  • 语法:SELECT index

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值。

    默认使用 0 号数据库。

  • 返回值

    OK
    
  • 代码示例

    redis> SET db_number 0         # 默认使用 0 号数据库
    OK
    
    redis> SELECT 1                # 使用 1 号数据库
    OK
    
    redis[1]> GET db_number        # 已经切换到 1 号数据库,注意 Redis 现在的命令提示符多了个 [1]
    (nil)
    
    redis[1]> SET db_number 1
    OK
    
    redis[1]> GET db_number
    "1"
    
    redis[1]> SELECT 3             # 再切换到 3 号数据库
    OK
    
    redis[3]>                      # 提示符从 [1] 改变成了 [3]
    
SWAPDB
  • 语法:SWAPDB db1 db2

    版本要求: >= 4.0.0

    时间复杂度: O(1)

    对换指定的两个数据库, 使得两个数据库的数据立即互换。

  • 返回值

    OK
    
  • 代码示例

    # 对换数据库 0 和数据库 1
    redis> SWAPDB 0 1
    OK
    

7、自动过期

EXPIRE
  • 语法:EXPIRE key seconds

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。

    在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。

    生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SETGETSET 命令覆写(overwrite),这意味着,如果一个命令只是修改(alter)一个带生存时间的 key 的值而不是用一个新的 key 值来代替(replace)它的话,那么生存时间不会被改变。

    比如说,对一个 key 执行 INCR 命令,对一个列表进行 LPUSH 命令,或者对一个哈希表执行 HSET 命令,这类操作都不会修改 key 本身的生存时间。

    另一方面,如果使用 RENAME 对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。

    RENAME 命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。

    使用 PERSIST 命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个『持久的』(persistent) key

    更新生存时间

    可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。

    过期时间的精确度

    在 Redis 2.4 版本中,过期时间的延迟在 1 秒钟之内 —— 也即是,就算 key 已经过期,但它还是可能在过期之后一秒钟之内被访问到,而在新的 Redis 2.6 版本中,延迟被降低到 1 毫秒之内。

    Redis 2.1.3 之前的不同之处

    在 Redis 2.1.3 之前的版本中,修改一个带有生存时间的 key 会导致整个 key 被删除,这一行为是受当时复制(replication)层的限制而作出的,现在这一限制已经被修复。

  • 返回值

    设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0

  • 代码示例

    redis> SET cache_page "www.google.com"
    OK
    
    redis> EXPIRE cache_page 30  # 设置过期时间为 30 秒
    (integer) 1
    
    redis> TTL cache_page    # 查看剩余生存时间
    (integer) 23
    
    redis> EXPIRE cache_page 30000   # 更新过期时间
    (integer) 1
    
    redis> TTL cache_page
    (integer) 29996
    

    模式:导航会话

    假设你有一项 web 服务,打算根据用户最近访问的 N 个页面来进行物品推荐,并且假设用户停止阅览超过 60 秒,那么就清空阅览记录(为了减少物品推荐的计算量,并且保持推荐物品的新鲜度)。

    这些最近访问的页面记录,我们称之为『导航会话』(Navigation session),可以用 INCRRPUSH 命令在 Redis 中实现它:每当用户阅览一个网页的时候,执行以下代码:

    MULTI
        RPUSH pagewviews.user:<userid> http://.....
        EXPIRE pagewviews.user:<userid> 60
    EXEC
    

    如果用户停止阅览超过 60 秒,那么它的导航会话就会被清空,当用户重新开始阅览的时候,系统又会重新记录导航会话,继续进行物品推荐。

EXPIREAT
  • 语法:EXPIREAT key timestamp

    可用版本: >= 1.2.0

    时间复杂度: O(1)

    EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。

    不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。

  • 返回值

    如果生存时间设置成功,返回 1 ; 当 key 不存在或没办法设置生存时间,返回 0

  • 代码示例

    redis> SET cache www.google.com
    OK
    
    redis> EXPIREAT cache 1355292000     # 这个 key 将在 2012.12.12 过期
    (integer) 1
    
    redis> TTL cache
    (integer) 45081860
    
TTL
  • 语法:TTL key

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。

  • 返回值

    key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。

  • 代码示例

    # 不存在的 key
    redis> FLUSHDB
    OK
    
    redis> TTL key
    (integer) -2
    
    # key 存在,但没有设置剩余生存时间
    redis> SET key value
    OK
    
    redis> TTL key
    (integer) -1
    
    # 有剩余生存时间的 key
    redis> EXPIRE key 10086
    (integer) 1
    
    redis> TTL key
    (integer) 10084
    
PERSIST
  • 语法:PERSIST key

    可用版本: >= 2.2.0

    时间复杂度: O(1)

    移除给定 key 的生存时间,将这个 key 从“易失的”(带生存时间 key )转换成“持久的” (一个不带生存时间、永不过期的 key )。

  • 返回值

    当生存时间移除成功时,返回 1 . 如果 key 不存在或 key 没有设置生存时间,返回 0

  • 代码示例

    redis> SET mykey "Hello"
    OK
    
    redis> EXPIRE mykey 10  # 为 key 设置生存时间
    (integer) 1
    
    redis> TTL mykey
    (integer) 10
    
    redis> PERSIST mykey    # 移除 key 的生存时间
    (integer) 1
    
    redis> TTL mykey
    (integer) -1
    
PEXPIRE
  • 语法:PEXPIRE key milliseconds

    可用版本: >= 2.6.0

    时间复杂度: O(1)

    这个命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。

  • 返回值

    设置成功,返回 1 key 不存在或设置失败,返回 0

  • 代码示例

    redis> SET mykey "Hello"
    OK
    
    redis> PEXPIRE mykey 1500
    (integer) 1
    
    redis> TTL mykey    # TTL 的返回值以秒为单位
    (integer) 2
    
    redis> PTTL mykey   # PTTL 可以给出准确的毫秒数
    (integer) 1499
    
PEXPIREAT
  • 语法:PEXPIREAT key milliseconds-timestamp

    可用版本: >= 2.6.0

    时间复杂度: O(1)

    这个命令和 expireat 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 expireat 那样,以秒为单位。

  • 返回值

    如果生存时间设置成功,返回 1 。 当 key 不存在或没办法设置生存时间时,返回 0 。(查看 EXPIRE key seconds 命令获取更多信息)

  • 代码示例

    redis> SET mykey "Hello"
    OK
    
    redis> PEXPIREAT mykey 1555555555005
    (integer) 1
    
    redis> TTL mykey           # TTL 返回秒
    (integer) 223157079
    
    redis> PTTL mykey          # PTTL 返回毫秒
    (integer) 223157079318
    
PTTL
  • 语法:PTTL key

    可用版本: >= 2.6.0

    复杂度: O(1)

    这个命令类似于 TTL 命令,但它以毫秒为单位返回 key 的剩余生存时间,而不是像 TTL 命令那样,以秒为单位。

  • 返回值

    • key 不存在时,返回 -2
    • key 存在但没有设置剩余生存时间时,返回 -1
    • 否则,以毫秒为单位,返回 key 的剩余生存时间。
    Note
    在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
    
  • 代码示例

    # 不存在的 key
    redis> FLUSHDB
    OK
    
    redis> PTTL key
    (integer) -2
    
    # key 存在,但没有设置剩余生存时间
    redis> SET key value
    OK
    
    redis> PTTL key
    (integer) -1
    
    
    # 有剩余生存时间的 key
    redis> PEXPIRE key 10086
    (integer) 1
    
    redis> PTTL key
    (integer) 6179
    

8、事务

MULTI
  • 语法:MULTI

    标记一个事务块的开始。

    事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。

  • 返回值

    总是返回 OK

  • 代码示例

    redis> MULTI            # 标记事务开始
    OK
    
    redis> INCR user_id     # 多条命令按顺序入队
    QUEUED
    
    redis> INCR user_id
    QUEUED
    
    redis> INCR user_id
    QUEUED
    
    redis> PING
    QUEUED
    
    redis> EXEC             # 执行
    1) (integer) 1
    2) (integer) 2
    3) (integer) 3
    4) PONG
    
EXEC
  • 语法:EXEC

    执行所有事务块内的命令。

    假如某个(或某些) key 正处于 WATCH 命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么 EXEC 命令只在这个(或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort)。

  • 返回值

    事务块内所有命令的返回值,按命令执行的先后顺序排列。

    当操作被打断时,返回空值 nil

  • 代码示例

    # 事务被成功执行
    
    redis> MULTI
    OK
    
    redis> INCR user_id
    QUEUED
    
    redis> INCR user_id
    QUEUED
    
    redis> INCR user_id
    QUEUED
    
    redis> PING
    QUEUED
    
    redis> EXEC
    1) (integer) 1
    2) (integer) 2
    3) (integer) 3
    4) PONG
    
    
    # 监视 key ,且事务成功执行
    
    redis> WATCH lock lock_times
    OK
    
    redis> MULTI
    OK
    
    redis> SET lock "huangz"
    QUEUED
    
    redis> INCR lock_times
    QUEUED
    
    redis> EXEC
    1) OK
    2) (integer) 1
    
    
    # 监视 key ,且事务被打断
    
    redis> WATCH lock lock_times
    OK
    
    redis> MULTI
    OK
    
    redis> SET lock "joe"        # 就在这时,另一个客户端修改了 lock_times 的值
    QUEUED
    
    redis> INCR lock_times
    QUEUED
    
    redis> EXEC                  # 因为 lock_times 被修改, joe 的事务执行失败
    (nil)
    
DISCARD
  • 语法:DISCARD

    可用版本: >= 2.0.0

    时间复杂度: O(1)。

    取消事务,放弃执行事务块内的所有命令。

    如果正在使用 WATCH 命令监视某个(或某些) key,那么取消所有监视,等同于执行命令 UNWATCH

  • 返回值

    总是返回 OK

  • 代码示例

    redis> MULTI
    OK
    
    redis> PING
    QUEUED
    
    redis> SET greeting "hello"
    QUEUED
    
    redis> DISCARD
    OK
    
WATCH
  • 语法:WATCH key [key …]

    监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

  • 返回值

    总是返回 OK

  • 代码示例

    redis> WATCH lock lock_times
    OK
    
UNWATCH
  • 语法:UNWATCH

    取消 WATCH 命令对所有 key 的监视。

    如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。

    因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了;而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了。

  • 返回值

    总是 OK

  • 代码示例

    redis> WATCH lock lock_times
    OK
    
    redis> UNWATCH
    OK
    

9、持久化

SAVE
  • 语法:SAVE

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为要保存到数据库中的 key 的数量。

    SAVE 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘。

    一般来说,在生产环境很少执行 SAVE 操作,因为它会阻塞所有客户端,保存数据库的任务通常由 BGSAVE 命令异步地执行。然而,如果负责保存数据的后台子进程不幸出现问题时, SAVE 可以作为保存数据的最后手段来使用。

    请参考文档: Redis 的持久化运作方式(英文) 以获取更多消息。

  • 返回值

    保存成功时返回 OK

  • 代码示例

    redis> SAVE
    OK
    
BGSAVE
  • 语法:可用版本: >= 1.0.0

    时间复杂度: O(N), N 为要保存到数据库中的 key 的数量。

    在后台异步(Asynchronously)保存当前数据库的数据到磁盘。

    BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。

    客户端可以通过 LASTSAVE 命令查看相关信息,判断 BGSAVE 命令是否执行成功。

    请移步 持久化文档 查看更多相关细节。

  • 返回值

    反馈信息。

  • 代码示例

    redis> BGSAVE
    Background saving started
    
BGREWRITEAOF
  • 语法:BGREWRITEAOF

    可用版本: >= 1.0.0

    时间复杂度: O(N), N 为要追加到 AOF 文件中的数据数量。

    执行一个 AOF文件 重写操作。重写会创建一个当前 AOF 文件的体积优化版本。

    即使 BGREWRITEAOF 执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 BGREWRITEAOF 成功之前不会被修改。

    重写操作只会在没有其他持久化工作在后台执行时被触发,也就是说:

    • 如果 Redis 的子进程正在执行快照的保存工作,那么 AOF 重写的操作会被预定(scheduled),等到保存工作完成之后再执行 AOF 重写。在这种情况下, BGREWRITEAOF 的返回值仍然是 OK ,但还会加上一条额外的信息,说明 BGREWRITEAOF 要等到保存操作完成之后才能执行。在 Redis 2.6 或以上的版本,可以使用 [INFO section] 命令查看 BGREWRITEAOF 是否被预定。
    • 如果已经有别的 AOF 文件重写在执行,那么 BGREWRITEAOF 返回一个错误,并且这个新的 BGREWRITEAOF 请求也不会被预定到下次执行。

    从 Redis 2.4 开始, AOF 重写由 Redis 自行触发, BGREWRITEAOF 仅仅用于手动触发重写操作。

    请移步 持久化文档(英文) 查看更多相关细节。

  • 返回值

    反馈信息。

  • 示例

    redis> BGREWRITEAOF
    Background append only file rewriting started
    
LASTSAVE
  • 语法:LASTSAVE

    可用版本: >= 1.0.0

    时间复杂度: O(1)

    返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示。

  • 返回值

    一个 UNIX 时间戳。

  • 代码示例

    redis> LASTSAVE
    (integer) 1324043588
    

三、Redis可用性

1、redis持久化

持久化就是把内存中的数据持久化到本地磁盘,防止服务器宕机了内存数据丢失

Redis 提供两种持久化机制 RDB(默认)AOF 机制,Redis4.0以后采用混合持久化,用 AOF 来保证数据不丢失,作为数据恢复的第一选择,因为AOF文件保存的数据集通常比RDB文件保存的数据集更完整; 用 RDB 来做不同程度的冷备。甚至可以关闭持久化功能,让数据值在服务器运行时存在。

RDB和AOF的优缺点

**RDB:**是Redis DataBase缩写快照

​ RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。

优点:

​ 1)只有一个文件 dump.rdb,方便持久化,非常适合用于进行备份;

​ 2)容灾性好,一个文件可以保存到安全的磁盘。

​ 3)可以将Redis性能最大化,父进程在保存RDB文件时唯一要做的就是fork 出一个子进程来进行持久化写操作,让主进程继续处理命令,只存在毫秒级不响应请求。

​ 4)相对于数据集大时,比 AOF 的启动效率更高。

缺点:

​ 数据安全性低,RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。

​ 每次保存RDB时,都需要fork出一个子进程,由子进程来进行实际的持久化工作。在数据集非常庞大的时候,fork可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。如果数据集非常巨大,并且CPU时间非常紧张的话,那么这种停止时间设置可能会长达1秒。虽然AOF重写也需要进行fork,但是无论AOF重写执行间隔有多长,数据的耐久性都不会有任何损失。

AOF:持久化

​ AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。

优点:

​ 1)数据安全,aof 持久化可以配置 appendfsync 属性。你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者always每次执行写入命令时 fsync 。 AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。

​ 2)通过append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题(如为写入完整的命令)。

​ 3)AOF文件体积变得过大时,自动地在后台对AOF进行重写。

缺点:

​ 1)AOF 文件比 RDB 文件大,且恢复速度慢。

​ 2)数据集大的时候,比 rdb 启动效率低。

RDB和AOF,应该应用哪一个

​ 一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

​ 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

​ 有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug(过去发生过,因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样) 。

RDB快照

​ 在默认情况下, Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。

​ 你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。

​ 你也可以通过调用 SAVE 或者 BGSAVE , 手动让 Redis 进行数据集保存操作。

​ 比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集:

save 60 1000

这种持久化方式被称为快照(snapshot)。

快照地运作方式

当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:

  1. Redis 调用 fork() ,同时拥有父进程和子进程。
  2. 子进程将数据集写入到一个临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。

只进行追加操作的文件(AOF)

​ 快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。

​ 尽管对于某些程序来说, 数据的耐久性并不是最重要的考虑因素, 但是对于那些追求完全耐久能力(full durability)的程序来说, 快照功能就不太适用了。

​ 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。

你可以通过修改配置文件来打开 AOF 功能:

appendonly yes

​ 从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 [SET key value EX seconds] [PX milliseconds] [NX|XX]), 这个命令就会被追加到 AOF 文件的末尾。

这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的,否则仍使用RDB文件进行重建数据集。

AOF的耐久性如何?

你可以配置 Redis 多久才将数据 fsync 到磁盘一次。

有三个选项:

  • 每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全。
  • 每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
  • 从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。

appendfsync always #每次有数据修改发生时都会写入AOF文件。

appendfsync everysec #每秒钟同步一次,该策略为AOF的默认策略。

appendfsync no #从不同步。高效但是数据不会被持久化。

推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。

总是 fsync 的策略在实际使用中非常慢, 即使在 Redis 2.0 对相关的程序进行了改进之后仍是如此 —— 频繁调用 fsync 注定了这种策略不可能快得起来。

如果AOF文件出错了,怎么办?

服务器可能在程序正在对 AOF 文件进行写入时停机, 如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。

当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:

  1. 为现有的 AOF 文件创建一个备份。
  2. 使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复。
$ redis-check-aof --fix
  1. (可选)使用 diff -u 对比修复后的 AOF 文件和原始 AOF 文件的备份,查看两个文件之间的不同之处。

  2. 重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。

AOF的运作方式

AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制。

以下是 AOF 重写的执行步骤:

  1. Redis 执行 fork() ,现在同时拥有父进程和子进程。

  2. 子进程开始将新 AOF 文件的内容写入到临时文件。

  3. 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。

  4. 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。

  5. 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

怎么从 RDB 持久化切换到 AOF 持久化

在 Redis 2.2 或以上版本,可以在不重启的情况下,从 RDB 切换到 AOF :

  1. 为最新的 dump.rdb 文件创建一个备份。
  2. 将备份放到一个安全的地方。
  3. 执行以下两条命令:
redis-cli> CONFIG SET appendonly yes

redis-cli> CONFIG SET save ""
  1. 确保命令执行之后,数据库的键的数量没有改变。
  2. 确保写命令会被正确地追加到 AOF 文件的末尾。

步骤 3 执行的第一条命令开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件创建完成为止, 之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。

步骤 3 执行的第二条命令用于关闭 RDB 功能。 这一步是可选的, 如果你愿意的话, 也可以同时使用 RDB 和 AOF 这两种持久化功能。

注:别忘了在 redis.conf 中打开 AOF 功能! 否则的话, 服务器重启之后, 之前通过 CONFIG SET 设置的配置就会被遗忘, 程序会按原来的配置来启动服务器。

备份 Redis 数据

在阅读这个小节前, 先将下面这句话铭记于心: 一定要备份你的数据库!

磁盘故障, 节点失效, 诸如此类的问题都可能让你的数据消失不见, 不进行备份是非常危险的。

Redis 对于数据备份是非常友好的, 因为你可以在服务器运行的时候对 RDB 文件进行复制: RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用 rename(2) 原子地用临时文件替换原来的 RDB 文件

这也就是说, 无论何时, 复制 RDB 文件都是绝对安全的。

以下是我们的建议:

  • 创建一个定期任务(cron job), 每小时将一个 RDB 文件备份到一个文件夹, 并且每天将一个 RDB 文件备份到另一个文件夹。

  • 确保快照的备份都带有相应的日期和时间信息, 每次执行定期任务脚本时, 使用 find 命令来删除过期的快照: 比如说, 你可以保留最近 48 小时内的每小时快照, 还可以保留最近一两个月的每日快照。

  • 至少每天一次, 将 RDB 备份到你的数据中心之外, 或者至少是备份到你运行 Redis 服务器的物理机器之外。

容灾备份

​ Redis 的容灾备份基本上就是对数据进行备份, 并将这些备份传送到多个不同的外部数据中心。

​ 容灾备份可以在 Redis 运行并产生快照的主数据中心发生严重的问题时, 仍然让数据处于安全状态。

2、redis事务

​ 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

Redis事务的概念

​ Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

事务命令:

**MULTI:**用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

**EXEC:**执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。

**WATCH :**是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,执行EXEC之前一旦其中有一个键被修改(或删除),那么整个事务就不会执行(然后执行EXEC命令会返回会null,提醒用户执行失败),监控一直持续到EXEC命令,。(秒杀场景

**DISCARD:**调用该命令,客户端可以清空事务队列,并放弃执行事务,且客户端会从事务状态中退出,连接变为正常状态。

UNWATCH:命令可以取消watch对所有key的监控。

3、redis失效策略

内存淘汰策略

1)全局的键空间选择性移除

noeviction:默认策略。当内存不足以容纳新写入数据时,新写入操作会报错,只有get命令可以正常执行。(字典库常用)

allkeys-lru:对所有的键使用LRU算法淘汰,移除最近最少使用的key。(缓存常用)

allkeys-lfu:对所有的key使用LFU算法淘汰。

allkeys-random:对所有的键使用随机淘汰,随机移除某个key。

2)设置过期时间的键空间选择性移除

volatile-lru:在设置了过期时间的键使用LRU算法淘汰,移除最近最少使用的key。

volatile-lfu:对设置了过期时间的键使用LFU算法进行淘汰。

volatile-random:在设置了过期时间的键中使用随机淘汰,随机移除某个key。

volatile-ttl:在设置了过期时间的键中根据过期时间淘汰,有更早过期时间的key优先移除。

LRU、LFU和volatile-ttl都是近似随机算法;

在缓存的内存淘汰策略中有FIFO、LRU、LFU三种,其中LRU和LFU是Redis在使用的。

1. FIFO是最简单的淘汰策略,遵循着先进先出的原则,这里简单提一下:

img

2. LRU(Least Recently Used)表示最近最少使用,该算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

LRU算法的常见实现方式为链表:

​ 新数据放在链表头部 ,链表中的数据被访问就移动到链头,链表满的时候从链表尾部移出数据。

img

​ 而在Redis中使用的是近似LRU算法,为什么说是近似呢?Redis中是随机采样5个(可以修改参数maxmemory-samples配置)key,然后从中选择访问时间最早的key进行淘汰,因此当采样key的数量与Redis库中key的数量越接近,淘汰的规则就越接近LRU算法。但官方推荐5个就足够了,最多不超过10个,越大就越消耗CPU的资源。

​ 但在LRU算法下,如果一个热点数据最近很少访问,而非热点数据近期访问了,就会误把热点数据淘汰而留下了非热点数据,因此在Redis4.x中新增了LFU算法。

在LRU算法下,Redis会为每个key新增一个3字节的内存空间用于存储key的访问时间;

**3. LFU(Least Frequently Used)**表示最不经常使用,它是根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

LFU算法反映了一个key的热度情况,不会因LRU算法的偶尔一次被访问被误认为是热点数据。

​ LFU算法的常见实现方式为链表:

​ 新数据放在链表尾部 ,链表中的数据按照被访问次数降序排列,访问次数相同的按最近访问时间降序排列,链表满的时候从链表尾部移出数据。

img

上述的LRU和LFU算法都是在Redis内存占用满的情况下的淘汰策略,那么当内存没占满时在内存中过期的Key是如何从内存中删除以达到优化内存占用呢?相关策略如下:

过期删除策略

在Redis中过期的key不会立刻从内存中删除,而是会同时以下面两种策略进行删除:

​ **定时清除:**每隔一段时间,随机检查设置过期时间的key并删除已过期的key;针对每个设置过期时间的key都创建指定定时器,维护定时器消耗CPU资源。

​ **惰性清除:**访问时判断,对内存不友好。当key被访问时检查该key的过期时间,若已过期则删除;已过期但未访问的数据仍保持在内存中,消耗内存资源。

​ **1. 定时扫描清除:**定时10ms随机20个检查过期的字典,若存在25%以上则继续循环删除。Redis每10秒进行一次过期扫描:

  1. 随机取20个设置了过期策略的key;

  2. 检查20个key中过期时间中已过期的key并删除;

  3. 如果有超过25%的key已过期则重复第一步;

    这种循环随机操作会持续到过期key可能仅占全部key的25%以下时,并且为了保证不会出现循环过多的情况,默认扫描时间不会超过25ms;

AOF和RDB的过期删除策略

前面介绍了Redis的持久化策略RDB和AOF,当Redis中的key已过期未删除时,如果进行RDB和AOF的持久化操作时候会怎么操作呢?

  • ​ 在RDB持久化模式中我们可以使用save和bgsave命令进行数据持久化操作

  • ​ 在AOF持久化模式中使用rewriteaof和bgrewriteaof命令进行持久化操作

    这四个命令都不会将过期key持久化到RDB文件或AOF文件中,可以保证重启服务时不会将过期key载入Redis。

    为了保证一致性,在AOF持久化模式中,当key过期时候,会同时发送DEL命令给AOF文件和所有节点;

    从节点不会主动的删除过期key除非它升级为主节点或收到主节点发来的DEL命令;

4、redis更新策略

CacheAside旁路缓存

写请求更新数据库后删除缓存数据。读请求不命中则查询数据库,查询完成写入缓存

在这里插入图片描述

​ 业务端处理所有数据访问细节,同时利用 Lazy 计算的思想,更新 DB 后,直接删除 cache 并通过 DB 更新,确保数据以 DB 结果为准,则可以大幅降低 cache 和 DB 中数据不一致的概率

​ 如果没有专门的存储服务,同时是对数据一致性要求比较高的业务,或者是缓存数据更新比较复杂的业务,适合使用 Cache Aside 模式。如微博发展初期,不少业务采用这种模式

// 延迟双删,用以保证最终一致性,防止小概率旧数据读请求在第一次删除后更新数据库
public void write(String key,Object data){
	redis.delKey(key);
	db.updateData(data);
	Thread.sleep(1000);
	redis.delKey(key);
}

高并发下保证绝对的一致,先删缓存再更新数据,需要用到内存队列做异步串行化。非高并发场景,先更新数据再删除缓存,延迟双删策略基本满足了。

​ 常见问题:

  • 先更新db后删除redis:删除redis失败则出现问题

  • 先删redis后更新db:删除redis瞬间,旧数据被回填redis,可能出现数据不一致的情况。

  • 先删redis后更新db休眠后删redis:同第二点,休眠后删除redis 可能宕机,但是能保证高一致性。

  • java内部jvm队列:不适用分布式场景且降低并发

    常见的面试问题:写数据的过程,可以先删除缓存,然后更新数据库吗?
    答案是不可以,数据会出现不一致的情况

Read/Write Though(读写穿透)

​ 读写穿透模式把缓存作为主要存储,负责将数据读取和写入到数据库中,开发少见,一方面是性能问题,另一面是redis并没有提供cache把数据写入到数据库

读查询首先检查缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库中同步加载数据,然后写入缓存之后响应。

在这里插入图片描述

写查询首先检查写入的数据在缓存中是否已经存在,如果已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中。

在这里插入图片描述

​ 适用于用户读操作较多。相较于Cache aside而言更适合缓存一致的场景。使用简单,屏蔽了底层数据库的操作,只是操作缓存。

​ 出现脏数据效率较低,依赖缓存

场景:

微博 Feed 的 Outbox Vector(即用户最新微博列表)就采用这种模式。一些粉丝较少且不活跃的用户发表微博后,Vector 服务会首先查询 Vector Cache,如果 cache 中没有该用户的 Outbox 记录,则不写该用户的 cache 数据,直接更新 DB 后就返回,只有 cache 中存在才会通过 CAS 指令进行更新。

Write Behind Caching(异步缓存写入)

​ 跟读写穿透很像,都是缓存把数据读取写入到数据库中读写穿透是同步更新缓存和数据库,异步缓存是只更新缓存不直接更新数据库,而是异步批量的方式更新数据库

​ 应用场景:消息队列中消息异步写入磁盘,写性能很高,适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量,点赞量。但是数据一致性较差,有数据丢失的可能。

img

比如对一些计数业务,一条 Feed 被点赞 1万 次,如果更新 1万 次 DB 代价很大,而合并成一次请求直接加 1万,则是一个非常轻量的操作。但这种模型有个显著的缺点,即数据的一致性变差,甚至在一些极端场景下可能会丢失数据。

5、多级缓存

**浏览器本地内存缓存:**专题活动,一旦上线,在活动期间是不会随意变更的。

**浏览器本地磁盘缓存:**Logo缓存,大图片懒加载

**服务端本地内存缓存:**由于没有持久化,重启时必定会被穿透

服务端网络内存缓存:Redis等,针对穿透的情况下可以继续分层,必须保证数据库不被压垮

为什么不是使用服务器本地磁盘做缓存?

​ 当系统处理大量磁盘 IO 操作的时候,由于 CPU 和内存的速度远高于磁盘,可能导致 CPU 耗费太多时间等待磁盘返回处理的结果。对于这部分 CPU 在 IO 上的开销,我们称为 iowait

四、Redis七大经典问题

1、缓存雪崩

​ 指缓存同一时间大面积的失效,所以后面的查询请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

​ 主要原因是预置的固定过期时间,导致大量数据同时过期。

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生,使用公式:过期时间 = base 时间 + 随机时间
  • 逻辑上永不过期给每一个缓存数据增加相应的缓存标记,缓存标记失效则更新数据缓存
  • 多级缓存,失效时通过二级更新一级,由第三方插件更新二级缓存。
  • Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃
  • 本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死

2、缓存穿透

​ https://blog.csdn.net/lin777lin/article/details/105666839

​ 缓存穿透是指查询一个不存在的数据,缓存和持久层(数据库)中都无法命中。持久层查不到数据则不写入缓存,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存对后端的保护作用。

img

解决方案:

​ 1)接口层增加校验:如用户鉴权校验,id做基础校验,id<=0的直接拦截;

​ 2)缓存空对象:从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒。这样可以防止攻击用户反复用同一个id暴力攻击;

​ 3)采用布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。(宁可错杀一千不可放过一人)

方案对比:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8AGZaBbr-1625484805999)(C:\Users\AdministratorGUET\AppData\Roaming\Typora\typora-user-images\image-20210503095113553.png)]

3、缓存击穿

​ 是指一个热点key,在高并发的情况下,大量请求集中对这个key进行访问。当key失效的瞬间,持续的高并发请求击穿缓存,直接请求数据库,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

​ 1)设置热点数据永远不过期,异步线程处理。

​ 2)加写回操作加互斥锁,查询失败默认值快速返回。

​ 只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存中获取数据即可。

img

​ 3)缓存预热

​ 系统上线后,将相关**可预期(例如排行榜)**热点数据直接加载到缓存。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。

​ 缓存预热的解决思路:

	1. 写一个缓存刷新页面,手动操作热点数据**(例如广告推广)**上下线。
	2. 数据量不大,可以在项目启动的时候自动进行加载。
	3. 定时刷新缓存。

4、数据不一致

​ 在缓存机器的带宽被打满,或者机房网络出现波动时,缓存更新失败,新数据没有写入缓存,就会导致缓存和 DB 的数据不一致。缓存 rehash 时,某个缓存机器反复异常,多次上下线,更新请求多次 rehash。这样,一份数据存在多个节点,且每次 rehash 只更新某个节点,导致一些缓存节点产生脏数据。

  • Cache 更新失败后,可以进行重试,则将重试失败的 key 写入mq,待缓存访问恢复后,将这些 key 从缓存删除。这些 key 在再次被查询时,重新从 DB 加载,从而保证数据的一致性

  • 缓存时间适当调短,让缓存数据及早过期后,然后从 DB 重新加载,确保数据的最终一致性。

  • 不采用 rehash 漂移策略,而采用缓存分层策略,尽量避免脏数据产生。

5、数据并发竞争

​ 数据并发竞争在大流量系统也比较常见,比如车票系统,如果某个火车车次缓存信息过期,但仍然有大量用户在查询该车次信息。又比如微博系统中,如果某条微博正好被缓存淘汰,但这条微博仍然有大量的转发、评论、赞。上述情况都会造成并发竞争读取的问题。

  • ​ 加写回操作加互斥锁,查询失败默认值快速返回。
  • ​ 对缓存数据保持多个备份,减少并发竞争的概率

6、热点key问题

​ 明星结婚、离婚、出轨这种特殊突发事件,比如奥运、春节这些重大活动或节日,还比如秒杀、双12、618 等线上促销活动,都很容易出现 Hot key 的情况。

如何提前发现HotKey?

  • 对于重要节假日、线上促销活动这些提前已知的事情,可以提前评估出可能的热 key 来。
  • 而对于突发事件,无法提前评估,可以通过 Spark,对应流任务进行实时分析,及时发现新发布的热点 key。而对于之前已发出的事情,逐步发酵成为热 key 的,则可以通过 Hadoop 对批处理任务离线计算,找出最近历史数据中的高频热 key。

解决方案:

  • 这 n 个 key 分散存在多个缓存节点,然后 client 端请求时,随机访问其中某个后缀的 hotkey,这样就可以把热 key 的请求打散,避免一个缓存节点过载。

  • 缓存集群可以单节点进行主从复制和垂直扩容

  • 利用应用内的前置缓存,但是需注意需要设置上限

  • 延迟不敏感,定时刷新,实时感知用主动刷新

  • 和缓存穿透一样,限制逃逸流量,单请求进行数据回源并刷新前置

  • 无论如何设计,最后都要写一个兜底逻辑,千万级流量说来就来

7、BigKey问题

​ 比如互联网系统中需要保存用户最新 1万 个粉丝的业务,比如一个用户个人信息缓存,包括基本资料、关系图谱计数、发 feed 统计等。微博的 feed 内容缓存也很容易出现,一般用户微博在 140 字以内,但很多用户也会发表 1千 字甚至更长的微博内容,这些长微博也就成了大 key

  • 首先Redis底层数据结构里,根据Value的不同,会进行数据结构的重新选择
  • 可以扩展新的数据结构,进行序列化构建,然后通过 restore一次性写入
  • 将大 key 分拆为多个 key,设置较长的过期时间

五、Redis 分区容错性

1、redis数据分区 (分片)

Hash:(不稳定)

​ 客户端分片:哈希+取余

​ 节点伸缩:数据节点关系变化,导致数据迁移

​ 迁移数量和添加节点数量有关:建议翻倍扩容

​ 一个简单直观的想法是直接用Hash来计算,以Key做哈希后对节点数取模。可以看出,在key足够分散的情况下,均匀性可以获得,但一旦有节点加入或退出,所有的原有节点都会受到影响,稳定性无从谈起。

一致性Hash:(不均衡)

​ 客户端分片:哈希+顺时针(优化取余)

​ 节点伸缩:只影响邻近节点,但是还是有数据迁移

​ 翻倍伸缩:保证最小迁移数据和负载均衡

​ 一致性Hash可以很好的解决稳定问题,可以将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会顺时针找到先遇到的一组存储节点存放。而当有节点加入或退出时,仅影响该节点在Hash环上顺时针相邻的后续节点,将数据从该节点接收或者给予。但这又带来均匀性的问题,即使可以将存储节点等距排列,也会在存储节点个数变化时带来数据的不均匀

Codis的Hash槽

​ Codis 将所有的 key 默认划分为 1024 个槽位(slot),它首先对客户端传过来的 key 进行 crc32 运算计算 哈希值,再将 hash 后的整数值对 1024 这个整数进行取模得到一个余数,这个余数就是对应 key 的槽位。

RedisCluster

​ Redis-cluster把所有的物理节点映射到[0-16383]个slot上,对key采用crc16算法得到hash值后对16384取模,基本上采用平均分配和连续分配的方式。当用户put或者是get一个数据的时候,首先会查找这个数据对应的槽位是多少,然后查找对应的节点,然后才把数据放入这个节点。这样就做到了把数据均匀的分配到集群中的每一个节点上,从而做到了每一个节点的负载均衡,充分发挥了集群的威力。

2、主从模式=简单

​ 主从模式最大的优点是部署简单,最少两个节点便可以构成主从模式,并且可以通过读写分离避免读和写同时不可用。不过,一旦 Master 节点出现故障,主从节点就无法自动切换,直接导致 SLA 下降。所以,主从模式一般适合业务发展初期,并发量低,运维成本低的情况

Drawing 1.png

主从复制原理:

​ ①通过从服务器发送到PSYNC命令给主服务器

​ ②如果是首次连接,触发一次全量复制。此时主节点会启动一个后台线程,生成 RDB 快照文件

​ ③主节点会将这个 RDB 发送给从节点,slave 会先写入本地磁盘,再从本地磁盘加载到内存中

​ ④master会将此过程中的写命令写入缓存,从节点实时同步这些数据

​ ⑤如果网络断开了连接,自动重连后主节点通过命令传播增量复制给从节点部分缺少的数据

缺点

​ 所有的slave节点数据的复制和同步都由master节点来处理,会照成master节点压力太大,使用主从从结构来解决,redis4.0中引入psync2 解决了slave重启后仍然可以增量同步。

3、哨兵模式=读多

​ 由一个或多个sentinel实例组成sentinel集群可以监视一个或多个主服务器和多个从服务器。哨兵模式适合读请求远多于写请求的业务场景,比如在秒杀系统中用来缓存活动信息。 如果写请求较多,当集群 Slave 节点数量多了后,Master 节点同步数据的压力会非常大。

image-20201220231241725

当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。

检测主观下线状态

​ Sentinel每秒一次向所有与它建立了命令连接的实例(主服务器、从服务器和其他Sentinel)发送PING命令

​ 实例在down-after-milliseconds毫秒内返回无效回复Sentinel就会认为该实例主观下线(SDown)

检查客观下线状态

​ 当一个Sentinel将一个主服务器判断为主观下线后 ,Sentinel会向监控这个主服务器的所有其他Sentinel发送查询主机状态的命令

​ 如果达到Sentinel配置中的quorum数量的Sentinel实例都判断主服务器为主观下线,则该主服务器就会被判定为客观下线(ODown)。

选举Leader Sentinel

​ 当一个主服务器被判定为客观下线后,监视这个主服务器的所有Sentinel会通过选举算法(raft),选出一个Leader Sentinel去执行**failover(故障转移)**操作。

Raft算法

​ Raft协议是用来解决分布式系统一致性问题的协议。 Raft协议描述的节点共有三种状态:Leader, Follower, Candidate。 Raft协议将时间切分为一个个的Term(任期),可以认为是一种“逻辑时间”。 选举流程:
①Raft采用心跳机制触发Leader选举系统启动后,全部节点初始化为Follower,term为0

​ ②节点如果收到了RequestVote或者AppendEntries,就会保持自己的Follower身份

​ ③节点如果一段时间内没收到AppendEntries消息,在该节点的超时时间内还没发现Leader,Follower就会转换成Candidate,自己开始竞选Leader。 一旦转化为Candidate,该节点立即开始下面几件事情:
​ --增加自己的term,启动一个新的定时器
​ --给自己投一票,向所有其他节点发送RequestVote,并等待其他节点的回复。

​ ④如果在计时器超时前,节点收到多数节点的同意投票,就转换成Leader。同时通过 AppendEntries,向其他节点发送通知。

​ ⑤每个节点在一个term内只能投一票,采取先到先得的策略,Candidate投自己, Follower会投给第一个收到RequestVote的节点。

​ ⑥Raft协议的定时器采取随机超时时间(选举的关键),先转为Candidate的节点会先发起投票,从而获得多数票。

主服务器的选择

​ 当选举出Leader Sentinel后,Leader Sentinel会根据以下规则去从服务器中选择出新的主服务器。

  1. 过滤掉主观、客观下线的节点
  2. 选择配置slave-priority最高的节点,如果有则返回没有就继续选择
  3. 选择出复制偏移量最大的系节点,因为复制偏移量越大则数据复制的越完整
  4. 选择run_id最小的节点,因为run_id越小说明重启次数越少

故障转移

​ 当Leader Sentinel完成新的主服务器选择后,Leader Sentinel会对下线的主服务器执行故障转移操作,主要有三个步骤:

​ 1、它会将失效 Master 的其中一个 Slave 升级为新的 Master , 并让失效 Master 的其他 Slave 改为复制新的 Master ;

​ 2、当客户端试图连接失效的 Master 时,集群会向客户端返回新 Master 的地址,使得集群当前状态只有一个Master。

​ 3、Master 和 Slave 服务器切换后, Master 的 redis.conf 、 Slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变,即 Master 主服务器的 redis.conf配置文件中会多一行 replicaof 的配置, sentinel.conf 的监控目标会随之调换。

4、集群模式=写多

​ 为了避免单一节点负载过高导致不稳定,集群模式采用一致性哈希算法或者哈希槽的方法将 Key 分布到各个节点上。其中,每个 Master 节点后跟若干个 Slave 节点,用于出现故障时做主备切换,客户端可以连接任意 Master 节点,集群内部会按照不同 key 将请求转发到不同的 Master 节点

​ 集群模式是如何实现高可用的呢?集群内部节点之间会互相定时探测对方是否存活,如果多数节点判断某个节点挂了,则会将其踢出集群,然后从 Slave 节点中选举出一个节点替补挂掉的 Master 节点。整个原理基本和哨兵模式一致

​ 虽然集群模式避免了 Master 单节点的问题,但集群内同步数据时会占用一定的带宽。所以,只有在写操作比较多的情况下人们才使用集群模式,其他大多数情况,使用哨兵模式都能满足需求。

5、分布式锁

利用Watch实现Redis乐观锁

​ 乐观锁基于CAS(Compare And Swap)比较并替换思想,不会产生锁等待而消耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。因此我们可以利用redis来实现乐观锁**(秒杀)**。具体思路如下:

1、利用redis的watch功能,监控这个redisKey的状态值。
2、获取redisKey的值,创建redis事务,给这个key的值+1。
3、执行这个事务,如果key的值被修改过则回滚,key不加1。

利用setnx防止库存超卖
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。 利用Redis的单线程特性对共享资源进行串行化处理

// 获取锁推荐使用set的方式
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
String result = jedis.setnx(lockKey, requestId); //如线程死掉,其他线程无法获取到锁
// 释放锁,非原子操作,可能会释放其他线程刚加上的锁
if (requestId.equals(jedis.get(lockKey))) { 
  jedis.del(lockKey);
}
// 推荐使用redis+lua脚本
String lua = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object result = jedis.eval(lua, Collections.singletonList(lockKey),

分布式锁存在的问题

  • 客户端长时间阻塞导致锁失效问题

​ 计算时间内异步启动另外一个线程去检查的问题,这个key是否超时,当锁超时时间快到期且逻辑未执行完,延长锁超时时间。

  • **Redis服务器时钟漂移问题导致同时加锁
    redis的过期时间是依赖系统时钟的,如果时钟漂移过大时 理论上是可能出现的 **会影响到过期时间的计算。

  • 单点实例故障,锁未及时同步导致丢失

    RedLock算法

  1. 获取当前时间戳T0,配置时钟漂移误差T1

  2. 短时间内逐个获取全部N/2+1个锁,结束时间点T2

  3. 实际锁能使用的处理时长变为:TTL - (T2 - T0)- T1

    该方案通过多节点来防止Redis的单点故障,效果一般,也无法防止:

  • 主从切换导致的两个客户端同时持有锁

    大部分情况下持续时间极短,而且使用Redlock在切换的瞬间获取到节点的锁,也存在问题。已经是极低概率的时间,无法避免。Redis分布式锁适合幂等性事务,如果一定要保证安全,应该使用Zookeeper或者DB,但是,性能会急剧下降

与zookeeper分布式锁对比

  • redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。
  • zk 分布式锁,注册个监听器即可,不需要不断主动尝试获取锁,ZK获取锁会按照加锁的顺序,所以是公平锁,性能和mysql差不多,和redis差别大

Redission生产环境的分布式锁

​ Redisson是基于NIO的Netty框架上的一个Java驻内存数据网格(In-Memory Data Grid)分布式锁开源组件。

image-20201221000119586

但当业务必须要数据的强一致性,即不允许重复获得锁,比如金融场景(重复下单,重复转账),请不要使用redis分布式锁。可以使用CP模型实现,比如:zookeeper和etcd。

Rediszookeeperetcd
一致性算法paxos(ZAB)raft
CAPAPCPCP
高可用主从集群n+1n+1
实现setNXcreateNoderestfulAPI

6、redis心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率向主服务器发送ACK命令:

​ 1、检测主从的连接状态 检测主从服务器的网络连接状态

​ lag的值应该在0或1之间跳动,如果超过1则说明主从之间的连接有 故障。

​ 2、辅助实现min-slaves,Redis可以通过配置防止主服务器在不安全的情况下执行写命令

min-slaves-to-write 3 (min-replicas-to-write 3 )

min-slaves-max-lag 10 (min-replicas-max-lag 10)

​ 上面的配置表示:从服务器的数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10 秒时,主服务器将拒绝执行写命令。

​ 3、检测命令丢失,增加重传机制

​ 如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发 送REPLCONF ACK命令时,主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量, 然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数据,并将这些数据重新发送给从服务器。

六、Redis实战

1、Redis优化

img

读写方式
简单来说就是不用keys等,用range、contains之类。比如,用户粉丝数,大 V 的粉丝更是高达几千万甚至过亿,因此,获取粉丝列表只能部分获取。另外在判断某用户是否关注了另外一个用户时,也只需要关注列表上进行检查判断,然后返回 True/False 或 0/1 的方式更为高效。

KV size
如果单个业务的 KV size 过大,需要分拆成多个 KV 来缓存。拆分时应考虑访问频率

key 的数量
如果数据量巨大,则在缓存中尽可能只保留频繁访问的热数据,对于冷数据直接访问 DB。

读写峰值
如果小于 10万 级别,简单分拆到独立 Cache 池即可
如果达到 100万 级的QPS,则需要对 Cache 进行分层处理,可以同时使用 Local-Cache 配合远程 cache,甚至远程缓存内部继续分层叠加分池进行处理。(多级缓存)

命中率
缓存的命中率对整个服务体系的性能影响甚大。对于核心高并发访问的业务,需要预留足够的容量,确保核心业务缓存维持较高的命中率。比如微博中的 Feed Vector Cache(热点资讯),常年的命中率高达 99.5% 以上。为了持续保持缓存的命中率,缓存体系需要持续监控,及时进行故障处理或故障转移。同时在部分缓存节点异常、命中率下降时,故障转移方案,需要考虑是采用一致性 Hash 分布的访问漂移策略,还是采用数据多层备份策略。

过期策略

​ 可以设置较短的过期时间,让冷 key 自动过期;也可以让 key 带上时间戳,同时设置较长的过期时间,比如很多业务系统内部有这样一些 key:key_20190801。

缓存穿透时间
平均缓存穿透加载时间在某些业务场景下也很重要,对于一些缓存穿透后,加载时间特别长或者需要复杂计算的数据,而且访问量还比较大的业务数据,要配置更多容量,维持更高的命中率,从而减少穿透到 DB 的概率,来确保整个系统的访问性能。

缓存可运维性
对于缓存的可运维性考虑,则需要考虑缓存体系的集群管理,如何进行一键扩缩容,如何进行缓存组件的升级和变更,如何快速发现并定位问题,如何持续监控报警,最好有一个完善的运维平台,将各种运维工具进行集成。

缓存安全性
对于缓存的安全性考虑,一方面可以限制来源 IP,只允许内网访问,同时加密鉴权访问。

2、Redis热升级

在 Redis 需要升级版本或修复 bug 时,如果直接重启变更,由于需要数据恢复,这个过程需要近 10 分钟的时间,时间过长,会严重影响系统的可用性。面对这种问题,可以对 Redis 扩展热升级功能,从而在毫秒级完成升级操作,完全不影响业务访问。

热升级方案如下,首先构建一个 Redis 壳程序,将 redisServer 的所有属性(包括redisDb、client等)保存为全局变量。然后将 Redis 的处理逻辑代码全部封装到动态连接库 so 文件中。Redis 第一次启动,从磁盘加载恢复数据,在后续升级时,通过指令,壳程序重新加载 Redis 新的 redis-4.so 到 redis-5.so 文件,即可完成功能升级,毫秒级完成 Redis 的版本升级。而且整个过程中,所有 Client 连接仍然保留,在升级成功后,原有 Client 可以继续进行读写操作,整个过程对业务完全透明。

3、分片/分区

​ 在Redis中,数据分片(分区)是一种在多个Redis实例之间拆分所有数据的技术,以便每个实例仅包含键的一个子集。 通过添加越来越多的实例并将数据划分为较小的部分(碎片或分区),这样的过程可以减轻数据的增长。不仅如此,这还意味着越来越多的计算能力可用于处理数据,有效地支持水平缩放。

分片(分区)的复杂性和缺点

  • 通常不支持涉及多个键的操作。 例如,如果两个集合( SINTER )存储在映射到不同Redis实例的键中,则不可能直接执行它们之间的交集。
  • 涉及映射到不同Redis实例的多个密钥的事务是不可能的。
  • 分区是基于键的,因此无法使用单个大键(很大的排序集或列表)对数据集进行分片(分区)。
  • 备份和持久管理要复杂得多:您必须处理多个RDB / AOF文件,备份涉及来自许多实例的RDB文件的聚合(合并)。
  • 除非您对此进行了计划,否则在运行时添加和删除实例可能会导致数据失衡(请参阅规划分片(分区) )。

分区的方案

  • 范围划分
    通过将对象范围映射到特定的Redis实例中来完成。 例如,假设我们正在存储一些用户数据,并且每个用户都有其唯一的标识符(ID)。 在我们的分区方案中,我们可以定义ID从0到10000的用户将进入实例Redis 1,而ID从10001到20000的用户将进入实例Redis 2,依此类推。 该方案的缺点是,范围和实例之间的映射应该被维护,并且应该与Redis中保留的对象(用户,产品等)的种类一样多的映射。

  • 哈希分区

    该方案适用于任何密钥,但涉及哈希函数:此函数应将密钥名称映射到某个数字。 假设我们有这样一个函数(我们称其为hash_func),这样的方案就是这样的:

    • 取得键名,并使用hash_func将其映射到数字

    哈希函数的选择非常重要。 良好的哈希函数可确保密钥在所有Redis实例上平均分布,因此不会在任何单个实例上建立过多的密钥。

  • 一致性哈希
    它是hash partitioning的一种高级形式,被许多解决方案用于数据分片(分区)。

分片(分区)实现

从实现的角度来看,根据应用程序的体系结构,有几种可能的数据分片(分区)实现:

  • 客户端分区
    客户端直接选择正确的实例来写入或读取给定的密钥。
  • 代理辅助分区
    客户端将请求发送到支持Redis协议的代理,而不是直接将请求发送到正确的Redis实例。 代理将确保根据配置的分区方案将请求转发到正确的Redis实例,并将答复发送回客户端(最著名的实现是Twitter的Twemproxyhttps://github.com/twitter/ twemproxy )。
  • 查询路由
    客户端将查询发送到随机Redis实例,该实例将确保将查询转发到正确的实例。 查询路由的混合形式假定客户端被重定向到正确的实例(但是查询不会直接从一个Redis实例转发到另一个实例)

slot槽位

​ redis中,总共划分了16384个slot,并把集群中所有实例映射到这些slot上 [0-16383],比如上面有5个master,redisCluster在创建的时候,可以将slot映射为:

redis10-3270
redis23271-6542
redis36543-9814
redis49815-13087
redis513088-16383

存储时使用crc16 算法计算key值,并对16384取余,最终落到争取的redis。

注意: slot在cluster上必须是连续的

点的问题,但集群内同步数据时会占用一定的带宽。所以,只有在写操作比较多的情况下人们才使用集群模式,其他大多数情况,使用哨兵模式都能满足需求。

5、分布式锁

利用Watch实现Redis乐观锁

​ 乐观锁基于CAS(Compare And Swap)比较并替换思想,不会产生锁等待而消耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。因此我们可以利用redis来实现乐观锁**(秒杀)**。具体思路如下:

1、利用redis的watch功能,监控这个redisKey的状态值。
2、获取redisKey的值,创建redis事务,给这个key的值+1。
3、执行这个事务,如果key的值被修改过则回滚,key不加1。

利用setnx防止库存超卖
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。 利用Redis的单线程特性对共享资源进行串行化处理

// 获取锁推荐使用set的方式
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
String result = jedis.setnx(lockKey, requestId); //如线程死掉,其他线程无法获取到锁
// 释放锁,非原子操作,可能会释放其他线程刚加上的锁
if (requestId.equals(jedis.get(lockKey))) { 
  jedis.del(lockKey);
}
// 推荐使用redis+lua脚本
String lua = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object result = jedis.eval(lua, Collections.singletonList(lockKey),

分布式锁存在的问题

  • 客户端长时间阻塞导致锁失效问题

​ 计算时间内异步启动另外一个线程去检查的问题,这个key是否超时,当锁超时时间快到期且逻辑未执行完,延长锁超时时间。

  • **Redis服务器时钟漂移问题导致同时加锁
    redis的过期时间是依赖系统时钟的,如果时钟漂移过大时 理论上是可能出现的 **会影响到过期时间的计算。

  • 单点实例故障,锁未及时同步导致丢失

    RedLock算法

  1. 获取当前时间戳T0,配置时钟漂移误差T1

  2. 短时间内逐个获取全部N/2+1个锁,结束时间点T2

  3. 实际锁能使用的处理时长变为:TTL - (T2 - T0)- T1

    该方案通过多节点来防止Redis的单点故障,效果一般,也无法防止:

  • 主从切换导致的两个客户端同时持有锁

    大部分情况下持续时间极短,而且使用Redlock在切换的瞬间获取到节点的锁,也存在问题。已经是极低概率的时间,无法避免。Redis分布式锁适合幂等性事务,如果一定要保证安全,应该使用Zookeeper或者DB,但是,性能会急剧下降

与zookeeper分布式锁对比

  • redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。
  • zk 分布式锁,注册个监听器即可,不需要不断主动尝试获取锁,ZK获取锁会按照加锁的顺序,所以是公平锁,性能和mysql差不多,和redis差别大

Redission生产环境的分布式锁

​ Redisson是基于NIO的Netty框架上的一个Java驻内存数据网格(In-Memory Data Grid)分布式锁开源组件。

image-20201221000119586

但当业务必须要数据的强一致性,即不允许重复获得锁,比如金融场景(重复下单,重复转账),请不要使用redis分布式锁。可以使用CP模型实现,比如:zookeeper和etcd。

Rediszookeeperetcd
一致性算法paxos(ZAB)raft
CAPAPCPCP
高可用主从集群n+1n+1
实现setNXcreateNoderestfulAPI

6、redis心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率向主服务器发送ACK命令:

​ 1、检测主从的连接状态 检测主从服务器的网络连接状态

​ lag的值应该在0或1之间跳动,如果超过1则说明主从之间的连接有 故障。

​ 2、辅助实现min-slaves,Redis可以通过配置防止主服务器在不安全的情况下执行写命令

min-slaves-to-write 3 (min-replicas-to-write 3 )

min-slaves-max-lag 10 (min-replicas-max-lag 10)

​ 上面的配置表示:从服务器的数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10 秒时,主服务器将拒绝执行写命令。

​ 3、检测命令丢失,增加重传机制

​ 如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发 送REPLCONF ACK命令时,主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量, 然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数据,并将这些数据重新发送给从服务器。

六、Redis实战

1、Redis优化

[外链图片转存中…(img-8clrTh4R-1625484806004)]

读写方式
简单来说就是不用keys等,用range、contains之类。比如,用户粉丝数,大 V 的粉丝更是高达几千万甚至过亿,因此,获取粉丝列表只能部分获取。另外在判断某用户是否关注了另外一个用户时,也只需要关注列表上进行检查判断,然后返回 True/False 或 0/1 的方式更为高效。

KV size
如果单个业务的 KV size 过大,需要分拆成多个 KV 来缓存。拆分时应考虑访问频率

key 的数量
如果数据量巨大,则在缓存中尽可能只保留频繁访问的热数据,对于冷数据直接访问 DB。

读写峰值
如果小于 10万 级别,简单分拆到独立 Cache 池即可
如果达到 100万 级的QPS,则需要对 Cache 进行分层处理,可以同时使用 Local-Cache 配合远程 cache,甚至远程缓存内部继续分层叠加分池进行处理。(多级缓存)

命中率
缓存的命中率对整个服务体系的性能影响甚大。对于核心高并发访问的业务,需要预留足够的容量,确保核心业务缓存维持较高的命中率。比如微博中的 Feed Vector Cache(热点资讯),常年的命中率高达 99.5% 以上。为了持续保持缓存的命中率,缓存体系需要持续监控,及时进行故障处理或故障转移。同时在部分缓存节点异常、命中率下降时,故障转移方案,需要考虑是采用一致性 Hash 分布的访问漂移策略,还是采用数据多层备份策略。

过期策略

​ 可以设置较短的过期时间,让冷 key 自动过期;也可以让 key 带上时间戳,同时设置较长的过期时间,比如很多业务系统内部有这样一些 key:key_20190801。

缓存穿透时间
平均缓存穿透加载时间在某些业务场景下也很重要,对于一些缓存穿透后,加载时间特别长或者需要复杂计算的数据,而且访问量还比较大的业务数据,要配置更多容量,维持更高的命中率,从而减少穿透到 DB 的概率,来确保整个系统的访问性能。

缓存可运维性
对于缓存的可运维性考虑,则需要考虑缓存体系的集群管理,如何进行一键扩缩容,如何进行缓存组件的升级和变更,如何快速发现并定位问题,如何持续监控报警,最好有一个完善的运维平台,将各种运维工具进行集成。

缓存安全性
对于缓存的安全性考虑,一方面可以限制来源 IP,只允许内网访问,同时加密鉴权访问。

2、Redis热升级

在 Redis 需要升级版本或修复 bug 时,如果直接重启变更,由于需要数据恢复,这个过程需要近 10 分钟的时间,时间过长,会严重影响系统的可用性。面对这种问题,可以对 Redis 扩展热升级功能,从而在毫秒级完成升级操作,完全不影响业务访问。

热升级方案如下,首先构建一个 Redis 壳程序,将 redisServer 的所有属性(包括redisDb、client等)保存为全局变量。然后将 Redis 的处理逻辑代码全部封装到动态连接库 so 文件中。Redis 第一次启动,从磁盘加载恢复数据,在后续升级时,通过指令,壳程序重新加载 Redis 新的 redis-4.so 到 redis-5.so 文件,即可完成功能升级,毫秒级完成 Redis 的版本升级。而且整个过程中,所有 Client 连接仍然保留,在升级成功后,原有 Client 可以继续进行读写操作,整个过程对业务完全透明。

3、分片/分区

​ 在Redis中,数据分片(分区)是一种在多个Redis实例之间拆分所有数据的技术,以便每个实例仅包含键的一个子集。 通过添加越来越多的实例并将数据划分为较小的部分(碎片或分区),这样的过程可以减轻数据的增长。不仅如此,这还意味着越来越多的计算能力可用于处理数据,有效地支持水平缩放。

分片(分区)的复杂性和缺点

  • 通常不支持涉及多个键的操作。 例如,如果两个集合( SINTER )存储在映射到不同Redis实例的键中,则不可能直接执行它们之间的交集。
  • 涉及映射到不同Redis实例的多个密钥的事务是不可能的。
  • 分区是基于键的,因此无法使用单个大键(很大的排序集或列表)对数据集进行分片(分区)。
  • 备份和持久管理要复杂得多:您必须处理多个RDB / AOF文件,备份涉及来自许多实例的RDB文件的聚合(合并)。
  • 除非您对此进行了计划,否则在运行时添加和删除实例可能会导致数据失衡(请参阅规划分片(分区) )。

分区的方案

  • 范围划分
    通过将对象范围映射到特定的Redis实例中来完成。 例如,假设我们正在存储一些用户数据,并且每个用户都有其唯一的标识符(ID)。 在我们的分区方案中,我们可以定义ID从0到10000的用户将进入实例Redis 1,而ID从10001到20000的用户将进入实例Redis 2,依此类推。 该方案的缺点是,范围和实例之间的映射应该被维护,并且应该与Redis中保留的对象(用户,产品等)的种类一样多的映射。

  • 哈希分区

    该方案适用于任何密钥,但涉及哈希函数:此函数应将密钥名称映射到某个数字。 假设我们有这样一个函数(我们称其为hash_func),这样的方案就是这样的:

    • 取得键名,并使用hash_func将其映射到数字

    哈希函数的选择非常重要。 良好的哈希函数可确保密钥在所有Redis实例上平均分布,因此不会在任何单个实例上建立过多的密钥。

  • 一致性哈希
    它是hash partitioning的一种高级形式,被许多解决方案用于数据分片(分区)。

分片(分区)实现

从实现的角度来看,根据应用程序的体系结构,有几种可能的数据分片(分区)实现:

  • 客户端分区
    客户端直接选择正确的实例来写入或读取给定的密钥。
  • 代理辅助分区
    客户端将请求发送到支持Redis协议的代理,而不是直接将请求发送到正确的Redis实例。 代理将确保根据配置的分区方案将请求转发到正确的Redis实例,并将答复发送回客户端(最著名的实现是Twitter的Twemproxyhttps://github.com/twitter/ twemproxy )。
  • 查询路由
    客户端将查询发送到随机Redis实例,该实例将确保将查询转发到正确的实例。 查询路由的混合形式假定客户端被重定向到正确的实例(但是查询不会直接从一个Redis实例转发到另一个实例)

slot槽位

​ redis中,总共划分了16384个slot,并把集群中所有实例映射到这些slot上 [0-16383],比如上面有5个master,redisCluster在创建的时候,可以将slot映射为:

redis10-3270
redis23271-6542
redis36543-9814
redis49815-13087
redis513088-16383

存储时使用crc16 算法计算key值,并对16384取余,最终落到争取的redis。

注意: slot在cluster上必须是连续的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值