简介
Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。所以,监控 Redis 的内存消耗并了解 Redis 内存模型对高效并长期稳定使用 Redis 至关重要。
Redis 内存统计
redis提供了下面命令来查询当前redis内存的使用情况。
info memory
执行结果如下:
具体指标如下所示:
属性 |
说明 |
used_memory |
Redis 分配器分配的内存总量,也就是内部存储的所有数据内存占用量 |
used_memory_human |
以可读的格式返回 used_memory |
used_memory_rss |
从操作系统的角度显示 Redis 进程占用的物理内存总量 |
used_memory_rss_human |
used_memory_rss 的用户宜读格式的显示 |
used_memory_peak |
内存使用的最大值,表示 used_memory 的峰值 |
used_memory_peak_human |
以可读的格式返回 used_memory_peak的值 |
used_memory_lua |
Lua 引擎所消耗的内存大小。 |
mem_fragmentation_ratio |
used_memory_rss / used_memory 的比值,可以代表内存碎片率 |
maxmemory |
Redis 能够使用的最大内存上限,0表示没有限制,以字节为单位。 |
maxmemory_policy |
Redis 使用的内存回收策略,可以是 noeviction、allkeys-lru、volatile-lru、allkeys-random、volatile-random 或者 volatile-ttl。默认是noeviction,也就是不会回收。 |
mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大,mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。
一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说)
Redis内存划分
Redis作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前面的叙述可以知道,除了数据以外,Redis的其他部分也会占用内存。
Redis的内存占用主要可以划分为以下几个部分:
对象内存
为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。
Redis使用键值对存储数据,其中的值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。这5种类型是Redis对外提供的,实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现;此外,Redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等;这篇文章后面将重点介绍Redis中数据存储的细节。
进程本身运行内存
Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。
补充说明:除了主进程外,Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程。当然,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。
缓冲内存
缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。
输入缓冲无法控制,最大空间为 1G,如果超过将断开连接。而且输入缓冲区不受 maxmemory 控制,假设一个 Redis 实例设置了 maxmemory 为 4G,已经存储了 2G 数据,但是如果此时输入缓冲区使用了 3G,就已经超出了 maxmemory 限制,可能导致数据丢失、键值淘汰或者 OOM。
内存碎片
内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。
内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。后面将要说到的jemalloc便在控制内存碎片方面做的很好。
如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。
内存回收策略
Redis的内存回收机制主要体现在两个方面上:
对过期数据的处理 当内存使用情况达到maxmemory时触发内存回收策略
过期键的删除
惰性删除:什么时候执行呢?就是在客户端读取带有超时属性的键时,如果已经超过键值设置的过期时间,则删除并返回空。这样做的目的主要是为了节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是,如果单独使用这种方式存在一个问题,如果当前的键值永远不再被访问呢?就不删除了吗?那肯定不行,这就会造成内存泄漏的问题。那Redis是怎么解决的呢?Redis提供了一个定时任务的删除机制来做补充。
定时任务删除
Redis内部维护了一个定时任务,默认是每秒运行十次。删除的逻辑如下图:
内存溢出控制策略
当Redis使用的内存达到上限maxmemory后,就会根据maxmemory-policy设置的相关策略进行对应的操作,Redis支持一下6种策略:
策略 |
备注 |
noeviction |
默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory |
volatile-lru |
只对设置有超时属性的Key根据LRU算法执行删除操作,如果没有可删除的Key,则回退到noeviction策略 |
allkeys-lru |
针对所有的key,根据LRU算法执行删除操作直到回收到足够内存空间 |
allkeys-random |
随机删除所有的键,直到腾出足够空间 |
volatile-random |
针对带有过期属性的键,进行删除操作,直到腾出足够空间 |
volatile-ttl |
根据键值对象的ttl属性,删除最近将要过期数据。如果没有,则回退到noviction策略 |
参考:
https://juejin.im/entry/5b93ce4d5188255c48349316
https://juejin.im/post/5da5dc5851882520233f65c5
https://www.cnblogs.com/kismetv/p/8654978.html