redis内存优化的探索和实践

源代码 2024-9-23 05:43:04 102 0 来自 中国
1,redis怎样管理内存布局
redis内存模型:


1.png 【used_memory】:Redis内存占用中最重要的部分,Redis分配器分配的内存总量(单元是KB)(在编译时指定编译器,默认是jemalloc),重要包罗自身内存(字典、元数据)、对象内存、缓存,lua内存。
【自身内存】:自身维护的一些数据字典及元数据,一样平常占用内存很低。
【对象内存】:全部对象都是Key-Value型,Key对象都是字符串,Value对象则包罗5种类(String,List,Hash,Set,Zset),5.0还支持stream范例。
【缓存】:客户端缓冲区(普通 + 主从复制 + pubsub)以及aof缓冲区。
【Lua内存】:重要是存储加载的 Lua 脚本,内存利用量和加载的 Lua 脚本数目有关。
【used\_memory\_rss】:Redis 主历程占据操纵体系的内存(单元是KB),是从操纵体系角度得到的值,如top、ps等下令。
ps 和 top 下令常用来检察Linux体系历程干系信息。
ps下令:可以检察历程的瞬间信息。
top下令:可以一连的监视历程的信息。


【内存碎片】:如果对数据的更改频仍,可能导致redis开释的空间在物理内存中并没有开释,但redis又无法有效利用,这就形成了内存碎片。
【运行内存】:运行时斲丧的内存,一样平常占用内存较低,在10M内。
【子历程内存】:重要是在恒久化的时间,aof rewrite大概rdb产生的子历程斲丧的内存,一样平常也是比力小。


2,对象内存存储 Redis 全部的key-value型数据范例,key对象都是 string 范例,value对象重要有五种数据范例String、List、Hash、Set、Zset,差别范例的对象通过对应的编码各种封装,对外界说为RedisObject布局体,RedisObject都是由字典(Dict)生存的,而字典底层是通过哈希表来实现的。通过哈希表中的节点生存字典中的键值对,布局如下:


此图泉源于《redis的计划与实现》
为了到达极大的进步 Redis 的机动性和服从,Redis 根据差别的利用场景来对一个对象设置差别的编码,从而优化某一场景下的服从。 
【int】:(整数且数字长度小于20,直接记录在ptr*内里)
【embstr】: (一连分配的内存(字符串长度小于即是44字节的字符串))
【raw】: 动态字符串(大于44个字节的字符串,同时字符长度小于 512M(512M是字符串的巨细限定))
list (列表)
【ziplist】:(元素个数小于hash-max-ziplist-entries设置(默认512个),同时全部值都小于hash-max-ziplist-value设置(默认64个字节))
【linkedlist】:(当列表范例无法满意ziplist的条件时,Redis会利用linkedlist作为列表的内部实现)
【quicklist】:(Redis 3.2 版本引入了 quicklist 作为 list 的底层实现,不再利用 linkedlist 和 ziplist 实现)
list (列表)
【ziplist】:(元素个数小于hash-max-ziplist-entries设置(默认512个),同时全部值都小于hash-max-ziplist-value设置(默认64个字节))
【linkedlist】:(当列表范例无法满意ziplist的条件时,Redis会利用linkedlist作为列表的内部实现)
【quicklist】:(Redis 3.2 版本引入了 quicklist 作为 list 的底层实现,不再利用 linkedlist 和 ziplist 实现)
list (列表)
【ziplist】:(元素个数小于hash-max-ziplist-entries设置(默认512个),同时全部值都小于hash-max-ziplist-value设置(默认64个字节))
【linkedlist】:(当列表范例无法满意ziplist的条件时,Redis会利用linkedlist作为列表的内部实现)
【quicklist】:(Redis 3.2 版本引入了 quicklist 作为 list 的底层实现,不再利用 linkedlist 和 ziplist 实现)
zset(有序聚集)
【ziplist】:(元素个数小于zset-max-ziplist-entries设置(默认128个)同时每个元素的value小于zset-max-ziplist-value设置(默认64个字节))
【skiplist】:(当ziplist条件不满意时,有序聚集会利用skiplist作为内部实现)
2.2  缓冲内存

2.2 1 客户端缓存
客户端缓冲指的是全部接入 Redis 服务的 TCP 毗连的输入输出缓冲。有普通客户端缓冲、主从复制缓冲、订阅缓冲,这些都由对应的参数缓冲控制巨细(输入缓冲无参数控制,最大空间为1G),若到达设定的最大值,客户端将断开。
【client-output-buffer-limit】: 限定客户端输出缓存的巨细,背面接客户端种类(normal、slave、pubsub)及限定巨细,默认是0,不做限定,如果做了限定,到达阈值之后,会断开链接,开释内存。
【repl-backlog-size】:默认是1M,backlog是一个主从复制的缓冲区,是一个环形buffer,假设到达设置的阈值,不存在溢出的题目,会循环覆盖,比如slave停止过程中同步数据没有被覆盖,实行增量同步就可以。backlog设置的越大,slave可以失连的时间就越长,受参数maxmemory限定,正常不要设置太大。


AOF 缓冲
当我们开启了 AOF 的时间,先将客户端传来的下令存放在AOF缓冲区,再去根据详细的计谋(always、everysec、no)去写入磁盘中的 AOF 文件中,同时记录刷盘时间。
AOF 缓冲没法限定,也不必要限定,由于主线程每次进行 AOF会对比前次刷盘乐成的时间;如果凌驾2s,则主线程壅闭直到fsync同步完成,主线程被壅闭的时间,aof\_delayed\_fsync状态变量记录会增长。因此 AOF 缓存只会存几秒时间的数据,斲丧内存比力小


2.3 内存碎片
步调出现内存碎片是个很常见的题目,Redis的默认分配器是jemalloc ,它的计谋是按照一系列固定的巨细分别内存空间,比方 8 字节、16 字节、32 字节、…, 4KB、8KB 等。当步调申请的内存最靠近某个固定值时,jemalloc 会给它分配比它大一点的固定巨细的空间,以是会产生一些碎片,别的在删除数据的时间,开释的内存不会立即返回给操纵体系,但redis自己又无法有效利用,就形成碎片。
内存碎片不会被统计在used\_memory中,内存碎片比率在redis info内里记录了一个动态值mem\_fragmentation\_ratio,该值是used\_memory\_rss / used\_memory的比值,mem\_fragmentation\_ratio越靠近1,碎片率越低,正常值在1~1.5内,凌驾了阐明碎片很多。
备注:JeMalloc 是一款内存分配器,与别的内存分配器相比,它最大的上风在于多线程情况下的高性能以及内存碎片的镌汰。


2.4子历程内存
前面提到子历程重要是为了生成 RDB 和 AOF rewrite产生的子历程,也会占用肯定的内存,但是在这个过程中写操纵不频仍的情况下内存占用较少,写操纵很频仍会导致占用内存较多。


3,redis优化
内存优化的对象重要是对象内存、客户端缓冲、内存碎片、子历程内存等几个方面,由于这几个内存斲丧比力大大概有的时间不稳固,我们优化内存的方向分为如:
(1)镌汰内存利用
(2)进步性能
(3)镌汰内存异常发生。
3.1对象内存优化
对象内存优化可以低落内存利用率,进步性能,优化点重要针对差别对象差别编码的选择上做优化
(1)起首是字符串范例的3种编码,int编码除了自身object无需分配内存,object 的指针不必要指向其他内存空间,无论是从性能还是内存利用都是最优的,embstr是会分配一块一连的内存空间,但是假设这个value有任何变革,那么value对象会酿成raw编码,而且是不可逆的。
(2)ziplist 存储 list 时每个元素会作为一个 entry; 存储 hash 时 key 和 value 会作为相邻的两个 entry; 存储 zset 时 member 和 score 会作为相邻的两个entry,当不满意上述条件时,ziplist 会升级为 linkedlist, hashtable 或 skiplist 编码。
(3)在任何情况下大内存的编码都不会降级为 ziplist。
(4)linkedlist 、hashtable 便于进行增编削操纵但是内存占用较大。
(5)ziplist 内存占用较少,但是由于每次修改都可能触发 realloc 和 memcopy, 可能导致连锁更新(数据可能必要挪动)。因此修改操纵的服从较低,在 ziplist 的条目很多时这个题目更加突出。
(6)由于现在大部分redis运行的版本都是在3.2以上,以是 List 范例的编码都是quicklist,它是 ziplist 构成的双向链表linkedlist ,它的每个节点都是一个ziplist,考虑了综合均衡空间碎片和读写性能两个维度以是利用了个新编码quicklist,quicklist有个比力告急的参数list-max-ziplist-size,当它取正数的时间,正数体现限定每个节点ziplist中的entry数目,如果是负数则只能为-1~-5,限定ziplist巨细,从-1~-5的限定分别为4kb、8kb、16kb、32kb、64kb,默认是-2,也就是限定不凌驾8kb。
(7)【rehash】: redis存储底层很多是hashtable,客户端可以根据key计算的hash值找到对应的对象,但是当数据量越来越大的时间,可能就会存在多个key计算的hash值雷同,这个时间这些雷同的hash值就会以链表的情势存放,如果这个链表过大,那么遍历的时间性能就会降落,以是Redis界说了一个阈值(负载因子 loader_factor = 哈希表中键值对数目 / 哈希表长度),会触发渐进式的rehash,过程是新建一个更大的新hashtable,然后把数据渐渐移动到新hashtable中。
(8)【bigkey】:bigkey一样平常指的是value的值占用内存空间很大,但是这个巨细着实没有一个固定的标准,我们自己界说凌驾10M就可以称之为bigkey。


优化发起:
key只管控制在44个字节数内,走embstr编码,embstr比raw编码镌汰一次内存分配,同时由于是一连内存存储,性能会更好。
多个string范例可以归并成小段hash范例去维护,小的hash范例走ziplist是有很好的压缩效果,节省内存。
非string的范例的value对象的元素个数只管不要太多,克制产生大key。
在value的元素较多且频仍变更,不要利用ziplist编码,由于ziplist是一连的内存分配,对频仍更新的对象并不友爱,性能斲丧反而大。
hash范例对象包罗的元素不要太多,克制在rehash的时间斲丧过多内存。
只管不要修改ziplist限定的参数值,由于ziplist编码固然可以对内存有很好的压缩,但是如果元素太多利用ziplist的话,性能可能会有所降落。


3.2  客户端缓冲优化
客户端缓存是很多内存异常增长的罪魁罪魁,大部分都是普通客户端输出缓冲区异常增长导致,我们先相识下实行下令的过程,客户端发送一个大概通过piplie发送一组哀求下令给服务端,然后期待服务端的相应,一样平常客户端利用壅闭模式来期待服务端相应,数据在被客户端读取前,数据是存放在客户端缓存区,下令实行的浅易流程图如下:




异常增长缘故原由可能如下几种:
客户端访问大key 导致客户端输出缓存异常增长。
客户端利用monitor下令访问Redis,monitor下令会把全部访问redis的下令一连存放到输出缓冲区,导致输出缓冲区异常增长。
客户端为了加快访问服从,利用pipline封装了大量下令,导致返回的效果集异常大(pipline的特性是等全部下令全部实行完才返回,返回前都是暂存在输出缓存区)。
从节点应用数据较慢,导致输出主从复制输出缓存有很多数据积存,末了导致缓冲区异常增长。
异常体现
在Redis的info下令返回的效果内里,client部分client\_recent\_max\_output\_buffer的值很大。
在实行client list下令返回的效果集内里,omem不为0且很大,omem代表该客户端的输出代表缓存利用的字节数。
在集群中,可能少部分used_memory在监控体现存在异常增长,由于不管是monitor大概pipeline都是针对单个实例的下发的下令。


优化发起
应用不要计划大key,大key只管拆分。
服务端的普通客户端输出缓存区通过参数设置,由于内存告警的阈值大部分是利用率80%开始,实际发起参数可以设置为实例内存的5%~15%左右,最好不要凌驾20%,克制OOM。
非特别情况下克制利用monitor下令大概rename该下令。
在利用pipline的时间,pipeline不能封装过多的下令,特别是一些返回效果集较多的下令更应该少封装。
主从复制输出缓冲区巨细设置参考: 缓冲区巨细=(主库写入下令速率 * 操纵巨细 - 主从库间网络传输下令速率 * 操纵巨细)* 2。
注:Redis MONITOR 是一个调试下令,返回服务器处理处罚的每一个下令,它能资助我们相识在数据库上发生了什么操纵,可以通过 redis-cli 和 telnet 下令利用。 当利用 Redis 作为数据库和分布式缓存体系时,可以或许检察服务器处理处罚的全部哀求对于发现应用步调中的错误非常有效。


3.3  碎片优化
碎片优化可以低落内存利用率,进步访问服从,在4.0以下版本,我们只能利用重启规复,重启加载rdb大概重启通过高可用主从切换实现数据的重新加载可以镌汰碎片,在4.0以上版本,Redis提供了自动和手动的碎片整理功能,原理大致是把数据拷贝到新的内存空间,然后把老的空间开释掉,这个是有肯定的性能斲丧的。
【a. redis手动整理碎片】:实行memory purge下令即可。
【b.redis自动整理碎片】:通过如下几个参数控制
【activedefrag yes 】:启用自动碎片整理开关
【active-defrag-ignore-bytes 100mb】:内存碎片空间到达多少才开启碎片整理
【active-defrag-threshold-lower 10】:碎片率到达百分之多少才开启碎片整理
【active-defrag-threshold-upper 100 】:内存碎片率凌驾多少,则尽最大积极整理(占用最大资源去做碎片整理)
【active-defrag-cycle-min 25 】:内存自动整理占用资源最小百分比
【active-defrag-cycle-max 75】:内存自动整理占用资源最大百分比


3.4子历程内存优化
前面谈到 AOF rewrite和 RDB 生成动作会产生子历程,正常在两个动作实行的过程中,Redis 写操纵没有那么频仍的情况下fork出来的子历程是不会斲丧很多内存的,这个重要是由于 Redis 子历程利用了 Linux 的 copy on write 机制,简称COW。
COW的核心是在fork出子历程后,与父历程共享内存空间,只有在父历程发生写操纵修改内存数据时,才会真正去分配内存空间,并复制内存数据。
但是有一点必要留意,不要开启操纵体系的大页THP(Transparent Huge Pages),开启 THP 机制后,原来页的巨细由4KB变为 2MB了。它固然可以加快 fork 完成的速率( 由于要拷贝的页的数目镌汰 ),但是会导致 copy-on-write 复制内存页的单元从 4KB 增大为 2MB,如果父历程有大量写下令,会加重内存拷贝量,从而造成过度内存斲丧。


4,内存优化案例
线上业务 Redis 集群出现内存告警,内存利用率增长很快到达100%,值班职员先辈行了告急扩容,同时反馈至业务群是否有大量新数据写入,业务反馈并无大量新数据写入,且同时扩容后的内存还在涨,很快又要触发告警了,业务 DBA 去查监控看看详细缘故原由。


起首我们看used_memory增长只是集群的少数几个实例,同时内存异常的实例的key的数目并没有异常增长,阐明没有写入大批量数据导致。


5.png 我们再往下分析,可能是客户端的内存占用异常比力大,检察实例 info 内里的客户端干系指标,观察发现output\_list的增长曲线和used\_memory同等,可以判定是客户端的输出缓冲异常导致。


接下来我们再去通过client list检察是什么客户端导致output增长,客户端在实行什么下令,同时去分析是否访问大key。
实行 client list |grep -i  omem=0  发现如下:
id=12593807 addr=192.168.101.1:52086 fd=10767 name=  age=15301 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0  qbuf-free=32768  obl=16173  oll=341101  omem=5259227504  events=rw  cmd=get
阐明下干系的几个重点的字段的寄义:
【id】:就是客户端的唯一标识,经常用于我们kill客户端用到id;
【addr】:客户端信息;
【obl】:固定缓冲区巨细(字节),默认是16K;
【oll】:动态缓冲区巨细(对象个数),客户端如果每条下令的相应效果凌驾16k大概固定缓冲区写满了会写动态缓冲区;
【omem】: 指缓冲区的总字节数;
【cmd】: 近来一次的操纵下令。
可以看到缓冲区内存占用很大,近来的操纵下令也是get,以是我们先看看是否大key导致(我们是直接分析RDB发现并没有大key),但是发现并没有大key,而且get对应的肯定是string范例,string范例的value最大是512M,以是单个key也不太可能产生这么大的缓存,以是断定是客户端缓存了多个key。
这个时间为了尽快规复,和业务沟通暂时kill该毗连,内存开释,然后为了克制防止背面还产生异常,和业务方沟通设置普通客户端缓存限定,由于最大内存是25G,我们把缓存设置了2G-4G, 动态设置参数如下:
config set client-output-buffer-limit normal 4096mb 2048mb 120
由于参数限定也只是针对单个client的输出缓冲这么大,以是还必要查抄客户端利用利用 pipline 这种管道下令大概雷同实现了封装大批量下令导致效果同一返回之前被壅闭,背面确定确实会有这个操纵,业务层就必要去渐渐优化,不然我们限定了输出缓冲,到达了上限,会话会被kill, 以是业务不改的话还是会有抛错。
业务方反馈用的是 C++ 语言 brpc 自带的 Redis客户端,第一次直接搜刮没有pipline的关键字,但是征象又指向利用的管道,以是继续细致看了下代码,发现其内部是实现了pipline雷同的功能,也是会对多个下令进行封装去哀求redis,然后同一返回效果;


总结:pipline 在 Redis 客户端中利用的挺多的,由于确实可以提供访问服从,但是利用不妥反而会影响访问,应该控制好访问,生产情况也只管加这些内存限定,克制部分客户端的异常访问影响全局利用。


4.2从节点内存异常增长案例
线上 Redis 集群出现内存利用率凌驾 95% 的劫难告警,但是该集群是有190个节点的集群触发异常内存告警的只有3个节点。以是检察集群对应信息以及监控指标发现如下有效信息:
1,3个从节点对应的主节点内存没有变革,从节点的内存是渐渐增长的。
2,发现集群团体ops比力低,阐明业务变革并不大,没有发现有效下令突增。
3,主从节点的最大内存不同等,主节点是6G,从节点是5G,这个是导致劫难告警的告急缘故原由。
4,在出题现在,主节点比从节点的内存大概多出1.3G,背面从节点used_memory渐渐增长到凌驾主节点内存,但是rss内存是末了保持了一样。
5,主从复制出现耽误也内存增长的谁人时间段。


7.png

8.png

处理处罚过程:
起首想到的应该是保持主从节点最大内存同等,但是由于主机内存利用率比力高暂时没法扩容,由于想到的是从节点可能什么缘故原由壅闭,以是和业务方沟通是重启下2从节点缓解下,重启后从节点内存开释,降到发生题现在的水平,如上图,背面主机空出了内存资源,以是优先把内存调解同等。
内存调解好了一周后,这3个从节点内存又告警了,由于现在主从内存是同等的,以是触发的是严峻告警(>85%),检察监控发现情况是和之前一样,推测这个是某些操纵触发的,以是还是决定问问业务方这 两个时间段都有哪些操纵,业务反馈这段时间就是在写业务,那2个时间段都是在写入,也看了写redis的那段代码,用了一个比力少见的下令append,append是对string范例的value进行追加。
这里就得提下string范例在 Redis 内里是怎么分配内存的:string范例都是都是sds存储,当前分配的sds内存空间不敷存储且小于1M时间,Redis会重新分配一个2倍之前内存巨细的内存空间。
根据上面到知识点,以是可以大致可以剖析上述一系列的题目,大概是其时做 append 操纵,从节点必要分配空间从而发生内存膨胀,而主节点不必要分配空间,由于内存重新分配计划malloc和free操纵,以是其时有lag也是正常的。
Redis的主从自己是一个逻辑复制,加载 RDB 的过程着实也是拿到kv不断的写入到从节点,以是主从到内存巨细也经常存在不雷同的情况,特别是这种values巨细经常改变的场景,主从存储的kv所用的空间很多可能是不一样的。
为了证实这一推测,我们可以通过获取一个key(value巨细要比力大)在主从节点占用空间的巨细,由于是4.0以上版本,以是我们可以利用memory USAGE 去获取巨细,看看差别有多少,我们随机找了几个稍微大点的key去检察,发现在有些key从库占用空间是主库的近2倍,有的差不多,有的也是1倍多,rdb剖析出来的这个key空间更小,阐明从节点重启后加载rdb进行存放是最小的,然后由于某段时间大批量key操纵,导致从节点的大批量的key分配的空间不敷,必要扩容1倍空间,导致内存出现增长。
到这就分析的着实差不多了,由于append的特性,为了克制内存再次出现内存告警,决定把该集群的内存进行扩容,控制内存利用率在70%以下(克制可能发生的大量key利用内存翻倍的情况)。
末了另有1个题目:上面的used\_memory为什么会比memory\_rss的值还大呢?(swap是关闭的)。
这是由于jemalloc内存分配一开始着实分配的是捏造内存,只有往分配的page页内里写数据的时间才会真正分配内存,memory\_rss是实际内存占用,used\_memory着实是一个计数器,在 Redis做内存的malloc/free的时间,对这个used_memory做加减法。


总结:
在知晓 Redis内存分配原理的情况下,数据库的内存异常题目进行分析会比力快速定位,别的可能某个题目看起来和业务没什么关联,但是我们还是应该多和业务方沟通获取一些线索排查题目,末了主从内存肯定按照规范保持同等。


5,总结
Redis在数据存储、缓存都是做了很奇妙的计划和优化,我们在相识了它的内部布局、存储方式之后,我们可以提前在key的计划上做优化。我们在碰到内存异常大概性能优化的时间,可以不再范围于外貌的一些分析如:资源斲丧、下令的复杂度、key的巨细,还可以连合根据Redis的一些内部运行机制和内存管理方式去深入发现是否另有可能哪些方面导致异常大概性能降落。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 20:26, Processed in 0.123836 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表