# 管理单元

Redis 的管理单元从上到下依次分为:

  • database :数据库
    • 一个 Redis 中默认创建了 16 个数据库,从 0 开始编号。
  • key-value :键值对
    • 每个数据库中可以存储很多键值对,类似于 Python 中的字典。

# database

  • 命令:
    select n      # 切换到第 n 号数据库
    move <key> n  # 将当前数据库的一个 key 移动到第 n 号数据库
    dbsize        # 返回当前数据库的 key 总数
    
    flushdb       # 删除当前数据库的所有 key
    flushall      # 删除所有数据库的所有 key
    

# key

  • 命令:
    keys      <pattern>       # 显示匹配 pattern 的所有 key(例如 keys *)
    exists    <key>...        # 判断 key 是否存在
    del       <key>...        # 删除 key 。需要指定具体的 key 名,不能指定 pattern 。这属于同步操作,会阻塞当前终端
    unlink    <key>...        # 删除 key 。这属于异步操作,不会阻塞当前终端
    
    rename    <key> <newkey>  # 重命名 key(如果 newkey 已存在,则覆盖它)
    renamenx  <key> <newkey>  # 重命名 key(如果 newkey 已存在,则不执行操作)
    
    type      <key>           # 返回 key 的数据类型
    dump      <key>           # 返回 key 的序列化后的 value
    
  • keys 的时间复杂度为 O(n) ,如果在上千万的 key 中进行查询,容易阻塞 Redis 几秒,因此要谨慎使用。
  • 大部分命令执行之后会返回生效的 key 数,返回值为 0 则表示执行失败,返回值为 nil 则表示不存在该 key 。如下:
    127.0.0.1:6379> exists key1 key2
    (integer) 2
    127.0.0.1:6379> get key1
    "hello"
    127.0.0.1:6379> del key1
    (integer) 1
    127.0.0.1:6379> get key1
    (nil)
    

# scan

  • 使用 scan 命令的开销比 keys 小很多,语法:

    # 从游标 cursor 位置开始迭代一定数量的 key ,最多返回的数量为 count(默认为 10 )
    scan  <cursor> [MATCH pattern] [COUNT count] [TYPE type]
    
  • 例:

    127.0.0.1:6379> scan 0 MATCH key*
    1) "1152"                   # 返回本次迭代后的游标位置
    2)  1) "key:000000001311"   # 返回 10 个 key
        2) "key:000000000626"
        3) "key:000000001973"
        4) "key:000000000554"
        5) "key:000000001630"
        6) "key:000000000318"
        7) "key:000000000761"
        8) "key:000000000291"
        9) "key:000000001039"
        10) "key:000000001179"
    
    • 使用本次迭代后的游标位置,作为下一次 scan 迭代的起始游标,就可以继续迭代剩下的 key 。但是多次迭代后返回的 key 可能有重复的,客户端需要自己去重。
    • 如果某次迭代后返回的游标位置为 0 ,则说明已迭代完所有 key 。
    • scan 每次迭代的时间复杂度为 O(1) ,不会阻塞 Redis 。不过迭代完所有 key 的时间复杂度依然为 O(n) 。
  • 基于 scan 命令批量删除 key ,不会阻塞 Redis ,如下:

    redis-cli scan 0 COUNT 1000 | xargs redis-cli del
    
    login_redis='redis-cli -n 0'
    cursor=0
    while [ 1 ]
    do
        echo `date`, dbsize=`$login_redis dbsize`, cursor=$cursor
        list=(`$login_redis scan $cursor COUNT 1000`)
        echo ${list[@]:1} | xargs -n 1 -d ' ' echo del | $login_redis > /dev/null
        cursor=${list[0]}
        if [ cursor == 0 ]
        then
            break
        fi
    done
    
    • 实测发现,主从部署时,删 key 的速度为每秒 1000 个左右,即使每次删 1w 个 key 也是这么慢。

# 过期时间

  • 可以给 key 设置过期时间(time to live),过期之后会被 Redis 自动删除(不一定会立即删除)。

    • 如果 key 的 ttl 为-1 ,则表示不会过期。
  • 命令:

    expire    <key> <seconds>         # 设置 key 在几秒后过期
    pexpire   <key> <milliseconds>    # 设置 key 在几毫秒后过期
    
    expireat  <key> <timestamp>                 # 设置 key 在某个时间戳(单位为秒)之后过期
    pexpireat <key> <milliseconds-timestamp>    # 设置 key 在某个时间戳(单位为毫秒)之后过期
    
    ttl       <key>                   # 返回 key 剩下的过期时间(单位为秒)
    pttl      <key>                   # 返回 key 剩下的过期时间(单位为毫秒)
    
    persist   <key>                   # 取消 key 的过期时间
    

# 其它功能

# 事务

  • Redis 的事务不能保证原子性。
  • 客户端可以将连续执行的多个命令声明为一个事务,如下:
    multi        # 声明一个事务的开始
    set...
    get...
    exec         # 开始执行该事务
    
    • 在执行 exec 之前,可以用 discard 取消执行剩下的命令(但已执行的命令并不能回滚)。

# 分区

:用于将 key 分散保存到多个 Redis 实例。

  • 分区的方式
    • 按 value 值的范围分区。
    • 按 key 的 hash 值的范围分区。
  • 优点
    • 合并多个 Redis 实例,扩展容量。
  • 缺点
    • 如果多个 key 存储在不同 Redis 实例,则不能在一个事务中处理,不能进行 set 交集、差集等运算。

# 发布/订阅

Redis 提供了发布/订阅功能,可用作功能简单的消息队列。