Redis的内存管理和优化

Redis内存管理

1.查询redis内存相关的信息

登录redis的客户端,查看当前redis服务器的内存使用情况:
使用info memory 命令:
《Redis的内存管理和优化》
关键词解释
used_memory:已经使用了的内存大小,包括redis进程内部开销和你的cache的数据所占用的内存,单位byte。
used_memory_human:以可读格式返回的used_memory。
used_memory_rss:从操作系统角度显示的redis占用的物理内存。
used_memory_rss_human:以可读格式返回的used_memory。
used_memory_peak:内存的最大使用值,表示used_memory的峰值。
used_memory_peak_human:
used_memory_memory_lua: Lua引擎所消耗的内存大小
mem_fragmentation_ratio: used_memory_rss/used_memory的比值,表示内存碎片率
used_allocator:Redis所使用的内存分配器

其中,我们需要重点关注的指标有:used_memory_rss和used_memory以及它们的比值mem_fragmentation_ratio
特别是mem_fragmentation_ratio:
当mem_fragmentation_ratio > 1时,说明有很多内存碎片,即被使用了但是没有实际用于存储数据的内存。对于一个存有大量数据的redis服务器,该值起码需要在1.5以下才算正常,否则需要思考一下如何做内存优化。

当mem_fragmentation_ratio < 1时,说明redis使用了虚拟内存,即redis把内存的数据交换到了硬盘上,这种情况要格外关注。因为硬盘的读写速度要远远慢于内存,如果请求的数据大量存储在硬盘上,Redis的性能会变得很差,甚至僵死。

2.redis内存是如何消耗的

如图:
《Redis的内存管理和优化》
1.自身内存:redis自身运行所消耗的内存,一般很小。

2.对象内存:这是redis消耗内存最大的一块,存储着用户所有的数据,每次存入key-value的键值对,暂时可以简单的理解为sizeof(key+value)的内存大小消耗。

3.缓冲内存:缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区。
客户端缓冲:是指客户端连接redis之后,输入或者输出数据的缓冲区,其中输出缓冲可以通过配置参数参数client-output-buffer-limit控制。
复制积压缓冲区:一个可重用的固定大小缓冲区用于实现部分复制功能,根据repl-backlog-size参数控制,默认1MB。对
于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区,因此可以设置较大的缓冲区空间,如100MB,这部分内存投入是有价值的,可以有效避免全量复制。
AOF缓冲区:这部分空间用于在Redis重写期间保存最近的写入命令,AOF缓冲区空间消耗用户无法控制,消耗的内存取决于AOF重写时间和写入命令量,这部分空间占用通常很小。

4.内存碎片:redis的内存分配器为了更好地管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配,如8byte,16byte……4kb,8kb……4mb…….每一个数据块的大小是固定的,如果当前输入的key-value实际占用的大小是6kb,那么redis可能申请一个8kb的内存块来存储这一份数据,那么该8kb内存块对于服务器来讲已经分配出去,不会再让其他的数据占用,但是实际上此次存入数据的时候,有2kb的内存是没有使用到的,所以这个插入操作产生了2kb的内存碎片。当然,这是所有内存分配器无法避免的通病,但是可以优化。

3.如何优化redis的内存

由上面的介绍可以看到,如果不对redis的内存进行优化,那么对一个64G内存的redis服务器,如果mem_fragmentation_ratio的值过大(即内存碎片太多),产生的结果就是实际上只存储了30G不到的数据,内存就已经达到了瓶颈(一般redis配置的最大使用内存需要比服务器实际内存小一些,留给服务器一些余地),这样就极大的浪费了珍贵的内存资源,显然是不合理的,所以如果要优化redis的内存,首先得对内存碎片进行优化。

优化内存碎片:
1.使用更好的内存分配器:
由上面的info memory命令可以看到,used_allocator参数的值是libc,表示当前redis使用的内存分配器是glibc,另一个redis的内存分配器jemalloc。jemalloc针对碎片化问题专门做了优化,一般不会存在过度碎片化的问题,使用jemalloc正常的碎片率(mem_fragmentation_ratio)在1.03左右。

2.不要频繁的对已存在的键做append、setrange等更新操作:
例如一个8byte的字符串正好存储在一个8byte的内存块上,此时内存碎片率为0,如果对改字段进行了append操作,新增了一个8byte长度的字符串,那么redis会为新字符串分配一个预留的内存片,大概是16byte。即新的字符串长度16byte,却用了32byte的内存来存储,碎片率到达了200%。append之后预留一倍的内存来是由redis的预分配机制来确定的,并非一定是翻倍的内存,具体可以查询redis字符串SDS的预分配机制。
所以对redis键的更新,最好是取出键值,删除之后再重新覆盖,这样就避免了redis内部的预分配。

3.大量的过期键:
由于redis对过期键惰性删除的,比如我在redis中设置了一个name-lihua的key-value键值对,然后将它的过期时间设置为了1天,但是当该键过期之后,程序并没有再访问name键。这种情况下即便该键过期了,redis却并没有在内存中将这个key-value删除,只有在客户端再次访问到这个超时的键name时,redis才会执行删除操作并返回空。这种策略是出于节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。
虽然redis内还维护了一个定时任务,默认每10s运行一次,通过自适应的算法来主动根据键的过期比例、使用快慢两种速率模式回收键,但是大量的过期键还是会造成释放的空间无法得到充分利用。

4.数据对齐:
在当前业务条件允许的情况下,使用数字类型或者固定长度的字符串,主动对齐redis的内存块,也可以适当的避免产生内存碎片。

5.重启redis服务:
对于没有高可用的redis服务器绝对不要这么做,对于做过主从高可用的redis集群,适当的重启一个redis服务,也可以有效的改善内存使用。

减少redis使用的内存:

1.使用共享对象池:
在redis内部,维护了一个0-9999的整型共享对象池。当执行如下两行redis插入命令时:
set a 100
set b 100
键a和b所指向的内存是同一片内存,即整型100的内存地址,这样有效的节省了存储大量整型值所消耗的内存。所以,比如在一些业务上,需要判断当前的某种操作类型,可以使用1,2,3,4来存储,然后在程序逻辑中做具体的判断。但是如果业务中有许多个类似的可以使用整型代替字符串的场景,维护一个使用整型共享对象处的业务消耗的内存是会大大减少的。需要注意的是整型共享对象池对list,hash,set,zset内部的整型元素也是适用的。
使用object refcount key 可以查看当前的整型对象的引用个数,依此判断该数据是否使用了共享对象池。因为在特定的LRU淘汰策略下,共享内存池是无法使用的,详情有兴趣可以自行了解。

2.尽可能让hash表以ziplist编码存储:
redis内部拥有许多种编码方式,ziplist为压缩编码格式,如何配置redis,让它能把各种表格和数据格式以ziplist存储这里就不详谈,展开讲篇幅太长,有兴趣可以自己研究一下。但是ziplist压缩的hash表和set表等查询数据的时间复杂度会增加,最坏的查询时间复杂度可能会达到O(n^2),所以使用ziplist编码存储虽然消耗的内存很少,但是对于数据量庞大的表并不推荐使用ziplist编码来存储。

3.控制键的数量:
上面提到了hash表以ziplist存储能大量的节约内存,然而现实中很多人使用redis存储数据时,大量使用get/set这样的API。导致redis中存在着大量的string类型的key-value,如此多的key-value一样会消耗大量的内存,因为string类型是无法以ziplist压缩编码来存储的。所以,合理的设置好数据结构,降低外层键的数量,也可以大量节约内存。
如图:
《Redis的内存管理和优化》
该业务中,在程序层将redis的所有key分类成为了三个业务key相关hash表和一个闲散key的hash表,在redis存储这些数据时,虽然同样存储了一样数量的key-value,hash表压缩之后使用的内存要远远小于直接在redis存储字符串型的key-value,大大提升了内存的使用率。

4.缩短key-value的长度
尽量的使用最短的key和value来完成业务逻辑,也可以适当的减少redis所使用的内存。

    原文作者:一个大太阳
    原文地址: https://blog.csdn.net/yahuuqq1314/article/details/100566688
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞