我们知道 Redis 的所有数据都存储在内存中,内存是我们系统中的一个非常珍贵的资源,不能随意浪费,所以如何合理高效地利用 Redis 内存就变得非常重要了。本文从两个方面来阐述 Redis 的内存机制:

  1. 知道 Redis 的内存主要消耗在什么地方
  2. 如何管理内存

查看内存

在文章【死磕 Redis】----- info 命令详解介绍了 info memory 命令可以查看 Redis 内存消耗情况,是我们分析 Redis 内存使用情况的好工具。执行命令后如下:

redis 本机 Read timed out redis info memory_内存碎片

我们重点关注几个指标:

属性名

属性说明

used_memory

Redis 分配器分配的内存总量,指 Redis 存储的所有数据所占的内存

used_memory_human

以可读的形式返回 user_memory

used_memory_rss

Redis 进程占用的物理内存总量

used_memory_peak

used_memory 使用的峰值

used_memory_peak_human

可读格式返回 usedmemorypeak

used_memory_lua

Lua 引擎消耗的内存大小

mem_fragmentation_ratio

usedmemoryrss/used_memory 比值,内存碎片率

mem_allocator

Redis 所使用的内存分配器,默认 jemalloc

这里我们需要重点关注

mem_fragmentation_ratio>1 说明多出来的部分名没有用于数据存储,而是被内存碎片所消耗,相差越大,说明内存碎片率越严重。mem_fragmentation_ratio<1 一般出现在Redis内存交换(Swap)到硬盘导致( used_memory>可用最大内存时,Redis会把旧的和不适用的数据写入到硬盘,这块空间就叫Swap空间),出现这种情况需要格外关注,硬盘速度远远慢于内存,Redis性能就会变得很差,甚至僵死。在理想情况下 mem_fragmentation_ratio 只会比 1 稍微大一点点,也就是 usedmemoryrss 的值应该只比 used_memory 稍微高一些。

内存消耗划分

Redis 的内存主要包括:对象内存 + 缓冲内存 + 自身内存 + 内存碎片。如下

redis 本机 Read timed out redis info memory_和redis_02

对象内存

对象内存是 Redis 内存中占用最大的一块,存储着所有的用户数据。我们知道 Redis 是一个 key-value 的内存数据库,所有的数据都采用 key-value 型数据类型,每次在创建 key-value 键值对对象的时候都要创建两个对象:key 对象和value 对象。其中 key 对象是字符串,value 对象我们知道有五中数据类型-String、Hash、List、Set、Zset,每种数据类型在使用的时候占用的内存不同。

缓冲内存

主要包括:客户端缓冲、AOF 缓冲区、复制积压缓冲区。

  • 客户端缓冲:普通的客户端连接
  • AOF 缓冲区:Redis 持久化分为两种:RDB 和 AOF,其中 RDB 是内存快照,AOF 是将 Redis 的命令 append 在文件中,不过在写入文件之前会先写入到缓冲区,然后根据不同的持久化策略向磁盘进行同步。在进行 AOF 重写时也有一个AOF 重写缓冲区。一般 AOF 缓冲区都会比较小。
  • 复制积压缓冲区:主要用于主从同步。在进行主从同步时,Redis 会将最新的命令写入到复制积压缓冲区,在进行复制的时候,会校验复制偏移量是否在复制积压缓冲区中,如果是则进行部分复制,否则进行全量复制。它默认情况下是 1MB,我们需要根据实际请求适当调整他的大小,毕竟设置太小的话,可能会使部分复制退化为全量复制。

自身内存

自身内存主要指 AOF/RDB 的时候 Redis 创建子进程内存的消耗,一般这部分的消耗会比较小。

内存碎片

目前可选的分配器有 jemalloc、glibc、tcmalloc,默认 jemalloc。

出现高内存碎片问题的情况:大量的更新操作,比如 append、setrange;大量的过期键删除,释放的空间无法得到有效利用。

解决办法:数据对齐,安全重启(高可用/主从切换)。

内存管理

设置 maxmemory

如果我们不设置 maxmemory ,Redis 则默认使用无限内存,所以为了 Redis 不系统的内存耗尽,我们在使用 Redis 的时候尽量去配置 maxmemory,给 Redis 设置内存使用上限。maxmemory 配置的是 Redis 的实际使用内存,即 usedmemory,但是由于有内存碎片的存在,所以 Redis 实际使用的内存会比 usedmemory 要大,在合理情况下一般只会大一点点。

配置内存回收策略

Redis 回收内存大致有两种机制:

  1. 删除达到过期时间的对象
  2. 当内存达到 maxmemory 时触发内存溢出控制策略,强制删除选择出来的对象

Redis 删除过期键值对对象一般有两种策略:惰性删除和主动定时任务删除。

惰性删除:这种删除策略,Redis 不会主动去删除已经过期的键值对,而是等待客户端去读取带有超时属性的键时,如果已经超时了则删除该键值对对象,然后返回空。这样有一个好处就是节省了 CPU ,因为 Redis 不需要单独去维护 TTL 链表来处理过期键的删除,但是有一个坏处就是如果过期的键一直都没有被访问,则永远不会被删除了。那么怎么解决呢?Redis 提供了一个定时任务的删除机制来补救。

定时任务删除:Redis 内部维护一个定时任务,默认是每秒运行 10 次,删除逻辑如下图:

redis 本机 Read timed out redis info memory_内存碎片_03

内存溢出控制策略

当 Redis 所用内存达到 maxmemory 上限时会触发相应的溢出控制策略。Redis支持6种策略,如下所示:

策略

说明

noeviction

默认策略,不会删除任何数据,拒绝所有写入操作并返 回客户端错误信息(error)OOM command not allowed when used memory,此 时Redis只响应读操作。

volatile-lru

根据LRU算法删除设置了超时属性(expire)的键,直 到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。

allkeys-lru

根据LRU算法删除键,不管数据有没有设置超时属性, 直到腾出足够空间为止。

allkeys-random

随机删除所有键,直到腾出足够空间为止。

volatile-random

随机删除过期键,直到腾出足够空间为止。

volatile-ttl

根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。

内存溢出控制策略可以使用 configsetmaxmemory-policy{policy} 语句进行动态配置。

当 Redis 因为内存溢出删除键时,可以通过执行 info stats 命令查看 evicted_keys 指标找出当前 Redis 服务器已剔除的键数量。

参考

  • 《Redis 开发与运维》
  • 理解Redis的内存