Reids作为一个基于内存的数据库,内存是否能够高效合理的利用至关重要!从价格上来讲,我们从某宝或某东上可以看到内存条的价格要比普通的机械硬盘贵上十几倍,就算是是固态硬盘也要贵上不少,从性能上来说,内存占用过高同样会引起Reids响应变慢,从高可用上来说,内存过大可能会会引起部分数据丢失,故障恢复变慢.......
我们首先要知道Redis都消耗在哪,才能更好的管理优化内存的使用,已达到使用更少的内存存储更多的数据、节省成本的目的,才能真正实现Redis的高性能、高可用。
1 Redis内存消耗Redis内存消耗主要在于其主进程消耗和子进程消耗。而主进程消耗又主要包括自身内存、对象内存、缓冲区内存、内存碎片五个方面。
1.1 自身内存自身内存是指Redis进程自身所占用的内存,这部分内存通常很小,一个空的Redis进程所消耗的内存几乎可以忽略不计。因此我们在分析内存消耗的时候一般不考虑其自身内存。
1.2 对象内存对象内存是Redis内存中占用最大的。我们知道Redis主要有五大对象,字符串、列表、哈希、集合、有序集合。其它如BigMaps、GEO等也都是基于这五大对象实现的。每种对象底层所使用的数据结构都是不同的,因此它们所占用的内存空间大小也是不同的。在使用过程中我们要根据场景选择合适的对象,以达到内存合理利用、避免溢出的目的。
关于Redis的五大对象和其底层所使用的几种数据结构,可以查看我的其他几篇文章。传送门
此外,Redis的每一种对象都是key-value的键值对形式。每个键值对的创建都包含两个对象,key对象和value对象。因此对象内存的消耗可以理解为sizeof(key)+sizeof(value)。而key对象都是字符串类型的,在使用过程中我们不应该忽略key对象所占用的内存,应该避免使用过大的key。
1.3 缓冲区内存Redis主要有三个缓冲区,客户端缓冲区、AOF缓冲区、复制积压缓冲区。
客户端缓冲区是为了解决客户端和服务端请求和处理速度不匹配问题的,它又分为输入和输出缓冲区。
输入缓冲区会先把客户端发送过来的命令暂存起来,Redis 主线程再从输入缓冲区中读取命令,进行处理。当在处理完数据后,会把结果写入到输出缓冲区,再通过输出缓冲区返回给客户端。
AOF缓冲区我们前面的文章已经学习过,是在进行AOF持久化时所用到的缓冲区,AOF缓冲区消耗的内存取决于AOF重写时间和写入命令量, 这部分空间占用通常很小关于AOF缓冲区的介绍我们可以复习一下
复制积压缓冲区则是在集群环境中为了保证主从节点数据同步的所设置的。
主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求。这些写命令就会先保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。主节点上会为每个从节点都维护一个复制缓冲区,来保证主从节点间的数据同步。
1.4 内存碎片内存碎片主要是由于操作系统的内存分配机制和Redis内存分配器的分配策略所决定的。
内存分配器为了更好地管理和重复利用内存, 分配内存策略一般采用固定范围的内存块进行分配。例如当保存5KB对象时内存分配器可能会采用8KB的块存储, 而剩下的3KB空间变为了内存碎片不能再分配给其他对象存储。
关于内存碎片的问题,后面会有单独的一篇文章来详细的进行解释。
1.5 子进程内存消耗除了上面所提到的4种内存消耗之外,还有一种也不能忽视,那就是子进程的内存消耗。
子进程内存消耗主要指执行AOF/RDB重写时Redis创建的子进程内存消耗。我们在学习Redis持久化的时候知道,在执行RDB快照和AOF重写时主进程会fork出一个子进程,由子进程完成快照和重写操作,虽然使用了写时复制的技术,子进程可以不用完全复制父进程的所有物理内存,但是仍然需要复制其内存页表,在此期间如果有写入操作则需要复制出一份副本出来。因此子进程同样会消耗一部分内存,其消耗的内存量取决于RDB和AOF期间的写入命令量。在执行RDB和AOF重写的时候为了防止内存溢出,会预留一部分内存。
2 内存消耗统计关于分析Redis内存消耗的分析,我们可以使用info memory命令来获取内存相关的指标,读懂每个指标有助于我们更加熟练的分析Redis的内存使用情况,如下一份全量的Redis最新版本(6.2.1)的info memory命令的各项指标说明。
127.0.0.1:6379> info memory used_memory:87795176 # Redis分配的内存总量(byte),包含redis进程内部的开销和数据占用的内存 used_memory_human:83.73M # Redis分配的内存总量(mb) used_memory_rss:222318592 # 向操作系统申请的内存大小(byte) used_memory_rss_human:212.02M # 向操作系统申请的内存大小(mb) used_memory_peak:337032496 # redis的内存消耗峰值(byte) used_memory_peak_human:321.42M # redis的内存消耗峰值(mb) used_memory_peak_perc:26.05% # 使用内存与峰值内存的百分比(used_memory / used_memory_peak) *100% used_memory_overhead:2010038 # Redis维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog used_memory_startup:1960232 # Redis启动完成使用的内存 used_memory_dataset:85785138 # 数据占用的内存(used_memory - used_memory_overhead) used_memory_dataset_perc:99.94% # 数据占用的内存大小百分比,(used_memory_dataset / (used_memory - used_memory_startup))*100% allocator_allocated:88139168 # 分配器分配的内存 allocator_active:89964544 # 分配器活跃的内存 allocator_resident:389095424 # 分配器常驻的内存 total_system_memory:67118374912 # 主机内存总量(byte) total_system_memory_human:62.51G # 主机内存总量(mb) used_memory_lua:37888 # Lua引擎存储占用的内存(byte) used_memory_lua_human:37.00K # Lua引擎存储占用的内存(mb) used_memory_scripts:0 # Lua脚本所占用的内存(byte) used_memory_scripts_human:0B # Lua脚本所占用的内存(mb) number_of_cached_scripts:0 # 缓存的Lua脚本数 maxmemory:0 # 配置中设置的最大可使用内存值(byte),默认0,不限制 maxmemory_human:0B # 配置中设置的最大可使用内存值(mb) maxmemory_policy:noeviction # 当达到maxmemory时的淘汰策略 allocator_frag_ratio:1.02 # 分配器的碎片率 allocator_frag_bytes:1825376 # 分配器的碎片大小 allocator_rss_ratio:4.32 # 分配器常驻内存比例 allocator_rss_bytes:299130880 # 分配器的常驻内存大小 rss_overhead_ratio:0.57 # 常驻内存开销比例 rss_overhead_bytes:-166776832 # 常驻内存开销大小 mem_fragmentation_ratio:2.53 # 碎片率(used_memory_rss / used_memory),正常(1,1.6),大于比例说明内存碎片严重 mem_fragmentation_bytes:134564432 # 内存碎片大小 mem_not_counted_for_evict:0 # 被驱逐的内存 mem_replication_backlog:0 # redis复制积压缓冲区内存 mem_clients_slaves:0 # Redis节点客户端消耗内存 mem_clients_normal:49694 # Redis所有常规客户端消耗内存 mem_aof_buffer:0 # AOF使用内存 mem_allocator:jemalloc-5.1.0 # 内存分配器 active_defrag_running:0 # 活动碎片整理是否处于活动状态(0没有,1正在运行) lazyfree_pending_objects:0 # 0-不存在延迟释放的挂起对象 lazyfreed_objects:0 # 已经延迟释放的对象 3 总结最后,我们用一张思维导图来看一看Redis的内存都去哪儿了