redis 简单整理——内存的管理[二十六]
前言
redis 是一个内存型数据库,那么就需要重点关注一下内存了。
正文
理解Redis内存,首先需要掌握Redis内存消耗在哪些方面。有些内存消 耗是必不可少的,而有些可以通过参数调整和合理使用来规避内存浪费。
内存消耗可以分为进程自身消耗和子进程消耗。
首先需要了解Redis自身使用内存的统计数据,可通过执行info memory 命令获取内存相关指标。
需要重点关注的指标有:used_memory_rss和used_memory以及它们的比 值mem_fragmentation_ratio。
当mem_fragmentation_ratio>1时,说明used_memory_rss-used_memory多出 的部分内存并没有用于数据存储,而是被内存碎片所消耗,如果两者相差很大,说明碎片率严重。
当mem_fragmentation_ratio<1时,这种情况一般出现在操作系统把Redis 内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘速 度远远慢于内存,Redis性能会变得很差,甚至僵死。
内存消耗
内存消耗划分
Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片, 其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右, used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
对象内存是Redis内存占用最大的一块,存储着用户所有的数据。Redis 所有的数据都采用key-value数据类型,每次创建键值对时,至少创建两个类 型对象:key对象和value对象。对象内存消耗可以简单理解为sizeof(keys) +sizeof(values)。键对象都是字符串,在使用Redis时很容易忽略键对内存 消耗的影响,应当避免使用过长的键。value对象更复杂些,主要包含5种基本数据类型:字符串、列表、哈希、集合、有序集合。
缓冲内存
缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区。
客户端缓冲指的是所有接入到Redis服务器TCP连接的输入输出缓冲。 输入缓冲无法控制,最大空间为1G,如果超过将断开连接。
普通客户端:
除了复制和订阅的客户端之外的所有连接,Redis的默认 配置是:client-output-buffer-limit normal000,Redis并没有对普通客户端的输 出缓冲区做限制,一般普通客户端的内存消耗可以忽略不计,但是当有大量 慢连接客户端接入时这部分内存消耗就不能忽略了,可以设置maxclients做 限制。特别是当使用大量数据输出的命令且数据无法及时推送给客户端时, 如monitor命令,容易造成Redis服务器内存突然飙升。
从客户端:
主节点会为每个从节点单独建立一条连接用于命令复制, 默认配置是:client-output-buffer-limit slave256mb64mb60。当主从节点之间 网络延迟较高或主节点挂载大量从节点时这部分内存消耗将占用很大一部 分,建议主节点挂载的从节点不要多于2个,主从节点不要部署在较差的网 络环境下,如异地跨机房环境,防止复制客户端连接缓慢造成溢出。
订阅客户端:
订阅客户端:当使用发布订阅功能时,连接客户端使用单独的输出缓 冲区,默认配置为:client-output-buffer-limit pubsub32mb8mb60,当订阅服务 的消息生产快于消费速度时,输出缓冲区会产生积压造成输出缓冲区空间溢 出
复制积压缓冲区:Redis在2.8版本之后提供了一个可重用的固定大小缓 冲区用于实现部分复制功能,根据repl-backlog-size参数控制,默认1MB。对 于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区,因此 可以设置较大的缓冲区空间,如100MB,这部分内存投入是有价值的,可以 有效避免全量复制
AOF缓冲区:这部分空间用于在Redis重写期间保存最近的写入命令。AOF缓冲区空间消耗用户无法控制,消耗的内存取决于 AOF重写时间和写入命令量,这部分空间占用通常很小。
内存碎片
Redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、 tcmalloc。内存分配器为了更好地管理和重复利用内存,分配内存策略一般 采用固定范围的内存块进行分配。例如jemalloc在64位系统中将内存空间划 分为:小、大、巨大三个范围。每个范围内又划分为多个小的内存块单位, 如下所示:
比如当保存5KB对象时jemalloc可能会采用8KB的块存储,而剩下的3KB 空间变为了内存碎片不能再分配给其他对象存储。内存碎片问题虽然是所有 内存服务的通病,但是jemalloc针对碎片化问题专门做了优化,一般不会存 在过度碎片化的问题,正常的碎片率(mem_fragmentation_ratio)在1.03左 右。但是当存储的数据长短差异较大时,以下场景容易出现高内存碎片问题:
-
·频繁做更新操作,例如频繁对已存在的键执行append、setrange等更新 操作。
-
·大量过期键删除,键对象过期删除后,释放的空间无法得到充分利 用,导致碎片率上升。、
解决手法:
-
·数据对齐:在条件允许的情况下尽量做数据对齐,比如数据尽量采用 数字类型或者固定长度字符串等,但是这要视具体的业务而定,有些场景无 法做到
-
·安全重启:重启节点可以做到内存碎片重新整理,因此可以利用高可用架构,如Sentinel或Cluster,将碎片率过高的主节点转换为从节点,进行 安全重启。
内存回收策略
Redis的内存回收机制主要体现在以下两个方面:
- 删除到达过期时间的键对象。
Redis所有的键都可以设置过期属性,内部保存在过期字典中。由于进 程内保存大量的键,维护每个键精准的过期删除机制会导致消耗大量的 CPU,对于单线程的Redis来说成本过高,因此Redis采用惰性删除和定时任 务删除机制实现过期键的内存回收。
a. 惰性删除
惰性删除用于当客户端读取带有超时属性的键时,如果已 经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省 CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是单独用 这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删 除,从而导致内存不能及时释放。正因为如此,Redis还提供另一种定时任 务删除机制作为惰性删除的补充。
b. 定时任务删除
Redis内部维护一个定时任务,默认每秒运行10次(通 过配置hz控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的 过期比例、使用快慢两种速率模式回收键
1)定时任务在每个数据库空间随机检查20个键,当发现过期时删除对 应的键。
2)如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或 运行超时为止,慢模式下超时时间为25毫秒。
3)如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模 式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次。
4)快慢两种模式内部删除逻辑相同,只是执行的超时时间不同。
- 内存使用达到maxmemory上限时触发内存溢出控制策略。
内存溢出控制策略
当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。 具体策略受maxmemory-policy参数控制,Redis支持6种策略,如下所示:
1)noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返 回客户端错误信息(error)OOM command not allowed when used memory,此 时Redis只响应读操作。
2)volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直 到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
3)allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性, 直到腾出足够空间为止。
4)allkeys-random:随机删除所有键,直到腾出足够空间为止。
5)volatile-random:随机删除过期键,直到腾出足够空间为止。
6)volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果 没有,回退到noeviction策略。
内存溢出控制策略可以采用config set maxmemory-policy{policy}动态配 置。Redis支持丰富的内存溢出应对策略,可以根据实际需求灵活定制,比如当设置volatile-lru策略时,
保证具有过期属性的键可以根据LRU剔除,而 未设置超时的键可以永久保留。还可以采用allkeys-lru策略把Redis变为纯缓 存服务器使用。当Redis因为内存溢出删除键时,可以通过执行info stats命令 查看evicted_keys指标找出当前Redis服务器已剔除的键数量。
每次Redis执行命令时如果设置了maxmemory参数,都会尝试执行回收 内存操作。当Redis一直工作在内存溢出(used_memory>maxmemory)的状 态下且设置非noeviction策略时,会频繁地触发回收内存的操作,影响Redis 服务器的性能。回收内存逻辑伪代码如下:
对于需要收缩Redis内存的场景,可以通过调小maxmemory来实现快速 回收。比如对一个实际占用6GB内存的进程设置maxmemory=4GB,之后第一 次执行命令时,如果使用非noeviction策略,它会一次性回收到maxmemory指定的内存量,从而达到快速回收内存的目的。注意,此操作会导致数据丢失 和短暂的阻塞问题,一般在缓存场景下使用。
结
下一节内存的优化。