redis

作者: 一斗 | 来源:发表于2019-02-22 15:15 被阅读0次

    一、简介

    使用场景

    • 缓存
    • 排行榜系统
    • 计数器应用
    • 社交网络
    • 消息队列系统

    Redis适合存贮数据规模不太大,热数据。

    单线程也快

    1. 纯内存访问,内存的响应时长约为100纳秒,这是Redis达到每秒万级别访问的基础
    2. 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多时间
    3. 单线程避免了线程切换和竞态产生的消耗

    二、数据类型

    常用数据类型有string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。

    这些数据结构都是Redis对外的数据结构,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,且随Redis版本迭代会有变化。这样设计好处,一是内部编码改进后对外部无影响,二是多种内部编码可以在不同场景下发挥各自的优势。


    对外数据结构.PNG
    内部编码实现.PNG
    object encoding key
    

    返回内部编码实现

    1、字符串

    优先键都是字符串类型,而且其他几种数据结构都是再字符串基础上构建的。

    字符串类型的值可以是字符串(简单的字符串,复杂的字符串如JSON、XML)、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB

    命令

    1、设置值

    set key value
    

    对同一个键多次设置会覆盖旧值

    setnx key value
    

    只有该键不存在才能设置成功。由于Redis单线程,如果有多个客户端同时执行setnx,只有一个能成功,setnx可以作为分布式锁的一种实现方案。
    2、获取值

    get key
    

    不存在返回nil
    3、批量设置值

    mset key value [key value ...]
    

    4、批量获取值

    mget key [key ...]
    

    批量操作可以提高效率


    多次get.PNG
    批量get.PNG

    Redis可以支撑每秒数万的读写操作,是指Redis服务端的处理能力,而网络可能成为性能瓶颈。
    5、计数

    incr key
    

    自增1,返回自增后的值,键不存在当作0自增,返回1

    decr key  // 自减
    incrby key increment  // 自增指定数字
    decrby key increment  // 自减指定数字
    incrbyfloat key increment  // 自增浮点数
    

    内部编码

    • int: 8个字节的长整型
    • embstr: 小于等于39个字节的字符串
    • raw: 大于39个字节的字符串
      Redis会根据当前值的类型和长度决定使用哪种内部编码实现。


      常见字符串命令.PNG

    2、哈希

    Redis中的哈希类型是指键值对里的值也是键值对类型,形如value={{field1, value1}, ...{fielNd, valueN}}

    命令

    1、设置值

    hset key field value
    

    hsetnx类似setnx,但作用域由键变成field

    2、获取值

    hget key value
    

    3、批量设置获取

    hmset key field value [field value ...]
    hmget key field [field ...]
    

    4、删除field

    hdel key field [field ...]
    

    5、计算field个数

    hlen key
    

    6、判断field存在

    hexists key field
    

    7、获取所有field

    hkeys key
    

    8、获取所有值

    hvals key
    

    9、field值自增

    hincrby key field increment
    hincrbyfloat key field increment
    

    内部编码

    • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,更节省内存
    • hashtable(哈希表):当哈希类型无法满足ziplist条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。


      常见哈希命令.PNG

    3、列表

    列表类型是用来存储多个有序的字符串的,一个列表最多存储232-1个元素,可以两端插入和弹出元素,故可充当栈和队列。

    list操作.PNG

    命令

    1、添加元素

    rpush key value [value ...]  // 从右边插入
    lpush key value [value ...]  // 从左边插入
    

    2、获取列表长度

    llen key
    

    3、索引查值

    索引从0开始计算

    lrange key start end  // start至end的索引范围内的值,包括end
    lindex key index  // index处的值
    

    4、删除元素

    rpop key  // 从右边弹出
    lpop key  // 从左边弹出
    

    5、阻塞弹出

    brpop key [key ...] timeout
    blpop key [key ...] timeout
    
    • 列表为空:如果timeout=3,则会等待3秒返回nil,如果timeout=0,会一直阻塞等待下去
    • 列表非空:timeout无效,立即返回
    • 多个键时,会遍历,只要有一个键能弹出元素就返回

    内部编码

    • ziplist(压缩列表):当列表类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为列表的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,更节省内存
    • linkedlist(链表):当列表类型无法满足ziplist条件时,Redis会使用linkedlist作为哈希的内部实现。
    • quicklist:结合了ziplist和linkedlist两者的优势

    使用场景

    • lpush + lpop = 栈
    • lpush + rpop = 队列
    • lpush + brpop = 消息队列


      常见列表命令.PNG

    4、集合

    一个集合最多存储232-1个元素,集合中元素是不重复的,无序的。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。

    命令

    1、添加元素

    sadd key element [element ...]
    

    返回添加成功元素个数

    2、删除元素

    srem key element [element ...]
    

    返回删除成功元素个数

    3、计算元素个数

    scard key
    

    直接内部用于统计数量的变量的值,时间复杂度为0(1)

    4、判断元素是否存在

    sismember key element
    

    存在返回1,反之0

    5、随机返回指定个数元素

    srandmember key [count]
    

    count是可选参数,不写则默认为1

    6、随机弹出一个元素

    spop key
    

    返回弹出的元素,集合不存在返回nil

    7、获取所有元素

    smembers key
    

    元素过多可能阻塞Redis,慎用

    集合间的操作

    1、交集

    sinter key [key ...]
    

    2、并集

    sunion key [key ...]
    

    3、差集

    sdiff key [key ...]
    
    集合间操作.PNG

    集合间的运算比较耗时,所以Redis提供了三个命令(原命令+store)将集合间的交集、并集、差集保存在destination key中。

    sinterstore destination key [key ...]
    sunionstore destination key [key ...]
    sdiffstore destination key [key ...]
    

    内部编码

    • intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合内部编码实现,节省内存
    • hashtable(哈希表):当集合元素无法满足intset条件时,Redis会使用hashtable


      常见集合命令.PNG

    5、有序集合

    有序集合的元素也不能重复,同时它给每个元素设置一个分数(score)作为排序的依据,分数可以有重复。有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能。


    有序集合.PNG

    列表、集合和有序集合三者异同点


    三者异同.PNG

    命令

    1、添加成员

    zadd key score member [score member ...]
    

    返回添加成功元素个数。有序集合相比集合提供了排序,但也产生了代价,zadd的时间复杂度为O(log(n)),sadd为O(1)

    2、计算成员个数

    zcard key
    

    时间复杂度为O(1)

    3、计算某个成员的分数

    zscore key member
    

    返回分数,成员不存在返回nil

    4、计算成员的排名

    zrank key member       // 分数从低到高
    zrevrank key member   // 分数从高到低
    

    排名从0开始计算,成员不存在返回nil

    5、删除成员

    zrem key member [member ...]
    

    返回删除成功元素个数

    6、增加成员的分数

    zincrby key increment member
    

    返回增加后的分数,increment可以为负数

    7、返回指定排名范围的成员

    zrange key start end [withscores]
    zrevrange key start end [withscores]
    

    withscores同时返回成员的分数

    8、返回指定分数范围的成员

    zrangebyscore key min max [withscores] [limit offset count]
    zrevrangebyscore key max min [withscores] [limit offset count]
    

    limit offset count选项可以限制输出的起始位置和个数。同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大。

    9、返回指定分数范围成员个数

    zcount key min max
    

    10、删除指定排名内的升序元素

    zremrangebyrank key start end
    

    11、删除指定分数范围的成员

    zremrangebyscore key min max
    

    内部编码

    • ziplist(压缩列表):当有序集合元素个数小于zset-max-ziplist-entries配置(默认128个),同时所有值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为有序集合的内部实现,ziplist更节省内存
    • skiplist(跳跃表):当ziplist无法满足条件时,Redis会使用skiplist作为内部实现


      常见有序集合命令.PNG

    键管理

    1、查看所有键

    keys *
    

    keys命令会遍历所有键,时间复杂度为O(n),当Redis保存了大量键时,线上环境禁止使用。

    2、键总数

    dbsize
    

    dbsize命令直接获取Redis内置的键总数变量,时间复杂度为O(n)

    3、检查键存在

    exists key
    

    存在返回1,反之0

    4、删除键

    del key [key...]
    

    可一次删除多个键,返回成功删除键个数,删除一个不存在的键返回0

    5、键过期

    expire key seconds   // 键在seconds秒后过期
    expireat key timestamp   // 键在秒级别时间戳后过期
    
    persist key   // 清除过期设置
    

    键要先存在才能设置其过期时间,如果seconds为负,则直接删除键

    ttl key
    

    ttl返回键剩余时间,返回以下情况

    • 大于等于0的整数:键剩余时间秒数
    • -1:键没设置过期时间
    • -2:键不存在

    6、键所对于值的数据结构类型

    type key
    

    可返回string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),键不存在返回none

    7、键重命名

    rename key newkey
    

    如果newkey已经存在,则newkey的值会被覆盖,可以使用renamenx命令,在newkey不存在时才重命名成功。由于重命名会执行del命令删除旧键,如果键对应的值比较大,会阻塞Redis

    8、随机返回一个键

    randomkey
    

    三、持久化

    RDB

    Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:

    save [seconds] [changes]

    意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存,例如

    save 60 100

    会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。可以配置多条save指令,让Redis执行多级的快照保存策略。Redis默认开启RDB快照。

    也可以通过BGSAVE命令手动触发RDB快照保存。

    RDB优点

    • 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
    • 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
    • 使用RDB文件进行数据恢复比使用AOF要快很多。

    RDB缺点

    • 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据。
    • 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力。

    AOF

    采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。AOF默认是关闭的,如要开启,进行如下配置:

    appendonly yes

    AOF提供了三种fsync配置,always/everysec/no,通过配置项[appendfsync]指定:

    • appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
    • appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
    • appendfsync everysec:折中的做法,交由后台线程每秒fsync一次

    随着AOF不断地记录写操作日志,因为所有的操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。

    AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:

    auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb

    上面两行配置的含义是,Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite。同时如果增长的大小没有达到64mb,则不会进行rewrite。

    AOF优点

    • 最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据。
    • AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复。
    • AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。

    AOF缺点

    • AOF文件通常比RDB文件更大
    • 性能消耗比RDB高
    • 数据恢复速度比RDB慢

    建议策略

    Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:

    • AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响
    • AOF + fsync every second是比较好的折中方案,每秒fsync一次
    • AOF + fsync never会提供AOF持久化方案下的最优性能
    • 使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置
    • 每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟

    Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝24GB / 4kB * 8 = 48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。

    可以通过INFO命令返回的latest_fork_usec字段查看上一次fork操作的耗时(微秒)

    相关文章

      网友评论

          本文标题:redis

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