美文网首页
二、Redis 键 管理

二、Redis 键 管理

作者: Suny____ | 来源:发表于2020-08-04 17:17 被阅读0次

    本文参考书籍:《Redis开发与运维》

    键重命名

    • rename key newkey
    • 需要注意的是,如果newkey存在,值会被覆盖
    127.0.0.1:6379> set a b
    OK
    127.0.0.1:6379> set c d
    OK
    127.0.0.1:6379> rename a c
    OK
    127.0.0.1:6379> get a
    (nil)
    127.0.0.1:6379> get c
    "b"
    

    为了防止被强行rename,Redis提供了renamenx命令,确保只有newkey不存在时才可以修改,当newkey存在时会返回0,代表没有完成重命名。

    127.0.0.1:6379> keys *
    1) "a"
    2) "c"
    127.0.0.1:6379> renamenx a c
    (integer) 0
    

    注意

    由于重命名键期间会执行del命令删除旧键,如果键对应的值较大,会存在阻塞Redis的可能
    如果rename和renamenx中的key和newkey相同,在Redis3.2和之前的版本返回结果是不同的
    Redis3.2会返回OK
    Redis3.2之前的版本会提示错误

    127.0.0.1:6379> renamenx a a
    (error) ERR source and destination objects are the same
    
    • 随机返回一个键 randomkey
    127.0.0.1:6379> randomkey
    "c"
    127.0.0.1:6379> randomkey
    "a"
    

    键过期

    • Redis提供了以下几个命令来执行键过期相关的操作:
    • expire、expireat、pexpire、pexpireat、ttl、pttl、persist等。
      • expire key seconds:键在seconds秒后过期
      • pexpire key milliseconds:键在milliseconds毫秒后过期
    127.0.0.1:6379> expire a 50
    (integer) 1
    127.0.0.1:6379> ttl a
    # 还剩47秒
    (integer) 47
    ...
    127.0.0.1:6379> ttl a
    # 返回结果为-2 说明键已过期(删除)
    (integer) -2
    127.0.0.1:6379> get a
    # 已经被删除了
    (nil)
    
    • expireat key timestamp:键在秒级时间戳timestamp后过期
    • pexpire key milliseconds-timestamp:键在毫秒级时间戳timestamp后过期
    127.0.0.1:6379> expireat c 1557370250
    (integer) 1
    127.0.0.1:6379> ttl c
    (integer) 10
    ...
    127.0.0.1:6379> ttl c
    (integer) -2
    127.0.0.1:6379> get c
    (nil)
    

    ttl命令和pttl命令都可以查询键的剩余过期时间,但是pttl精度更高可以达到毫秒级别,有三种返回值:

    1. 大于等于0的整数:键剩余过期时间(ttl是秒,pttl是毫秒)
    2. 键没有设置过期时间
    3. 键不存在

    无论使用过期还是时间戳,秒级还是毫秒级,Redis内部最终都是使用pexpireat。
    注意:

    1. 如果expire key的键不存在,返回结果为0
    2. 如果过期时间为负值,键会立即被删除
    3. persist命令可以将键的过期时间清除
    127.0.0.1:6379> expire a 30
    (integer) 1
    127.0.0.1:6379> ttl a
    (integer) 20
    127.0.0.1:6379> persist a
    (integer) 1
    127.0.0.1:6379> ttl a
    (integer) -1
    
    1. 对于字符串类型键,执行set命令会去掉过期时间
    127.0.0.1:6379> expire a 30
    (integer) 1
    127.0.0.1:6379> ttl a
    (integer) 25
    127.0.0.1:6379> set a c
    OK
    127.0.0.1:6379> ttl a
    (integer) -1
    
    1. Redis不支持二级数据结构(哈希、列表)内部元素的过期功能,也就是不能对列表类型的一个元素做过期时间的设置。
    2. setex命令作为set+expire的组合,不但是原子执行,同时减少了一次忘了通讯时间

    迁移键

    迁移键功能非常重要,有时候我们需要把部分数据由一个Redis迁移到另一个Redis(例如生产环境迁移到测试环境),Redis发展历程中提供了move、dump+restore、migrate三种迁移键的方法,它们的实现方式及使用场景不太相同。

    • move

    move key db

    move命令用于在Redis内部进行数据迁移,Redis内部可以有多个数据库,彼此的数据是相互隔离的。move key db就是把指定的键从源数据库移动到目标数据库中。但这个功能不建议在生产环境使用。

    image.png
    • dump + restore

    dump key
    restore key ttl value

    dump + restore可以实现在不同的Redis实例之间进行数据迁移的功能,整个迁移过程分为两步:

    • 在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式。
    • 在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0表示没有过期时间。
      image.png

    注意:

    整个迁移过程并非原子性的,而是通过客户端分步完成
    迁移过程是开启了两个客户端连接,所以dump的结果不是在源Redis和目标Redis之间进行传输

    在源Redis上执行dump

    127.0.0.1:6379> dump a
    "\x00\x01c\x06\x00>\xf1\x90\xdfz\xe7<\xe4"
    

    在目标Redis上执行restore

    127.0.0.1:6379> restore a 0 "\x00\x01c\x06\x00>\xf1\x90\xdfz\xe7<\xe4"
    OK
    127.0.0.1:6379> get a
    "c"
    
    • migrate

    migrate host port key|"" destination-db timeout [copy] [replace] [keys key1 key2 ...]

    参数说明:

    • host:目标Redis的IP
    • port:目标Redis的端口
    • key|"":在Redis3.0.6版本之前,migrate只支持迁移一个键,所以此处是要迁移的键,但Redis3.0.6版本之后支持迁移多个键,如果当前需要迁移多个键,此处为空字符串""
    • destination-db:目标Redis的数据库索引,例如0号数据库,这里就写0
    • timeout:迁移的超时时间(毫秒)
    • [copy]:如果添加此选项,迁移后不删除源键
    • [replace]:如果添加此选项,migrate不管目标Redis是否存在该键都会正常迁移进行数据覆盖
    • [keys key1 key2...]:迁移多个键,例如要迁移key1、key2,此处填keys key1 key2

    migrate命令也是用于Redis实例间数据迁移的,不过这个命令是将dump、restore、del三个命令进行组合,从而简化了操作流程。
    migrate命令具有原子性,而且从Redis3.0.6版本以后已经支持迁移多个键的功能,有效地提高了迁移效率。
    migrate命令实现过程和dump+restore基本类似,但是有3点不太相同:

    • 整个过程都是原子执行的,不需要再多个Redis实例上开启客户端,只需在源Redis上执行migrate命令即可
    • migrate命令的数据传输直接在源Redis和目标Redis上完成
    • 目标Redis完成restore后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否要源Redis上删除对应的键
      image.png

    例1:

    • 源Redis有键hello,目标Redis没有
    127.0.0.1:6380> migrate 127.0.0.1 6379 hello 0 1000
    OK
    

    例2:

    • 源Redis和目标Redis都有键hello
    • 如果命令没有加replace选项会收到错误提示,加了replace会返回OK表示成功
    127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000
    (error) ERR Target instance replied with error: BUSYKEY Target key name already
    
    127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000 replace
    OK
    

    例3:

    • 源Redis没有键hello
    • 这种情况使用migrate会收到nokey的提示:
    127.0.0.1:6379> migrate 127.0.0.1 6380 hello 0 1000
    NOKEY
    

    例4:

    • Redis3.0.6版本后迁移多个键
    127.0.0.1:6379> migrate 127.0.0.1 6380 "" 0 5000 keys key1 key2 key3
    OK
    

    根据三种迁移方式的比较,建议使用migrate进行迁移。

    image.png

    遍历键

    Redis提供了两个命令遍历所有的键,分别是keys和scan。

    • 全量遍历键

    keys pattern

    keys命令支持pattern匹配,例如向一个空Redis插入4个字符串类型的键值对:

    127.0.0.1:6379> mset hello world redis best jedis best hill high
    OK
    # 获取所有键
    127.0.0.1:6379> keys *
    1) "hill"
    2) "redis"
    3) "hello"
    4) "jedis"
    

    上面遍历了所有的键,pattern直接使用星号,这是因为pattern使用的是glob风格的通配符:

    • *:代表匹配任意字符
    127.0.0.1:6379> keys h*
    1) "hill"
    2) "hello"
    
    • ?:代表匹配一个字符
    127.0.0.1:6379> keys hell?
    1) "hello"
    
    • []:匹配任意一个字符
    # 匹配a-z之间的任意字符
    127.0.0.1:6379> keys h[a-z]ll
    1) "hill"
    # 匹配e或i之中的一个
    127.0.0.1:6379> keys h[e,i]ll
    1) "hill"
    127.0.0.1:6379> keys h[e,i]llo
    1) "hello"
    

    若要删除所有以某个字符开头的键,可以执行如下操作:

    # 利用Linux的管道删除
    ./redis-cli keys h* | xargs ./redis-cli del
    

    考虑到Redis的单线程架构,如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞,所以一般不再生产环境下使用keys命令。但有时候确实有遍历键的需求,可以在以下三种情况使用:

    • 在一个不对外提供服务的Redis从节点执行,这样不会阻塞到客户端的请求,但是会影响主从复制。

    • 如果确认键值总数确实较少,可以执行。

    • 使用scan命令渐进式的遍历,可以有效防止阻塞。

    • 渐进式遍历
      Redis从2.8版本后,提供了一个新的命令scan,他能有效的解决keys命令存在的问题。和keys命令执行时会遍历所有键不同,scan采用渐进式遍历的方式解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1),但是要真正实现keys的功能,需要执行多次scan。
      Redis存储键值对实际使用的是hashtable的数据结构。每次执行scan,可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。
      image.png

    scan使用方法:

    scan cursor [match pattern] [count number]

    参数说明:

    • cursor:是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
    • match pattern:是可选参数,他的作用是做模式的匹配,这点和keys的模式匹配很想。
    • count number:是可选参数,它的作用是表明每次要遍历的键个数,默认10

    例:

    127.0.0.1:6379> scan 0
    # 下次scan的游标
    1) "26"
    2)  1) "z"
        2) "a"
        3) "q"
        4) "v"
        5) "k"
        6) "u"
        7) "x"
        8) "y"
        9) "g"
       10) "c"
       
    127.0.0.1:6379> scan 26
    1) "3"
    2)  1) "t"
        2) "f"
        3) "j"
        4) "m"
        5) "i"
        6) "o"
        7) "p"
        8) "s"
        9) "l"
       10) "b"
       
    # 使用模式匹配 + 条数限制
    127.0.0.1:6379> scan 0 match [a-z] count 3
    1) "24"
    2) 1) "z"
       2) "a"
       3) "q"
    

    除了scan之外,Redis还提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似。
    下面以sscan为例子说明,当前集合有两种类型的元素,例如分别以old:user和new:user开头,这里查询old:user开头的元素

    127.0.0.1:6379> sadd test_scan old:user1 old:user2 new:user1 new:user2
    (integer) 4
    
    127.0.0.1:6379> sscan test_scan 0 match old:user?
    1) "0"
    2) 1) "old:user2"
       2) "old:user1"
    

    渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果再scan的过程中如果有键的变化(增、删、改),那么遍历效果可能会遇到如下问题:
    新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。

    数据库管理

    Redis提供了几个面向数据库的操作,它们分别是dbsize、select、flushdb、flushall命令。

    • 切换数据库

    select dbIndex

    Redos使用数字作为多个数据库的实现。Redis默认配置中是有16个数据库(0-15)。各个数据库之间的数据没有任何关联,可以存在相同的键。
    虽然Redis提供了多数据库,但是现在并不推荐使用。Redis3.0中已经逐渐弱化这个功能,例如Redis的分布式实现Redis Cluster只允许使用0号数据库,只不过为了向下兼容老版本的数据库功能才没有废弃掉。
    下面分析一下为什么要废弃掉这个“优秀”的功能?总结起来有三点:

    • Redis是单线程的。如果使用多个数据库,那么这些数据库仍然是使用一个CPU,彼此之间还是会受到影响。
    • 多数据库的使用方式,会让调试和运维不同业务的数据库变的困难,假如有一个慢查询存在,依然会影响其他数据库,这样会使得别的业务方定位问题非常困难。
    • 部分Redis客户端根本不支持这种方式。即使支持,开发的时候来回切换数字形式的数据库,很容易弄乱。

    建议还是可以在一台机器上部署多个Redis实例,彼此用端口做区分,因为现代计算机或者服务器通常是多核CPU。这样既保证了业务之间不会受到影响,又合理地使用了CPU资源。

    • flushdb、flushall
      flushdb、flushall命令用于清除数据库,两者的区别是flushdb只清除当前数据库,flushall会清除所有数据库。
      虽然这两个命令可以非常方便的清理数据,但是也带来两个问题:
    • 会将所有数据清除,一旦误操作后果不堪设想。不过可以通过rename-command配置规避这个问题,以及如何在误操作后快速恢复数据。
    • 如果当前数据库键值数据较多,flushdb/flushall存在阻塞Redis的可能性。
      所以使用flushdb、flushall一定要小心谨慎。

    重点回顾

    • Redis提供5种数据结构,每种数据结构都有多种内部编码实现。

    • 纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能的三个因素。

    • 由于Redis的单线程架构,所以需要每个命令能被快速执行完,否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和运维Redis的核心之一。

    • 批量操作(例如mget、mset、hmset等)能够有效提高命令执行的效率,但要注意每次批量操作的个数和字节数。

    • 了解每个命令的时间复杂度在开发中至关重要,例如在使用keys、hgetall、smembers、zrange等时间复杂度较高的命令时,需要考虑数据规模对于Redis的影响。

    • persist命令可以删除任意类型键的过期时间,但是set命令也会删除字符串类型键的过期时间,这在开发时容易被忽视。

    • move、dump+restore、migrate是Redis发展过程中三种迁移键的方式,其中move命令基本废弃,migrate命令用原子性的方式实现了dump+restore,并且支持批量操作,是Redis Cluster实现水平扩容的重要工具。

    • scan命令可以解决keys命令可能带来的阻塞问题,同时Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset。

    相关文章

      网友评论

          本文标题:二、Redis 键 管理

          本文链接:https://www.haomeiwen.com/subject/uhiwrktx.html