Redis 中返回1就是成功,0、-1、nul则为失败

常用命令

  1. select   0~15                      
  2. dbsize
  3. flushdb
  4. flushall

key常用命令

  1. set   k1   v1                   
  2. get   k1                          
  3. set   k1   ty
  4. keys *
  5. exists k3                     判断 k3 键是否存在
  6. move k3 1
  7. ttl k3                           查看 k3 还有多少秒过期(-1表示永不过期,-2表示已过期),过期就不存在库中
  8. randomkey
  9. rename k2 k3
  10. renamenx k2 k3
  11. expire k3 20
  12. type k3

String常用命令

Redis的字符串是动态字符串,支持扩容(一倍)。一个Key默认的储存大小的512M.

  1. del k1
  2. append k1 234               向键 k1 的 value 后面追加 “234” 字符串(拼接一个字符串,因为键默认是字符串类型
  3. strlen k1
  4. incr k1                           键 k1 对应的 value 加 1(必须是数字类型的字符串)原子操作
  5. decr k1                           键 k1 对应的 value 减 1(必须是数字类型的字符串)原子操作
  6. incrby k1 7                   键 k1 对应的 value 加 7(必须是数字类型的字符串
  7. decrby k1 5                   键 k1 对应的 value 减 5(必须是数字类型的字符串
  8. getrange k1 0 -1        获取键 k1 所对应的 value 的所有位置的值 == get k1
  9. getrange k1 0 2          获取键 k1 所对应的 value 的字符串的前三个字符串(就相当于 substring
  10. setrange k1 1 000
  11. setex k2 10 vvv
  12. setnx k1 v1
  13. mset k1 v1 k2 v2
  14. mget k1 k2 k3              同时返回多个键对应的 value(不存在的键会返回 nil
  15. msetnx k3 v3 k1 v1    不存在的键才会设置(只要有一个键设置失败,其他的都失败
  16. getset k1 v1

Redis的scan命令

      熟悉Redis的人都知道,它是单线程的。因此在使用一些时间复杂度为O(N)的命令时要非常谨慎。可能一不小心就会阻塞进程,导致Redis出现卡顿。

      有时,我们需要针对符合条件的一部分命令进行操作,比如删除以test_开头的key。那么怎么获取到这些key呢?在Redis2.8版本之前,我们可以使用keys命令按照正则匹配得到我们需要的key。但是这个命令有两个缺点:

  • 没有limit,我们只能一次性获取所有符合条件的key,如果结果有上百万条,那么等待你的就是“无穷无尽”的字符串输出。
  • keys命令是遍历算法,时间复杂度是O(N)。如我们刚才所说,这个命令非常容易导致Redis服务卡顿。因此,我们要尽量避免在生产环境使用该命令。

在满足需求和存在造成Redis卡顿之间究竟要如何选择呢?面对这个两难的抉择,Redis在2.8版本给我们提供了解决办法——scan命令。

相比于keys命令,scan命令有两个比较明显的优势:

  • scan命令的时间复杂度虽然也是O(N),但它是分次进行的,不会阻塞线程。
  • scan命令提供了limit参数,可以控制每次返回结果的最大条数。

这两个优势就帮助我们解决了上面的难题,不过scan命令也并不是完美的,它返回的结果有可能重复,因此需要客户端去重。

Redis的结构

Redis使用了Hash表作为底层实现,原因不外乎高效且实现简单。说到Hash表,很多Java程序员第一反应就是HashMap。没错,Redis底层key的存储结构就是类似于HashMap那样数组+链表的结构。其中第一维的数组大小为2n(n>=0)。每次扩容数组长度扩大一倍。

scan命令就是对这个一维数组进行遍历。每次返回的游标值也都是这个数组的索引。limit参数表示遍历多少个数组的元素,将这些元素下挂接的符合条件的结果都返回。因为每个元素下挂接的链表大小不同,所以每次返回的结果数量也就不同。

SCAN的遍历顺序

关于scan命令的遍历顺序,我们可以用一个小栗子来具体看一下。

127.0.0.1:6379> keys *
1) "db_number"
2) "key1"
3) "myKey"
127.0.0.1:6379> scan 0 MATCH * COUNT 1
1) "2"
2) 1) "db_number"
127.0.0.1:6379> scan 2 MATCH * COUNT 1
1) "1"
2) 1) "myKey"
127.0.0.1:6379> scan 1 MATCH * COUNT 1
1) "3"
2) 1) "key1"
127.0.0.1:6379> scan 3 MATCH * COUNT 1
1) "0"
2) (empty list or set)

我们的Redis中有3个key,我们每次只遍历一个一维数组中的元素。如上所示,SCAN命令的遍历顺序是

0->2->1->3

这个顺序看起来有些奇怪。我们把它转换成二进制就好理解一些了。

00->10->01->11

       我们发现每次这个序列是高位加1的。普通二进制的加法,是从右往左相加、进位。而这个序列是从左往右相加、进位的。这里大家可能会有疑问了,为什么要使用这样的顺序进行遍历,而不是用正常的0、1、2……这样的顺序呢,这是因为需要考虑遍历时发生字典扩容与缩容的情况(不得不佩服开发者考虑问题的全面性)。

我们来看一下在SCAN遍历过程中,发生扩容时,遍历会如何进行。加入我们原始的数组有4个元素,也就是索引有两位,这时需要把它扩充成3位,并进行rehash。
 

redis过期时间-2 Redis过期时间为-1的key_数组

       原来挂接在xx下的所有元素被分配到0xx和1xx下。在上图中,当我们即将遍历10时,dict进行了rehash,这时,scan命令会从010开始遍历,而000和100(原00下挂接的元素)不会再被重复遍历。

       再来看看缩容的情况。假设dict从3位缩容到2位,当即将遍历110时,dict发生了缩容,这时scan会遍历10。这时010下挂接的元素会被重复遍历,但010之前的元素都不会被重复遍历了。所以,缩容时还是可能会有些重复元素出现的。