《Redis开发与运维》读书笔记(一)

发布于 2019-12-30  2.3k 次阅读


全局命令

  • 查看所有的键(遍历所有key,慢查询之一)

    keys *

  • 键总数(redis内部维护的计数器,并不会扫描全库)

    dbsize

  • 检查键是否存在

    exists key

  • 删除键

    del key1 key2...

  • 过期设置

    expire key seconds

  • 获取数据类型

    type key

数据结构与内部编码

  • string:

    • int
    • embstr
    • raw
  • hash:

    • hashtable
    • ziplist
  • list:

    • quicklist
  • set:

    • hashtable
    • intset
  • zset:

    • skiplist
    • ziplist

 

单线程与I/O多路复用模型

客户端到服务端模型

  • 发送命令

  • 执行命令

    • 进入命令队列
  • 返回结果

为什么快

  • 纯内存访问(主要)
  • 非阻塞io:依赖linux内核中的多路复用IO接口epoll,自编写一套处理模型(不依赖于其他事件模型)将epoll中的连接、读写、关闭都转换为事件
  • 单线程避免了线程切换以及线程竞争的开销,单线程也简化了数据结构与算法的实现,坏处是对于慢查询非常敏感,一个慢查询将阻塞之后所有的命令

数据结构的概览

字符串

命令

命令 时间复杂度
set key value O(1)
get key O(1)
del key [key ...] O(k)
mset key value [key value ...] O(k)
mget key [key ...] O(k)
incr key O(1)
decr key O(1)
decr key O(1)
incrby key increment O(1)
decrby key decrment O(1)
incrbyfloat key incrment O(1)
append key value O(1)
strlen key O(1)
setrange key offset value O(1)
getrange key start end O(n)(n=strlen key)

内部编码

  • int:整形
  • embstr:小于等于44字节
  • raw:大于44字节

使用场景

  • 缓存
  • 计数
  • 共享变量(session etc.)
  • 限流
  • setnx分布式锁

哈希

命令

命令 时间复杂度
hset key field value O(1)
hget key field O(1)
hget key [field ...] O(k)(k=sum(fileds))
hlen key O(1)
hgetall key(遍历key,可能会阻塞,可以用hscan替换) O(1)
hmget key filed [filed ...] O(k)
hmset key filed value [field value] O(k)
hexists key field O(1)
hkeys key O(n)
hvals key O(n)
hsentx key field value O(1)
hincrby key field incrment O(1)
hincrbyfloat key field incrment O(1)
hstrlen key field O(1)

使用场景

  • 对象类型(k/v)数据

列表

命令

  • 添加
命令 时间复杂度
rpush key value [value ...] O(k)
lpush ... O(k)
linsert key before|after pivot value O(index(pivot)||end-index(pivot))
  • 查找
命令 时间复杂度
lrange key start end(可能阻塞) O(start+(end-start))
lindex key index O(index)
llen key(ziplist 超过65534需要遍历,否则和linkedlist一样用变量维护) O(1)
  • 删除
命令 时间复杂度
lpop key O(1)
rpop key O(1)
lrem count value O(n)(列表长度)
ltrim key start end O(n)(裁剪元素总数)
  • 修改
命令 时间复杂度
lset key index value O(n)(索引偏移量)
  • 阻塞操作
命令 时间复杂度
blpop brpop O(1)

内部编码

  • ziplist:

    • 元素个数小于list-max-ziplist-entries配置
    • 列表中每个元素的值都小于list-max-ziplist-value配置
  • linkedlist:

    • 无法满足ziplist条件
  • quicklist(当前):

    • 结合了ziplist和linkedlist

消息队列

  • 消息队列(单次消费,无ack)
  • lpush+lpop=Stack(栈)filo
  • lpush+rpop=Queue(队列)fifo
  • lpsh+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

集合

命令

命令 时间复杂度
sadd key element [element ...] O(k)
srem key element [element ...] O(k)
scard key O(1)
sismember key element O(1)
srandmember [count] O(count)
spop key O(1)
smember key(遍历,可以换为sscan) O(n)(元素总数)
sinter key [key ...] O(m*k)(k是多个集合中元素最少的个数,m是键的个数)
sunion key [key ...] O(k)(k是多个集合的元素个数)
sdiff key [key ...] O(k)(k是多个集合的元素个数)

内部编码

  • intset:

    • 元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
  • hashtable:

    • 无法满足intset

使用场景

  • 取共同好友等交集行为

有序集合

命令

命令 时间复杂度
zadd key element scoer [element score ...] O(k*log(n))(k:添加成员个数、n:当前有序集合个数)
zcard key O(1)
zscore key member O(1)
zrank|zrevrank key member O(log(n))(n:当前有序集合个数)
zrem key member [member ...] O(k*log(n))(k:添加成员个数、n:当前有序集合个数)
zincrby key incrment member O(log(n))(n:当前有序集合个数)
zrange|zrevrange key start end [withscore](返回成员分数) O(k+log(n))(k:要获取成员个数、n:当前有序集合个数)
zrangebyscore|zrevrangebyscore key start end [withscore](返回成员分数) O(k+log(n))(k:要获取成员个数、n:当前有序集合个数)
zcount O(log(n))(n:当前有序集合个数)
zremrangebyrank key start end O(k+log(n))(k:删除成员个数、n:当前有序集合个数)
zremrangebyscore key min max O(k+log(n))(k:删除成员个数、n:当前有序集合个数)
zinterstore dest numkeys key [key ...] O(k*n)+O(m*log(m))(n:成员最小的有序集合成员个数、k:有序集合个数、m:结果集成员个数)
ziunionstore dest numkeys key [key ...] O(n)+O(m*log(m))(n:所有有序集合成员个数、m:结果集成员个数)

内部编码

  • ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用
  • skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降

键管理

  • 重命名:rename key new key

    • 如果在rename之前,键java已经存在,那么它的值也将被覆盖
    • 为了防止被强行rename,Redis提供了renamenx命令
    • 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性
    • 如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之前版本返回结果略有不同
  • 随机返回一个键randomkey

  • 键过期

    • expire key second:键在seconds秒后过期
    • expiret key timestamp:键在秒级时间戳timestamp后过期

     

    • pexpire key milliseconds:键在milliseconds毫秒后过期
    • pexpireat key milliseconds-timestamp:键在毫秒级时间戳timestamp后过期
    • 无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最终使用的都是pexpireat

    注意

    • 如果expire key的键不存在,返回结果为0
    • 如果过期时间为负值,键会立即被删除,犹如使用del命令一样
    • persist key命令可以将键的过期时间清除
    • 对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易在开发中被忽视
    • Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置
    • setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间
  • 迁移键migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key ...]

    • Screenshot_20200101_193120
  • 遍历键

    • 全量遍历键keys pattern

      注意

      • 如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞
    • 渐进式遍历scan cursor [match pattern] [count number]

      注意

      • Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan
      • 如果在scan的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况

重点回顾

  • 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。

 


面向ACG编程