Nosql之Redis优化讲解

Redis持久化

  • Redis是一种高级key-value数据库。它跟memcached 类似,不过数据可以持久化,而且支持的数据类型很丰富。 有字符串, 链表, 集合和有序集合。 支持在服务器端计算集合的并,交和补(difference)等,还支持多种排序功能。 所以Redis也可以被看成是一个数据结构服务器。

  • Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式” );也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式” )。

  • 由于Redis的数据都存放在内存中, 如果没有配置持久化,redis 重启后数据就全丢失了,于是需要开启 redis 的持久化功能,将数据保存到磁盘上,当redis重启后, 可以从磁盘中恢复数据。redis 提供两种方式进行持久化,一种是 RDB 持久化(原理是将 Reids 在内存中的数据库记录定时dump到磁盘上的 RDB 持久化), 另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。

  1. 持久化概述
    Redis是运行在内存中,内存中的数据断电丢失为了能够重用Redis数据,或者防止系统故障,我们需要将Redis中的数据写入到磁盘空间中,即持久化。

  2. 持久化分类
    RDB方式:创建快照的方式获取某一时刻Redis中所有数据的副本。
    AOF方式:将执行的写命令写到文件的末尾,以日志的方式来记录数据的变化。

  3. RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘, 实际操作过程是fork 一个子进程, 先将数据集写入临时文件, 写入成功后, 再替换之前的文件, 用二进制压缩存储。

  4. AOF持久化以日志的形式记录服务器所处理的每一个写、 删除操作, 查询操作不会记录,以文本的方式记录, 可以打开文件看到详细的操作记录。

RDB与AOF的优缺点

RDB优点

  1. 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近 24 小时的数据,同时还要每天归档一次最近 30 天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
  2. 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
  3. 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程, 之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
  4. 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB缺点

  1. 如果想保证数据的高可用性,即最大限度的避免数据丢失,那么 RDB 将不是一个很好的选择。 因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
  2. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较
    大时,可能会导致整个服务器停止服务几百毫秒,甚至是 1 秒钟。

AOF优点

  1. 该机制可以带来更高的数据安全性, 即数据持久性。 Redis 中提供了 3 中同步策略,即每秒同步、 每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步, 我们可以将其视为同步持久化, 即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。 至于无同步,无需多言,我想大家都能正确的理解它。
  2. 由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机现象, 也不会破坏日志文件中已经存在的内容。 然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题, 不用担心, 在Redis下一次启动之前, 我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
  3. 如果日志过大,Redis可以自动启用 rewrite 机制。 即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时 Redis 还会创建一个新的文件用于记录此期间有哪些修改命令被执行。 因此在进行rewrite切换时可以更好的保证数据安全性。
  4. AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF缺点

  1. 对于相同数量的数据集而言, AOF 文件通常要大于 RDB 文件。 RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
  2. 根据同步策略的不同,AOF在运行效率上往往会慢于 RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和 RDB 一样高效。二者选择的标准,就是看系统是愿意牺牲一些性能, 换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能, 待手动运行 save 的时候,再做备份(rdb)。rdb这个就更有些eventually consistent的意思了。

Redis持久化配置

RDB 持久化配置

  • Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改 Redis 服务器dump快照的频率,在打开 6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
  • save 900 1 #在 900 秒(15 分钟)之后,如果至少有 1 个 key 发生变化,则dump内存快照。
  • save 300 10 #在 300 秒(5 分钟)之后,如果至少有 10 个 key 发生变化,则dump内存快照。
  • save 60 10000 #在 60 秒(1 分钟)之后,如果至少有 10000 个 key 发生变化,则dump内存快照。

AOF持久化配置

  • 在Redis的配置文件中存在三种同步方式,它们分别是:
  • appendfsync always #每次有数据修改发生时都会写入AOF 文件。
  • appendfsynceverysec #每秒钟同步一次,该策略为AOF的缺省策略。
  • appendfsync no #从不同步。高效但是数据不会被持久化。

AOF重写

  • AOF持久化存在以下缺点:

  • Redis会不断地将被执行的命令记录到 AOF 文件里面,所以随着Redis不断运行,AOF文件的体积也会不断增长。在极端情况下,体积不断增大的 AOF 文件甚至可能会用完硬盘的所有可用空间。

  • Redis在重启之后需要通过重新执行 AOF 文件记录的所有写命令来还原数据集, 所以如果AOF文件的体积非常大,那么还原操作执行的时间就可能会非常长。为了解AOF文件体积不断增大的问题, 用户可以向Redis发送BGREWRITEAOF命令,这个命令会通过移除 AOF 文件中的冗余命令来重写(rewrite)AOF文件, 使AOF文件的体积变得尽可能地小。 BGREWRITEAOF 的工作原理和 BGSAVE创建快照的工作原理非常相似:Redis 会创建一个子进程, 然后由子进程负责对 AOF 文件进行重写。因为AOF文件重写也需要用到子进程,所以快照持久化因为创建子进程而导致的性能问题和内存占用问题,在AOF持久化中也同样存在。

  • 跟快照持久化可以通过设置 save 选项来自动执行 BGSAVE 一样, AOF 持久化也可以通过设置 auto-aof-rewrite-percentage 选项和 auto-aof-rewrite-min-size 选项来自动执行BGREWRITEAOF。

  • 举个例子,假设用户对Redis设置了配置选项 auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb,并且启动了 AOF 持久化,那么当 AOF 文件的体积大于64MB, 并且 AOF 文件的体积比上一次重写之后的体积大了至少一倍(100%)的时候,Redis将执行 BGREWRITEAOF 命令。 如果 AOF 重写执行得过于频繁的话,用户可以考虑将auto-aof-rewrite-percentage 选项的值设置为 100 以上,这种做法可以让 Redis 在 AOF文件的体积变得更大之后才执行重写操作,不过也会Redis 在启动时还原数据集所需的时间变得更长。

性能管理

  • redis性能管理需要关注的数据指标有内存使用率、内存碎片率、回收key等。这其中有些数据都可以通过进入redis输入info命令进行查看。需要查看某一项的值就后面跟具体参数,下面查看redis使用内存值。
    Nosql之Redis优化讲解

内存碎片率

  • 上述信息中的mem_fragmentation_ratio给出了内存碎片率的数据指标,它是由操系统分配的内存值used_memory_rss除以 redis使用的内存值used_memory得出的。

  • used_memory_rss的rss是Resident Set Size的缩写,表示该进程所占物理内存的大小,是操作系统分配给 Redis 实例的内存大小。除了用户定义的数据和内部开销以外,used_memory_rss指标还包含了内存碎片的开销,内存碎片是由操作系统低效的分配/回收物理内存导致的。操作系统负责分配物理内存给各个应用进程,Redis使用的内存与物理内存的映射是由操作系统上虚拟内存管理分配器完成的。

  • 举个例子来说,Redis需要分配连续内存块来存储1G的数据集,这样的话更有利,但可能物理内存上没有超过 1G 的连续内存块,那操作系统就不得不使用多个不连续的小内存块来分配并存储这 1G 数据,也就导致内存碎片的产生。内存分配器另一个复杂的层面是,它经常会预先分配一些内存块给引用, 这样做会使加快应用程序的运行。跟踪内存碎片率对理解 redis 实例的资源性能是非常重要的。 内存碎片率稍大于 1 是合理的, 这个值表示内存碎片率比较低,也说明redis没有发生内存交换。但如果内存碎片率超过1.5那就说明redis消耗了实际需要物理内存的 150%,其中50%是内碎片率。 若是内存碎片率低于 1 的话,说明Redis内存分配超出了物理内存, 操作系统正在进行内存交换。 内存交换会引起非常明显的响应延迟。上述info memory 中内存碎片率达到 4.24 是因为 redis 里面没有数据, 使用的内存少导致, 所以不足为奇。如果生产环境内存碎片率过高,会导致 redis 性能降低,一般会有三种常见方法解决。

  1. 如果内存碎片率超过 1.5,重启redis服务器可以让额外产生的内存碎片失效并重新作为新内存来使用, 使操作系统恢复高效的内存管理。 额外碎片的产生是由于 redis 释放了内存块, 但内存分配器并没有返回内存给操作系统, 这个内存分配器是在编译时指定的,可以是 libc、 jemalloc或者 tcmalloc。通过比较used_memory_peak, used_memory_rss和used_memory_metrics的数据指标值可以检查额外内存碎片的占用。从名字上可以看出,used_memory_peak 是过去 redis内存使用的峰值,而不是当前使用内存的值。如果used_memory_peak 和 used_memory_rss的值大致上相等,而且二者明显超过了used_memory值, 这说明额外的内存碎片正在产生。 在重启服务器之前, 需要在 redis-cli 工具上输入shutdown save 命令, 意思是强制让redis数据库执行保存操作并关闭redis服务,这样做能保证在执行 redis 关闭时不丢失任何数据。在重启后,redis会从硬盘上加载持久化的文件,以确保数据集持续可用。

  2. 如果内存碎片率低于1,redis 实例可能会把部分数据交换到硬盘上。 内存交换会严重影响 Redis 的性能,所以应该增加可用物理内存或减少实redis内存占用。

  3. 修改内存分配,redis支持glibc’ smalloc、jemalloc11、tcmalloc三种不同的内存分配器,每个分配器在内存分配和碎片上都有不同的实现。 不建议运维人员修改 redis默认内存分配器,因为这需要完全理解这几种内存分配器的差异,也要重新编译 redis。这个方法更多的是让其了解Redis内存分配器所做的工作。

内存使用率

  • 内存使用率是redis服务最关键的一部分。如果一个redis实例的内存使用率超过可用最大内存,那么操作系统开始进行内存与 swap 空间交换,把内存中旧的或不再使用的内容写入硬盘上(硬盘上的这块空间叫 swap 分区)以便腾出新的物理内存给新页或活动页(page)使用。

  • 上面 used_memory 字段数据表示的是由 redis 分配器分配的内存总量,以字节为单位。其中 used_memory_human 上的数据和 used_memory 是一样的值,它以M为单位显示,仅为了方便阅读。

  • used_memory是redis 使用的内存总量,它包含了实际缓存占用的内存和redis自身运行所占用的内存(如元数据、 lua)。它是由redis使用内存分配器分配的内存, 所以这个数据并没有把内存碎片浪费掉的内存给统计进去。redis默认最大使用内存是可用物理内存剩余的所有内存,0代表没有限制。

  • 在硬盘上进行读写操作要比在内存上进行读写操作慢很多。如果redis进程上发生内存交换,那么 redis和依赖redis上数据的应用会受到严重的性能影响。 通过查看used_memory 指标可知道redis正在使用的内存情况,如果used_memory大于可用最大内存,那就说明 redis 实例正在进行内存交换或者已经内存交换完毕。运维人员应该根据这个情况执行相对应的应急措施。如何避免内存交换发生主要有以下三点:

  1. 针对缓存数据大小选择
    如果缓存数据小于4GB,就使用32位的Redis实例。 因为 32 位实例上的指针大小只有64位的一半,它的内存空间占用空间会更少些。 这有一个坏处就是,假设物理内存超过 4GB,那么32位实例能使用的内存仍然会被限制在 4GB 以下。要是实例同时也共享给其他一些应用使用的话,那可能需要更高效的 64 位 Redis 实例,这种情况下切换到32位是不可取的。不管使用哪种方式,Redis 的 dump 文件在 32 位和 64 位之间是互相兼容的,因此倘若有减少占用内存空间的需求,可以尝试先使用 32 位,后面再切换到 64 位上。

  2. 尽可能的使用 Hash 数据结构
    因为Redis在储存小于100个字段的Hash结构上,其存储效率是非常高的。所以在不需要集合(set)操作或 list 的 push/pop 操作的时候, 尽可能的使用 Hash 结构。比如,在一个web应用程序中, 需要存储一个对象表示用户信息, 使用单个 key 表示一个用户,其每个属性存储在 Hash 的字段里, 这样要比给每个属性单独设置一个 key-value 要高效的多。通常情况下倘若有数据使用 string 结构, 用多个 key 存储时, 那么应该转换成单 key 多字段的 Hash 结构。如上述例子中介绍的 Hash 结构应包含, 单个对象的属性或者单个用户各种各样的资料。Hash 结构的操作命令是 HSET(key, fields, value)和 HGET(key, field),使用它可以存储或从 Hash 中取出指定的字段。

  3. 设置 key 的过期时间
    一个减少内存使用率的简单方法就是, 每当存储对象时确保设置 key 的过期时间。 倘若key 在明确的时间周期内使用或者旧 key 不大可能被使用时, 就可以用 Redis 过期时间命令(expire,expireat, pexpire, pexpireat)去设置过期时间,这样Redis会在key过期时自动删除 key。假如你知道每秒钟有多少个新 key-value 被创建, 那可以调整 key 的存活时间,并指定阀值去限制Redis使用的最大内存。

回收key

  • 当内存使用达到设置的最大阀值时, 需要选择一种 key 的回收策略, 可在 redis.conf配置文件中修改“maxmemory-policy”属性值。 默认情况下回收策略是禁止删除, 若是 redis数据集中的 key 都设置了过期时间, 那么“volatile-ttl” 策略是比较好的选择。 但如果key 在达到最大内存限制时没能够迅速过期, 或者根本没有设置过期时间。 那么设置为"allkeys-lru”值比较合适, 它允许 Redis 从整个数据集中挑选最近最少使用的 key 进行删除(LRU 淘汰算法)。 Redis 还提供了一些其他淘汰策略。
参数策略 功能描述
volatile-lru 使用 LRU 算法从已设置过期时间的数据集合中淘汰数据。
volatile-ttl 从已设置过期时间的数据集合中挑选即将过期的数据淘汰。
volatile-random 从已设置过期时间的数据集合中随机挑选数据淘汰。
allkeys-lru 使用 LRU 算法从所有数据集合中淘汰数据。
allkeys-random 从数据集合中任意选择数据淘汰
no-enviction 禁止淘汰数据
  • info stats信息中的evicted_keys字段显示的是因为 maxmemory 限制导致 key 被回收删除的数量。 当redis由于内存压力需要回收一个key时,redis首先考虑的不是回收最旧的数据,而是在最近最少使用的key或即将过期的 key 中随机选择一个 key,从数据集中删除。

Nosql之Redis优化讲解

  • 根据key回收定位性能问题是非常重要的,因为通过回收key,可以保证合理分配redis有限的内存资源。 如果evicted_keys值经常超过 0,那应该会看到客户端命令响应延迟时间增加,因为Redis不但要处理客户端过来的命令请求,还要频繁的回收满足条件的key。需要注意的是,回收 key 对性能的影响远没有内存交换严重,若是在强制内存交换和设置回收策略做一个选择的话,则放弃设置回收 key 是比较合理的,因为把内存数据交换到硬盘上对性能影响非常大。既然频繁的回收key也会导致性能问题,需要减少回收key来提升性能,根据经验如果开启快照功能,maxmemory需要设置成物理内存的 45%, 这几乎不会有引发内存交换的危险。 若是没有开启快照功能,设置系统可用内存的95%是比较合理的。另外一种是分片技术,分片是把数据分割成合适大小,分别存放在不同的redis实例上,每一个实例都包含整个数据集的一部分。通过分片可以把很多服务器联合起来存储数据, 相当于增加总的物理内存,使其在没有内存交换和回收 key 的策略下也能存储更多的 key。 假如有一个非常大的数据集,maxmemory已经设置,实际内存使用也已经超过了推荐设置的阀值,通过数据分片能明显减少key的回收,从而提高Redis的性能。当然redis性能管理远远比上面列出的几种复杂的多,需要多加学习。