我们知道 Redis 的所有数据都存储在内存中,内存是我们系统中的一个非常珍贵的资源,不能随意浪费,所以如何合理高效地利用 Redis 内存就变得非常重要了。本文从两个方面来阐述 Redis 的内存机制:
- 知道 Redis 的内存主要消耗在什么地方
- 如何管理内存
查看内存
在文章【死磕 Redis】----- info 命令详解介绍了 info memory
命令可以查看 Redis 内存消耗情况,是我们分析 Redis 内存使用情况的好工具。执行命令后如下:
我们重点关注几个指标:
属性名 | 属性说明 |
---|---|
used_memory |
Redis 分配器分配的内存总量,指 Redis 存储的所有数据所占的内存 |
used_memory_human |
以可读的形式返回 user_memory |
used_memory_rss |
Redis 进程占用的物理内存总量 |
used_memory_peak |
used_memory 使用的峰值 |
used_memory_peak_human |
可读格式返回 used_memory_peak |
used_memory_lua |
Lua 引擎消耗的内存大小 |
mem_fragmentation_ratio |
used_memory_rss/used_memory 比值,内存碎片率 |
mem_allocator |
Redis 所使用的内存分配器,默认 jemalloc |
这里我们需要重点关注
1
说明多出来的部分名没有用于数据存储,而是被内存碎片所消耗,相差越大,说明内存碎片率越严重。
mem_fragmentation_ratio < 1一般出现在Redis内存交换(Swap)到硬盘导致(
used_memory > 可用最大内存时,Redis会把旧的和不适用的数据写入到硬盘,这块空间就叫Swap空间),出现这种情况需要格外关注,硬盘速度远远慢于内存,Redis性能就会变得很差,甚至僵死。 在理想情况下
mem_fragmentation_ratio` 只会比 1 稍微大一点点,也就是 used_memory_rss 的值应该只比 used_memory 稍微高一些。
内存消耗划分
Redis 的内存主要包括:对象内存 + 缓冲内存 + 自身内存 + 内存碎片。如下
对象内存
对象内存是 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 的实际使用内存,即 used_memory,但是由于有内存碎片的存在,所以 Redis 实际使用的内存会比 used_memory 要大,在合理情况下一般只会大一点点。
配置内存回收策略
Redis 回收内存大致有两种机制:
- 删除达到过期时间的对象
- 当内存达到 maxmemory 时触发内存溢出控制策略,强制删除选择出来的对象
Redis 删除过期键值对对象一般有两种策略:惰性删除和主动定时任务删除。
惰性删除:这种删除策略,Redis 不会主动去删除已经过期的键值对,而是等待客户端去读取带有超时属性的键时,如果已经超时了则删除该键值对对象,然后返回空。这样有一个好处就是节省了 CPU ,因为 Redis 不需要单独去维护 TTL 链表来处理过期键的删除,但是有一个坏处就是如果过期的键一直都没有被访问,则永远不会被删除了。那么怎么解决呢?Redis 提供了一个定时任务的删除机制来补救。
定时任务删除:Redis 内部维护一个定时任务,默认是每秒运行 10 次,删除逻辑如下图:
内存溢出控制策略
当 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策略。 |
内存溢出控制策略可以使用 config set maxmemory-policy {policy}
语句进行动态配置。
当 Redis 因为内存溢出删除键时,可以通过执行 info stats
命令查看 evicted_keys
指标找出当前 Redis 服务器已剔除的键数量。
参考
- 《Redis 开发与运维》
- 理解Redis的内存
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。