Redis命令使用技巧之Keys的相关操作
前言
介绍完Redis连接相关命令后,再来介绍一下与Key相关的命令,Redis作为一个key-value数据库,对Key进行操作是无法避免的。
KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,你最好还是用 Redis 的集合结构(set)来代替。
DEL
最早可用版本1.0.0
删除指定的键值对,如果指定的key不存在,则忽略。DEL命令的时间复杂度是O(N),对于除字符串外的其他数据类型,命令的时间复杂度为O(M),M是值的元素的个数。所以,在生产环境尽量避免一次性删除过多复杂数据类型的操作。
127.0.0.1:6379> SET key1 "jackey" OK 127.0.0.1:6379> SET key2 "zhe" OK 127.0.0.1:6379> DEL key1 key2 key3 (integer) 2
DUMP
最早可用版本2.6.0
使用一种Redis的格式序列化指定键存储的值。可用使用RESTORE命令将这个值反序列化。
这种序列化格式有以下3个特点:
- 它包含有64位的校验和,用于错误检查,RESTORE命令在反序列化之前会先检查校验和
- 值的编码格式和RDB文件的编码格式相同
- RDB的版本会被序列化到值中,因此,不同版本的Redis可能会因为不兼容RDB版本而拒绝反序列化
序列化的值不包含过期时间的相关信息,可以使用PTTL命令获取当前值的存活时间。如果值不存在则会返回nil
127.0.0.1:6379> SET key1 "jackey" OK 127.0.0.1:6379> DUMP key1 "\x00\x06jackey\b\x00\xec\x89'G'X\xfc:" 127.0.0.1:6379> DUMP not-exist-key (nil)
DUMP时间复杂度分为两部分:访问key值的时间复杂度为O(1),而序列化值的时间复杂度为O(N*M),N是组成值的元素的数量,M是元素的平均大小。如果序列化比较短的字符串,则该命令的时间复杂度可以看做O(1)。
EXISTS
最早可用版本1.0.0
用于判断key是否存在。3.0.3版本以后支持多参数,即可以一次性判断多个key,返回值是存在的key的数量。对于判断单个key是否存在,会返回1或者0,因此,该命令是向后兼容的。
需要注意的是:如果参数中有重复的存在命令,则返回结果不会去重。
127.0.0.1:6379> SET key1 "jackey" OK 127.0.0.1:6379> SET key2 "zhe" OK 127.0.0.1:6379> EXISTS key1 (integer) 1 127.0.0.1:6379> EXISTS not-exist-key (integer) 0 127.0.0.1:6379> EXISTS key1 key2 not-exist-key (integer) 2 127.0.0.1:6379> EXISTS key1 key1 key1 (integer) 3
EXPIRE
最早可用版本1.0.0
为指定的key设置存活时间。存活时间会被DEL,SET,GETSET和所有的STORE命令删除或者覆盖。如果我们只修改key的值而不修改存活时间或者保存到一个新的key中,则原来的key的存活时间保持不变。如果使用RENAME对一个key重命名,那么原有key的存活时间会赋给新的key。
如果想要清除存活时间,使指定的key成为一个永久的key,则可以使用PERSIST命令,我们稍后会详细介绍这个命令。
如果使用EXPIRE/PEXPIRE为某个key设置的存活时间为非正数,或者使用EXPIREAT/PEXPIREAT设置了一个过去的时间,则这个key会直接被删除。
127.0.0.1:6379> EXPIRE key1 -1 (integer) 1 127.0.0.1:6379> EXISTS key1 (integer) 0
对一个已经有存活时间的key再次使用EXPIRE设置存活时间,则将key的存活时间更新,在许多应用中我们都会用到这一点。
注意:在Redis的2.1.3版本之前,如果修改一个带有存活时间的key的值,则会删除整个key。
关于时间精度,Redis2.4版本中,一个key过期的一秒内仍可以访问,而到了2.6版本,这一时间已经被精确到了1毫秒。因为从2.6版本开始,存活时间保存的是绝对时间(Unix的时间戳),而这就意味着,你的计算机的时间需要保证可靠,如果你将RDB文件放到另一台机器上加载,当这两台机器的时间差距较大时,你就会发现可能有些key被删除了或者有些key的存活时间被延长了。
下面我们在来讨论一下Redis究竟是如何使key过期的,Redis的过期策略有两种:一种是被动的,一种是主动的。
被动过期就是当客户端访问某个key,服务端会去检查这个key的存活时间,判断是否过期。当然,这种过期策略存在一定的问题,如果某个key一直都不访问,就不会被发现它过期了,那么它将永远“苟活”在内存中。所以Redis会定期随机的查看被设置过存活时间的key,看它们是否过期,如果过期了,就会及时清理掉。Redis每秒会做10次下面的操作:
- 随机查看20个设置过存活时间的key(从设置存活时间的set中取)
- 删除所有过期的key
- 如果过期的key超过25%,那么会从第一步开始再执行一次
EXPIREAT
最早可用版本1.2.0
此命令和EXPIRE的作用相同,不同之处是它的参数需要传Unix时间戳(即从1970年1月1日起的毫秒数)。
127.0.0.1:6379> GET key2 "zhe" 127.0.0.1:6379> EXPIREAT key2 1537733374 (integer) 1 127.0.0.1:6379> TTL key2 (integer) 12960
KEYS
最早可用版本1.0.0
这个命令会返回匹配到的所有key,时间复杂度为O(N)。在官方文档中说,在入门级的笔记本电脑上,Redis扫描100万条key只需要40毫秒,但是我们仍然要避免在生产环境使用这个命令。特别是千万不要使用KEYS *这样的命令,因为你不知道生产环境存在多少key,这样的命令有可能使你的生产环境的Redis陷入很长一段时间的不可用状态。所以,请马上删除应用层代码中的KEYS命令或者抓紧时间更新自己的简历。
如果需要查找key,可以使用SCAN命令或者sets命令。
虽然我们非常不建议使用KEYS命令,但是它的匹配策略还是要介绍一下的:
?是单个字符的通配符,*是任意个数的通配符,[ae]会匹配到a或e,^e表示不匹配e,a-c表示匹配a或b或c,特殊符号使用\隔开。
127.0.0.1:6379> MSET key1hello jackey key2hello zhe age 3 OK 127.0.0.1:6379> KEYS key?hello 1) "key1hello" 2) "key2hello" 127.0.0.1:6379> KEYS k* 1) "key1hello" 2) "key2hello" 127.0.0.1:6379> KEYS *age* 1) "age" 127.0.0.1:6379> KEYS * 1) "age" 2) "key1hello" 3) "key2hello"
MIGRATE
最早可用版本2.6.0
这个命令用来将源实例的key以原子操作传输到目标实例,然后将源实例的key删除。相当于在源实例执行了DUMP+DEL操作,在目标实例执行了RESTORE操作。这一操作会阻塞进行传输的两个实例,在传输过程中,key总会存在于一个实例中,除非发生超时错误。在3.2版本以后,MIGRATE可以将多个key作为管线一次性传输。
在执行MIGRATE命令时,必须要设置一个超时时间,如果到了超时时间命令仍未执行完,则会抛出一个IOERR。但返回这个错误时,两个实例的状态可能有两种:要么两个实例都存在指定的key,要么只有源实例存在指定的key。总之,key是不会丢失的。
从3.0.6版本开始,MIGRATE支持一次传输多个key,为了保证不过载或者出现环形操作,MIGRATE需要使用KEYS参数,而原来指定的key的参数要被设置为空字符串。
MIGRATE 192.168.1.34 6379 "" 0 5000 KEYS key1 key2 key3
这里还有两个选填参数需要介绍:一个是COPY,加上这个参数的话,传输完成后不会删除源实例中的key。另一个是REPLACE,这个参数的作用是替换目标实例已存在的key。这两个参数在3.0版本以后才可以使用。
MOVE
最早可用版本1.0.0
不知道大家还记不记得前文中我们提到过的SELECT命令,SELECT用来切换数据库。使用MOVE命令就是将当前数据库的key移动到指定的数据库中,如果指定库中已经存在这个key或者当前库不存在这个key,那么这个命令什么也不做。
127.0.0.1:6379> KEYS * 1) "age" 2) "key1hello" 3) "key2hello" 127.0.0.1:6379> MOVE age 1 (integer) 1 127.0.0.1:6379> KEYS * 1) "key1hello" 2) "key2hello" 127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> KEYS * 1) "age"
OBJECT
最早可用版本2.2.3
OBJECT用来查看Redis对象内部的相关信息。这一命令在调试时经常被使用。下面我们来介绍OBJECT命令的具体用法:
- OBJECT REFCOUNT key:返回指定key的值的引用数量
- OBJECT ENCODING key:返回指定key的内部存储使用的编码格式
- OBJECT IDLETIME key:返回指定key的空闲时间(有多长时间没有被读写),目前最小精度为10秒,这一命令经常在Redis淘汰机制中使用(淘汰策略为LRU或noeviction)
- OBJECT FREQ key:返回指定key访问频率的对数,当淘汰策略为LFU时,这一命令会被用到
- OBJECT HELP:返回帮助信息
对象的编码格式也有很多种:
- Strings会被编码为raw或int
- Lists会被编码为ziplist或linkedlist
- Sets会被编码为intset或hashtable
- Hashs会被编码为ziplist或hashtable
- Sorted Sets会被编码为ziplist或skiplist
127.0.0.1:6379> OBJECT REFCOUNT key1hello (integer) 1 127.0.0.1:6379> OBJECT IDLETIME key2hello (integer) 3637 127.0.0.1:6379> OBJECT ENCODING age "int"
PERSIST
最早可用版本2.2.0
删除指定key的过期时间,使之变成永久的key。
PEXPIRE
最早可用版本2.6.0
PEXPIRE的作用和EXPIRE一样,只不过参数中的时间单位是毫秒。
PEXPIREAT
最早可用版本2.6.0
作用和EXPIREAT相同,参数同样是毫秒。
PTTL
最早可用版本2.6.0
返回指定key的剩余存活时间的毫秒数。2.8以后的版本返回值有些变化,如果key不存在,则返回-2;如果key是永久的,则返回-1。
RANDOMKEY
最早可用版本1.0.0
此命令用于从当前数据库返回一个随机的key。
RENAME
最早可用版本1.0.0
重命名一个key。如果key不存在,则会返回错误。而如果新的key已经存在,则此命令会覆盖原来的key(它其实是执行了一个隐式的DEL命令,因此如果原来的key存储的对象很大的话, 删除操作延时会很高)。在3.2版本以前,如果源key和目标key相同的话,会报错。
RENAMENX
如果新的key不存在的话,重命名key,如果存在的话返回0,成功返回1。
RESTORE
最早可用版本2.6.0
用法:RESTORE key ttl serialized-value [REPLACE]
此命令是将一组数据反序列化,并存到key。如果ttl是0,则key是永久的。在Redis3.0版本以后,如果不使用REPLACE参数并且key已经存在,则会返回一个错误“Target key name is busy”。
SCAN
最早可用版本2.8.0
用法:SCAN cursor MATCH pattern COUNT count
其中cursor为游标,MATCH和COUNT为可选参数。
SCAN命令和SSCAN、HSCAN、ZSCAN命令都用于增量的迭代元素集,它每次返回小部分数据,不会像KEYS那样阻塞Redis。SCAN命令是基于游标的,每次调用后,都会返回一个游标,用于下一次迭代。当游标返回0时,表示迭代结束。
SCAN每次返回的数量并不固定,也有可能返回数据为空。另外,SCAN命令和KEYS命令一样支持匹配。
我们在Redis里存入10000个key用于测试。
结果如下:
127.0.0.1:6379> scan 0 match key24* count 1000 1) "1688" 2) 1) "key2411" 2) "key2475" 3) "key2494" 4) "key2406" 5) "key2478" 127.0.0.1:6379> scan 1688 match key24* count 1000 1) "2444" 2) 1) "key2458" 2) "key249" 3) "key2407" 4) "key2434" 5) "key241" 6) "key2497" 7) "key2435" 8) "key2413" 9) "key2421" 10) "key248" 127.0.0.1:6379> scan 2444 match key24* count 1000 1) "818" 2) 1) "key2459" 2) "key2462" 3) "key2409" 4) "key2454" 5) "key2431" 6) "key2423" 7) "key2476" 8) "key2428" 9) "key2493" 10) "key2420" 127.0.0.1:6379> scan 818 match key24* count 1000 1) "9190" 2) 1) "key2402" 2) "key2415" 3) "key2429" 4) "key2424" 5) "key2425" 6) "key2400" 7) "key2472" 8) "key2479" 9) "key2448" 10) "key245" 11) "key2487" 12) "key2430" 13) "key2405" 127.0.0.1:6379> scan 9190 match key24* count 1000 1) "12161" 2) 1) "key2488" 2) "key2437" 3) "key2404" 4) "key2440" 5) "key2461" 6) "key2416" 7) "key2436" 8) "key2403" 9) "key2460" 10) "key2452" 11) "key2449" 12) "key2482" 127.0.0.1:6379> scan 12161 match key24* count 1000 1) "11993" 2) 1) "key2483" 2) "key2491" 3) "key242" 4) "key2466" 5) "key2446" 6) "key2465" 7) "key243" 8) "key2438" 9) "key2457" 10) "key246" 11) "key2422" 12) "key2418" 127.0.0.1:6379> scan 11993 match key24* count 1000 1) "7853" 2) 1) "key2498" 2) "key2451" 3) "key2439" 4) "key2495" 5) "key2408" 6) "key2410" 127.0.0.1:6379> scan 7853 match key24* count 1000 1) "5875" 2) 1) "key2486" 2) "key2490" 3) "key244" 4) "key2401" 5) "key2463" 6) "key2481" 7) "key2477" 8) "key2468" 9) "key2433" 10) "key2489" 11) "key2455" 12) "key2426" 13) "key24" 14) "key2450" 15) "key2414" 16) "key2442" 17) "key2473" 18) "key2467" 19) "key2469" 20) "key2456" 127.0.0.1:6379> scan 5875 match key24* count 1000 1) "14311" 2) 1) "key2453" 2) "key2492" 3) "key2480" 4) "key2427" 5) "key2443" 6) "key2417" 7) "key2432" 8) "key240" 9) "key2445" 10) "key2484" 11) "key2444" 12) "key247" 13) "key2485" 127.0.0.1:6379> scan 14311 match key24* count 1000 1) "16383" 2) 1) "key2441" 2) "key2474" 3) "key2447" 4) "key2471" 5) "key2470" 6) "key2464" 7) "key2412" 8) "key2419" 9) "key2499" 10) "key2496" 127.0.0.1:6379> scan 16383 match key24* count 1000 1) "0" 2) (empty list or set)
可以看到虽然我们设置的count为1000,但Redis每次返回的数值只有10个左右。
SORT
最早可用版本1.0.0
当有N个元素需要排序,并且要返回M个元素时,SORT命令的时间复杂度为O(N+M*log(M))
此命令用于返回或保存list,set和sorted set的键,默认将数字或者可排序的key进行排序,Redis会将其视为双精度浮点数。
如果想要对字符串按字典顺序排序,可以使用ALPHA参数。
如果想要按照外部字段进行排序,可以使用BY参数。
TOUCH
最早可用版本3.2.1
修改某一个或多个key的最后访问时间,如果key不存在,则忽略。
TTL
最早可用版本1.0.0
返回指定key的剩余存活时间,单位为秒。
在2.6版本及以前,如果key不存在或者是永久key,都会返回-1。从2.8版本开始,如果key不存在,则返回-2,如果key为永久key,则返回-1。
TYPE
最早可用版本1.0.0
返回key存储的值的类型。类型即为我们在Redis基础数据结构一文中描述的5中数据类型。
String
String是最基本的,也是最常用的类型。它是二进制安全的,也就是说,我们可以将对象序列化成json字符串作为value值存入Redis。在分配内存时,Redis会为一个字符串分配一些冗余的空间,以避免因字符串的值改变而出现频繁的内存分配操作。当字符串长度小于1M时,每次扩容都会加倍现有空间,当长度大于1M时,每次扩容,增加1M,Redis字符串的最大长度是512M。
Hash
Hash是键值对集合,相当于Java中的HashMap,实际结构也和HashMap一样,是数组+链表的结构。所不同的是扩容的方式不同,HashMap是进行一次rehash,而Redis为了不阻塞服务,会创建一个新的数组,在查询时会同时查询两个Hash,然后在逐渐将旧的Hash内容转移到新的中去。一个Hash最大可以存储232-1个键值对。
List
List相当于Java中的LinkedList,它的插入和删除操作的时间复杂度为O(1),而查询操作的时间复杂度为O(n)。我们可以利用List的rpush、rpop、lpush和lpop命令来构建队列或者栈。列表最多可以存储232-1个元素。
Set
Set是String类型的无序集合,并且元素唯一,相当于Java中的HashSet,它的插入、删除、查询操作的时间复杂度都是O(1)。其最大元素数也是232-1个。
zset
zset可以看做是Java中SortedSet和HashMap的结合,一方面它不允许元素重复,另一方面,它通过score为每个元素进行排序。
UNLINK
最早可用版本4.0.0
这个命令和DEL类似,会删除指定的key。所不同的是,此命令的时间复杂度为O(1),它先将key从keyspace中删除,此时指定的key已经删除,但是内存没有释放。所以,这个命令会在另一个线程中做释放内存的操作。这一步的操作时间复杂度为O(N)。
WAIT
最早可用版本3.0.0
这个命令会阻塞客户端,直到前面所有的写操作都完成并且保存了指定数量的副本。该命令总会返回副本数量或者超时。
总结