《Redis碎片整理原理解析与实践.docx》由会员分享,可在线阅读,更多相关《Redis碎片整理原理解析与实践.docx(36页珍藏版)》请在第一文库网上搜索。
1、Redis碎片整理原理解析与实践【摘要】某天下午,有个业务突然找到我说,他的集群在做了一个前缀删除后,耗时涨了3倍,工单如下:做了一个前缀删除,数据量从2.9亿-6千万一、诡异耗时问题RediS平台提供了各种删除数据的自助工单(每天数十个工单) 全部删除 按前缀删除 按过期时间删除 按空闲时间删除 按类型删除 上述的排列组合。某天下午,有个业务突然找到我说,他的集群在做了一个前缀删除后,耗时涨说实话,我当时脑子的瞬间想法是: 业务应该指的是删除期间耗时涨了? 删除后RediS端的耗时不会涨吧?AoF和RDB也没开,感觉业务那边描述有问题。 业务服务有异常?判断标准,当时瞄了一眼主调服务有20个
2、,其中只有3个可用性下降。带着疑问快速过了下相关指标,得到以下结论: 确实是删除数据后,耗时上涨了3倍(avg:ImS-3ms),删除期间整体还好(略诡异,脑子瞬间想到碎片整理,但这个集群的碎片整理CPU参数控制在10%-20%) 主调机器确实有异常,但是之前也有问题(可排除) RedisServerCPU消耗涨了3倍,但是max=24%,依据经验是没问题的,除非业务对耗时极度敏感。综合看,流量无上涨、主调方无变动、删除大量数据、CPU上涨但可容忍(但业务可能不容忍),立刻做了关掉碎片整理的决定,服务瞬间恢复。ps:上述是3分钟内完成的二自认为对碎片率、已经相关实践有所了解,但还是出了个问题,
3、于是准备仔细研究下,同时也分享给大家。二、什么是RediS内存碎片内存分配器为了更好地管理和重复利用内存,分配策略一般采用固定范围的内存块进行分配。Redis默认使用的是jema11oc在64位系统中将内存空间划分为:小、大、巨大三个范围,每个范围内又划分为多个小的内存块单位:类型空间值小881616,32,48,.,128类型空间值32160,192,224,25664320,384,448,512128640,768,896,10244KB4KB,8KB,12KB,.,4072KB也就是如果申请一个20字节,那jema11oc会直接申请32字节的空间,这种分配策略必然会有一个问题,有一些散
4、碎的小空间分配不出去(内存碎片),例如(演示需要)(1)从0开始申请6字节、3字节、3字节、3字节,从图中可以看到,开辟了3*8个字节,但实际使用了(6+3+3+3)字节,空闲了9字节(即为碎片,当然如果有后面有继续申请可能会填满)(2)开始是填满的,但是删除后空出了一些小空间,后续就无法使用了:通常如下情况可能会产生较多的内存碎片:大量删除:主动删除(比如上述工单)、被动删除(过期、逐出等)后产生大量内存碎片,导致碎片率上升。频繁更新:例如对已经存在的字符串类型键值做append、Set扩容(触发sds扩容)操作。例如第一节提到的数据删除后,碎片率猛增三、内存碎片的危害1 .碎片率1这种情况
5、一般出现在操作系统把ReeIiS内存交换(SWaP)到硬盘导致,一定要密切监控这种情况,RediS性能会急剧下降,甚至hang死。正确的做法的是,关闭掉SWaD(RediS在1inUX系统的配置优化),大多数看,真死比hang死要好很多。2 .碎片率1这种情况说明内存并没有被充分利用,碎片率越高,说明浪费越严重(,考虑下1个集群和1万个集群的区别),我们假设有一个日常满的IOOGB(一主一从)集群,不同碎片率的表现如下:际碎占容片用量率内存IOOGB1001.02*1GB5.05*2210GB100GB100*1. 1GB2*1*2际碎占容片用量率内存量220GB100GB*1002*11.2
6、 GB.2*2240GB100100GB1.3 GB*22*1际碎占容片用量率内存260GB100GB1002*11.4 GB.4*2280GB100100GB1.5 GB际碎占容片用量率内存量.5300GB100GB100*2GB2*2*2=400GB从RediS的内存分配模型看,碎片率是一定存在,但碎片率高到什么程度需要治理,是具体情况具体分析,本文最后会给出一些“最佳实践”。四、如何监控RediS内存碎片Redis提供infomemory命令查看相关内存情况:Imemfragment_a1ionratio:1.01InIenIa11OCator:jema11oc-5.10其中有三条指标可
7、以描述碎片率,各自含义如下:指含标义名从Redisuse角d_m度emo,ryRedis占含标义名的内存用量从操作系统use角dm度emory_Redrssis占用的含标义名存用量used_memory_memrss_frusagmed_entmematioryon_9rat表io示内存含标义名片率五、怎么解决内存碎片(RediS版本小于4.0)数据对齐:保持键值尽量使用较为固定的长度,说实话这个对业务来说很难做到。安全重启-S1aVe节点:如无流量,可以重启(一主一从的情况要谨慎,万一重启期间,master挂了。)master节点:如果当前架构可主从切换(例如redissentine1,re
8、disCIUSter等),可以尝试(但通常来说主从切换对业务100%有损)切换到新集群:新建一个集群,将老集群群整体迁移到新集群(但新集群不久后也会碎片率高)总结:上述方法要不不合实际,要不治标不治本。六、怎么解决内存碎片(RediS版本大于等于4.0)1.碎片整理功能:(仅jema11oc支持)Redis4.0re1ease后,提供了碎片整理功能Newmajorfeature:MemOryIfragmentation.ThiSfeatUre,COntribUtedbyOranAg1aa11owsRedistoperIfOrmon1inedefragmenta1ionOfmemOryifthe
9、Jema11oCa11ocatorisused.一图胜过千言万语:经过整理后24字节变为了16字节2.如何开启RediS提供了activedefrag配置实现对碎片整理的开启(默认关闭)(1)启动RediS前添加配置activedefragyes(2)动态开启onfigsetactiVedefragyes(关闭)OnfigSetaCtiVedefragnO(关闭)(3)动态参数开启(用于测试).redis-serveactivedefragyes3.相关配置解析为了更好的控制和管理RediS做碎片整理,还提供了多个参数(1) 开启阈值ictive-defrag-ignore-bytesIOOm
10、b(,kytt)ctivedefrag-threshoId-Iower10(默认值)三个条件同时满足: activedefrag=yes 碎片率大于active-defrag-thresho1d-1ower 碎片量大于active-defrag-ignore-bytesif(frag-pctserver,active_defrag_thresho1d_1OWerfrag_bytesserve.active_defrag_ignore_bytes)return;(2)增强系数active_defrag_thresho1d_upperIOO(默认值)这个参数很有迷惑性,好像相对于active-de
11、frag-thresho1d-1ower是碎片整理的上限,其实不是,它是一个增强系数:size_tfrag_bytes;F1oatfrag-pct二getA1IocatorFragnientation(&frag_bytes);ISerVer.active_defrag_thresho1d1OWerSm、r.ac1iv。defragIhrCShO1dUPI)er,Iserver.active_defragCyCIe_,:.-.H:,.:t.I:INTERPO1ATE的定义如下:把公式整理下如下:套用公式:可以看到active_defrag_threshoIe1UPPer越大,整个值越小,这个值
12、就是用来碎片整理的预估CPU值。(3)整理粒度#active-defrag-cyc1e-max75active-defrag-cyc1e-min和active-defrag-cyc1emax就是每次碎片整理的最小CPU时间和最大CPU时间,上述公式计算得到的cpu_pct还是会被这两个参数限制:ICPUPC1=11MIT(CPUPCt,server.active_defrag_cyc1emin,server,active.Idefrag_cyc1e_max)ffdefine1IMIT(yfmin,max)(y)(max)?max:(y)需要注意的是,这两个参数在不同版本的Redis默认值不太一
13、样:参4.05.O6.O6.27.O数,14.14.16.7.4active255111defrag参4.O5.O6.O6.27.O数.14.1416.7.4eyeIe-minactivedefrag7575252525eyeIe-max(4)大key优化(since5.0.0)碎片整理需要对RediS字典SCan,如果在进行中发现sethashZSet/1ist/StreanI的元素个数超过1000,则把这些键值放到一个单独的队列之后进行处理,这样做主要是为了防止碎片整理超时(如果一次SCan中包含了很多大的键值,可能在内部已经超时了)。IiS1门。小Ihati11bCProCC、dITcm
14、/hemaindietionarySCd%ctivo-defrag-maxscan-fe1dsIOOC相关代码:I1ongdefragQUick1ist(redisDb*db,dictEntry*kde)Iquick1ist*q1二ob-ptr;Iif(q1-1enserver,activedefragJnaX_scan_fie1ds)1ongdefragZsetSkip1ist(redisDb*db,dictEntry*kde)ZSet*zs=(ZSet*)ob-ptr;if(dictSize(zs-dict)server.activedefragmaXSCan.fie1ds)(Iefrag1ater(db,kde);1ongdefragHash(redisDb*db,diCtEn