Redis大key的一些场景及问题大key场景Redis使用者应该都遇到过大key相关的场景,比如:1、热门话题下评论、答案排序场景。2、大V的粉丝列表。3、使用不恰当,或者对业务预估不准确、不及时进行处理垃圾数据等。大key问题由于Redis主线程为单线程模型,大key也会带来一些问题,如:1、集群模式在slot分片均匀情况下,会出现数据和查询倾斜情况,部分有大key的Redis节点占用内存多,QPS高。2、大key相关的删除或者自动过期时,会出现qps突降或者突升的情况,极端情况下,会造成主从复制异常,Redis服务阻塞无法响应请求。大key的体积与删除耗时可参考下表:key类型 field数量耗时Hash~100万~1000msList~100万~1000msSet~100万~1000msSorted Set~100万~1000msRedis 4.0之前的大key的发现与删除方法1、redis-rdb-tools工具。redis实例上执行bgsave,然后对dump出来的rdb文件进行分析,找到其中的大KEY。2、redis-cli –bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。3、自定义的扫描脚本,以Python脚本居多,方法与redis-cli –bigkeys类似。4、debug object key命令。可以查看某个key序列化后的长度,每次只能查找单个key的信息。官方不推荐。redis-rdb-tools工具 关于rdb工具的详细介绍请查看链接https://github.com/sripathikrishnan/redis-rdb-tools,在此只介绍内存相关的使用方法。基本的命令为 rdb -c memory dump.rdb (其中dump.rdb为Redis实例的rdb文件,可通过bgsave生成)。输出结果如下:database,type,key,size_in_bytes,encoding,num_elements,len_largest_element0,hash,hello1,1050,ziplist,86,22,0,hash,hello2,2517,ziplist,222,8,0,hash,hello3,2523,ziplist,156,12,0,hash,hello4,62020,hashtable,776,32,0,hash,hello5,71420,hashtable,1168,12,可以看到输出的信息包括数据类型,key、内存大小、编码类型等。Rdb工具优点在于获取的key信息详细、可选参数多、支持定制化需求,结果信息可选择json或csv格式,后续处理方便,其缺点是需要离线操作,获取结果时间较长。redis-cli –bigkeys命令Redis-cli –bigkeys是redis-cli自带的一个命令。它对整个redis进行扫描,寻找较大的key,并打印统计结果。例如redis-cli -p 6379 –bigkeys#Scanning the entire keyspace to find biggest keys as well as#average sizes per key type. You can use -i 0.1 to sleep 0.1 sec#per 100 SCAN commands (not usually needed).[00.72%] Biggest hash found so far ‘hello6’ with 43 fields[02.81%] Biggest string found so far ‘hello7’ with 31 bytes[05.15%] Biggest string found so far ‘hello8’ with 32 bytes[26.94%] Biggest hash found so far ‘hello9’ with 1795 fields[32.00%] Biggest hash found so far ‘hello10’ with 4671 fields[35.55%] Biggest string found so far ‘hello11’ with 36 bytes——– summary ——-Sampled 293070 keys in the keyspace!Total key length in bytes is 8731143 (avg len 29.79)Biggest string found ‘hello11’ has 36 bytesBiggest hash found ‘hello10’ has 4671 fields238027 strings with 2300436 bytes (81.22% of keys, avg size 9.66)0 lists with 0 items (00.00% of keys, avg size 0.00)0 sets with 0 members (00.00% of keys, avg size 0.00)55043 hashs with 289965 fields (18.78% of keys, avg size 5.27)0 zsets with 0 members (00.00% of keys, avg size 0.00)我们可以看到打印结果分为两部分,扫描过程部分,只显示了扫描到当前阶段里最大的key。summary部分给出了每种数据结构中最大的Key以及统计信息。redis-cli –bigkeys的优点是可以在线扫描,不阻塞服务;缺点是信息较少,内容不够精确。扫描结果中只有string类型是以字节长度为衡量标准的。List、set、zset等都是以元素个数作为衡量标准,元素个数多不能说明占用内存就一定多。自定义Python扫描脚本通过strlen、hlen、scard等命令获取字节大小或者元素个数,扫描结果比redis-cli –keys更精细,但是缺点和redis-cli –keys一样,不赘述。总之,之前的方法要么是用时较长离线解析,或者是不够详细的抽样扫描,离理想的以内存为维度的在线扫描获取详细信息有一定距离。由于在redis4.0前,没有lazy free机制;针对扫描出来的大key,DBA只能通过hscan、sscan、zscan方式渐进删除若干个元素;但面对过期删除键的场景,这种取巧的删除就无能为力。我们只能祈祷自动清理过期key刚好在系统低峰时,降低对业务的影响。Redis 4.0之后的大key的发现与删除方法Redis 4.0引入了memory usage命令和lazyfree机制,不管是对大key的发现,还是解决大key删除或者过期造成的阻塞问题都有明显的提升。下面我们从源码(摘自Redis 5.0.4版本)来理解memory usage和lazyfree的特点。memory usage{“memory”,memoryCommand,-2,”rR”,0,NULL,0,0,0,0,0}(server.c285⾏)void memoryCommand(client c) {/…//计算key大小是通过抽样部分field来估算总大小。/elseif(!strcasecmp(c->argv[1]->ptr,”usage”) &&c->argc >=3) { size_t usage = objectComputeSize(dictGetVal(de),samples);/…/ }}(object.c1299⾏)从上述源码看到memory usage是通过调用objectComputeSize来计算key的大小。我们来看objectComputeSize函数的逻辑。#defineOBJ_COMPUTE_SIZE_DEF_SAMPLES 5 / Default sample size. /size_tobjectComputeSize(robj *o, size_t sample_size){/…代码对数据类型进行了分类,此处只取hash类型说明//…//循环抽样个field,累加获取抽样样本内存值,默认抽样样本为5/while((de = dictNext(di)) != NULL && samples < sample_size) { ele = dictGetKey(de); ele2 = dictGetVal(de); elesize += sdsAllocSize(ele) + sdsAllocSize(ele2);elesize +=sizeof(structdictEntry); samples++; } dictReleaseIterator(di);/根据上一步计算的抽样样本内存值除以样本量,再乘以总的filed个数计算总内存值/if(samples) asize += (double)elesize/samplesdictSize(d);/…/ }(object.c779⾏)由此,我们发现memory usage默认抽样5个field来循环累加计算整个key的内存大小,样本的数量决定了key的内存大小的准确性和计算成本,样本越大,循环次数越多,计算结果更精确,性能消耗也越多。我们可以通过Python脚本在集群低峰时扫描Redis,用较小的代价去获取所有key的内存大小。以下为部分伪代码,可根据实际情况设置大key阈值进行预警。forkeyinr.scan_iter(count=1000):redis-cli =’/usr/bin/redis-cli’configcmd =’%s -h %s -p %s memory usage %s’% (redis-cli, rip,rport,key) keymemory = commands.getoutput(configcmd)lazyfree机制Lazyfree的原理是在删除的时候只进行逻辑删除,把key释放操作放在bio(Background I/O)单独的子线程处理中,减少删除大key对redis主线程的阻塞,有效地避免因删除大key带来的性能问题。在此提一下bio线程,很多人把Redis通常理解为单线程内存数据库, 其实不然。Redis将最主要的网络收发和执行命令等操作都放在了主工作线程,然而除此之外还有几个bio后台线程,从源码中可以看到有处理关闭文件和刷盘的后台线程,以及Redis4.0新增加的lazyfree线程。/* Background job opcodes /#defineBIO_LAZY_FREE 2/ Deferred objects freeing. /(bio.h38⾏)下面我们以unlink命令为例,来理解lazyfree的实现原理。{“unlink”,unlinkCommand,-2,”wF”,0,NULL,1,-1,1,0,0},(server.c137⾏)void unlinkCommand(client *c) {delGenericCommand(c,1);}(db.c490⾏)通过这几段源码可以看出del命令和unlink命令都是调用delGenericCommand,唯一的差别在于第二个参数不一样。这个参数就是异步删除参数。/ This command implements DEL and LAZYDEL. /void delGenericCommand(client *c, intlazy) {/…/int deleted =lazy? dbAsyncDelete(c->db,c->argv[j]) :dbSyncDelete(c->db,c->argv[j]);/…/}(db.c468⾏)可以看到delGenericCommand函数根据lazy参数来决定是同步删除还是异步删除。当执行unlink命令时,传入lazy参数值1,调用异步删除函数dbAsyncDelete。否则执行del命令传入参数值0,调用同步删除函数dbSyncDelete。我们重点来看异步删除dbAsyncDelete的实现逻辑:#defineLAZYFREE_THRESHOLD 64/定义后台删除的阈值,key的元素大于该阈值时才真正丢给后台线程去删除/intdbAsyncDelete(redisDb *db, robj *key){/…//lazyfreeGetFreeEffort来获取val对象所包含的元素个数/size_tfree_effort = lazyfreeGetFreeEffort(val);/ 对删除key进行判断,满足阈值条件时进行后台删除 /if(free_effort > LAZYFREE_THRESHOLD && val->refcount ==1) {atomicIncr(lazyfree_objects,1);bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);/将删除对象放入BIO_LAZY_FREE后台线程任务队列/dictSetVal(db->dict,de,NULL);/将第一步获取到的val值设置为null/ }/…/}(lazyfree.c53⾏)上面提到了当删除key满足阈值条件时,会将key放入BIO_LAZY_FREE后台线程任务队列。接下来我们来看BIO_LAZY_FREE后台线程。/…/elseif(type == BIO_LAZY_FREE) {if(job->arg1)/ 后台删除对象函数,调用decrRefCount减少key的引用计数,引用计数为0时会真正的释放资源 / lazyfreeFreeObjectFromBioThread(job->arg1);elseif(job->arg2 && job->arg3)/ 后台清空数据库字典,调用dictRelease循环遍历数据库字典删除所有key / lazyfreeFreeDatabaseFromBioThread(job->arg2,job->arg3);elseif(job->arg3)/ 后台删除key-slots映射表,在Redis集群模式下会用*/ lazyfreeFreeSlotsMapFromBioThread(job->arg3);}(bio.c197⾏)unlink命令的逻辑可以总结为:执行unlink调用delGenericCommand函数传入lazy参数值1,来调用异步删除函数dbAsyncDelete,将满足阈值的大key放入BIO_LAZY_FREE后台线程任务队列进行异步删除。类似的后台删除命令还有flushdb async、flushall async。它们的原理都是获取删除标识进行判断,然后调用异步删除函数emptyDbAsnyc来清空数据库。这些命令具体的实现逻辑可自行查看flushdbCommand部分源码,在此不做赘述。除了主动的大key删除和数据库清空操作外,过期key驱逐引发的删除操作也会阻塞Redis服务。因此Redis4.0除了增加上述三个后台删除的命令外,还增加了4个后台删除配置项,分别为slave-lazy-flush、lazyfree-lazy-eviction、lazyfree-lazy-expire和lazyfree-lazy-server-del。slave-lazy-flush:slave接收完RDB文件后清空数据选项。建议大家开启slave-lazy-flush,这样可减少slave节点flush操作时间,从而降低主从全量同步耗时的可能性。lazyfree-lazy-eviction:内存用满逐出选项。若开启此选项可能导致淘汰key的内存释放不够及时,内存超用。lazyfree-lazy-expire:过期key删除选项。建议开启。lazyfree-lazy-server-del:内部删除选项,比如rename命令将oldkey修改为一个已存在的newkey时,会先将newkey删除掉。如果newkey是一个大key,可能会引起阻塞删除。建议开启。上述四个后台删除相关的参数实现逻辑差异不大,都是通过参数选项进行判断,从而选择是否采用dbAsyncDelete或者emptyDbAsync进行异步删除。总结在某些业务场景下,Redis大key的问题是难以避免的,但是,memory usage命令和lazyfree机制分别提供了内存维度的抽样算法和异步删除优化功能,这些特性有助于我们在实际业务中更好的预防大key的产生和解决大key造成的阻塞。关于Redis内核的优化思路也可从Redis作者Antirez的博客中窥测一二,他提出”Lazy Redis is better Redis”、”Slow commands threading”(允许在不同的线程中执行慢操作命令),异步化应该是Redis优化的主要方向。Redis作为个推消息推送的一项重要的基础服务,性能的好坏至关重要。个推将Redis版本从2.8升级到5.0后,有效地解决了部分大key删除或过期造成的阻塞问题。未来,个推将会持续关注Redis 5.0及后续的Redis 6.0,与大家共同探讨如何更好地使用Redis。参考文档: 1、http://antirez.com/news/932、http://antirez.com/news/126
https://juejin.im/post/5d8f0f7de51d4578323d51d0
redis-cli monitor
https://redis.io/commands/monitor
https://docs.microsoft.com/zh-cn/azure/azure-cache-for-redis/cache-troubleshoot-server#server-side-troubleshooting
http://www.blogjava.net/yongboy/archive/2014/11/09/419829.html
内存抽样分析
/redis/script/redis-sampler.rb 127.0.0.1 6379 0 10000
/redis/script/redis-audit.rb 127.0.0.1 6379 0 10000
https://www.bookstack.cn/read/All-About-Redis/Problem-memory-sample.md
一、Redis监控告警的价值
redis故障快速通知,定位故障点;对于DBA,redis的可用性和性能故障需快速发现和定位解决。
分析redis故障的Root cause
redis容量规划和性能管理
redis硬件资源利用率和成本
1、redis故障快速发现,定位故障点和解决故障
当redis出现故障时,DBA应在尽可能短时间内发现告警;如果故障对服务是有损的(如大面积网络故障或程序BUG),需立即通知SRE和RD启用故障预案(如切换机房或启用emergency switch)止损。
如果没完善监控告警;假设由RD发现服务故障,再排查整体服务调用链去定位;甚于用户发现用问题,通过客服投诉,再排查到redis故障的问题;整个redis故障的发现、定位和解决时间被拉长,把一个原本的小故障被”无限”放大。
2、分析redis故障的根本原因
任何一个故障和性能问题,其根本“诱因”往往只有一个,称为这个故障的Root cause。
一个故障从DBA发现、止损、分析定位、解决和以后规避措施;最重要一环就是DBA通过各种问题表象,层层分析到Root cause;找到问题的根据原因,才能根治这类问题,避免再次发生。
完善的redis监控数据,是我们分析root cause的基础和证据。
备注:Troubleshtooing定位Root cause,就像医生通过病人的病历和检查报告找到“真正的病灶”,让病人康复和少受苦,一样有意思和复杂;或像刑警通过案件的证据分析和推理,寻找那个唯一的真相,一样惊心动魄。(快看DBA又在吹牛了),其实在大型商业系统中,一次故障轻松就达直接损失数十万(间接损失更大),那“抓住元凶”,避免它再次“作案”,同样是“破案”。
问题表现是综合情的,一般可能性较复杂,这里举2个例子:
服务调用Redis响应时间变大的性能总是;可能网络问题,redis慢查询,redis QPS增高达到性能瓶颈,redis fork阻塞和请求排队,redis使用swap, cpu达到饱和(单核idle过低),aof fsync阻塞,网络进出口资源饱和等等
redis使用内存突然增长,快达到maxmemory; 可能其个大键写入,键个数增长,某类键平均长度突增,fork COW, 客户端输入/输出缓冲区,lua程序占用等等
Root cause是要直观的监控数据和证据,而非有技术支撑的推理分析。
redis响应抖动,分析定位root casue是bgsave时fork导致阻塞200ms的例子。而不是分析推理:redis进程rss达30gb,响应抖动时应该有同步,fork子进程时,页表拷贝时要阻塞父进程,估计页表大小xx,再根据内存copy连续1m数据要xx 纳秒,分析出可能fork阻塞导致的。(要的不是这种分析)
说明:粮厂有个习惯,在分析root cause尽量能拿到直观证据。因为一旦引入推理步骤,每一步的推理结果都可能出现偏差,最终可能给出错误root cause. “元凶”又逃过一劫,它下次作案估计就会更大。所以建议任何小的故障或抖动,至少从个人或小组内部,深入分析找到root cause;这样个人或组织都会成长快; 形成良好的氛围。
3、Redis容量规划和性能管理
通过分析redis资源使用和性能指标的监控历史趋势数据;对集群进行合理扩容(Scale-out)、缩容(Scale-back);对性能瓶颈优化处理等。
Redis资源使用饱和度监控,设置合理阀值;
一些常用容量指标:redis内存使用比例,swap使用,cpu单核的饱和度等;当资源使用容量预警时,能及时扩容,避免因资源使用过载,导致故障。
另一方面,如果资源利用率持续过低,及时通知业务,并进行redis集群缩容处理,避免资源浪费。
进一步,容器化管理redis后,根据监控数据,系统能自动地弹性扩容和缩容。
Redis性能监控管理,及时发现性能瓶颈,进行优化或扩容,把问题扼杀在”萌芽期“,避免它”进化“成故障。
4、Redis硬件资源利用率和成本
从老板角度来看,最关心的是成本和资源利用率是否达标。
如果资源不达标,就得推进资源优化整合;提高硬件利用率,减少资源浪费。砍预算,减成本。
资源利用率是否达标的数据,都是通过监控系统采集的数据。
这一小节,扯了这么多; 只是强调redis不是只有一个端口存活监控就可以了。
下面进入主题,怎么采集redsis监控数。
老板曾说:监控告警和数据备份,是对DBA和SRE最基础也是最高的要求;
当服务和存储达到产品规模后,可认为“无监控,不服务;无备份,不存储”。
二、Redis监控的内容
针对redis监控,可以分为几个层面:
1、服务器系统相关:服务器宕机,CPU,
2、redis应用进程。
3、redis性能指标
4、应用的响应时间:Redis慢查询监控
2.1、服务器系统监控
1)、服务器存活监控:
2)CPU
平均负载 (Load Average): 综合负载指标(暂且归类cpu子系统),当系统的子系统出现过度使用时,平均负载会升高。可说明redis的处理性能下降(平均响应时间变长、吞吐量降低)。
CPU整体利用率或饱和度 (cpu.busy): redis在高并发或时间复杂度高的指令,cpu整体资源饱和,导致redis性能下降,请求堆积。
CPU单核饱和度 (cpu.core.idle/core=0): redis是单进程模式,常规情况只使用一个cpu core, 单某个实例出现cpu性能瓶颈,导致性能故障,但系统一般24线程的cpu饱和度却很低。所以监控cpu单核心利用率也同样重样。
CPU上下文切换数 (cpu.switches):context swith过高xxxxxx
3)内存和swap
系统内存余量大小 (mem.memfree):redis是纯内存系统,系统内存必须保有足够余量,避免出现OOM,导致redis进程被杀,或使用swap导致redis性能骤降。
系统swap使用量大小 (mem.swapused):redis的”热数据“只要进入swap,redis处理性能就会骤降; 不管swap分区的是否是SSD介质。OS对swap的使用材质还是disk store. 这也是作者早期redis实现VM,后来又放弃的原因。
说明:系统内存余量合理,给各种缓冲区,fork cow足够的内存空间。
另一个问题:我的系统使用Redis缓存集群,”不怕挂,就怕慢“,或redis集群高可用做得厉害;这样redis的服务器是否能关闭swap呢?
4)磁盘
磁盘分区的使用率 (df.bytes.used.percent):磁盘空间使用率监控告警,确保有足磁盘空间用AOF/RDB, 日志文件存储。不过 redis服务器一般很少出现磁盘容量问题
磁盘IOPS的饱和度(disk.io.util):如果有AOF持久化时,要注意这类情况。如果AOF持久化,每秒sync有堆积,可能导致写入stall的情况。 另外磁盘顺序吞吐量还是很重要,太低会导致复制同步RDB时,拉长同步RDB时间。(期待diskless replication)
5)网络
网络吞吐量饱和度(net.if.out.bytes/net.if.in.bytes):如果服务器是千兆网卡(Speed: 1000Mb/s),单机多实例情况,有异常的大key容量导致网卡流量打滿。redis整体服务等量下降,苦于出现故障切换。
丢包率 :Redis服务响应质量受影响
2.2、Redis应用进程监控
1)、端口存活
2)、进程占用的cpu和内存
3)、网络连接数
2.3、redis性能指标
可以通过info 命令获取相关性能指标。
info命令输出的数据可分为10个类别,分别是:
server
clients
memory
persistence
stats
replication
cpu
commandstats
cluster
keyspace
我们主要关注信息:
Redis 连接数监控:clients
connected_clients 连接个数:客户端连接个数,如果连接数过高,影响redis吞吐量。常规建议不要超过5000.参考 官方benchmarks
connected_clients_pct(连接数使用率): 连接数使用百分比,通过(connected_clients/macclients)计算;如果达到1,redis开始拒绝新连接创建。
rejected_connections(拒绝的连接个数): redis连接个数达到maxclients限制,拒绝新连接的个数。
total_connections_received(新创建连接个数 ): 如果新创建连接过多,过度地创建和销毁连接对性能有影响,说明短连接严重或连接池使用有问题,需调研代码的连接设置。
blocked_clients (list阻塞调用被阻塞的连接个数 ): BLPOP这类命令没使用过,如果监控数据大于0,还是建议排查原因。
Redis内存监控和优化:memory
used_memory : redis真实使用内存,不包含内存碎片;单实例的内存大小不建议过大,常规10~20GB以内。
used_memory_pct(redis内存使用比例): 已分配内存的百分比,通过(used_memory/maxmemory)计算;对于redis存储场景会比较关注,未设置淘汰策略(maxmemory_policy)的,达到maxmemory限制不能写入数据。
used_memory_rss (redis进程使用内存大小): 进程实际使用的物理内存大小,包含内存碎片;如果rss过大导致内部碎片大,内存资源浪费,和fork的耗时和cow内存都会增大。
mem_fragmentation_ratio(redis内存碎片率 ): 表示(used_memory_rss/used_memory),碎片率过大,导致内存资源浪费;
说明:
1、如果内存使用很小时,mem_fragmentation_ratio可以远大于1的情况,这个告警值不好设置,需参考used_memory大小。
2、如果mem_fragmentation_ratio小于1,表示redis已使用swap分区
1、因内存交换引起的性能问题
如果Redis实例的内存使用率超过可用最大内存 (used_memory > 可用最大内存),那么操作系统开始进行内存与swap空间交换,把内存中旧的或不再使用的内容写入硬盘上(硬盘上的这块空间叫Swap分区),以便留出新的物理内存给新页或活动页(page)使用。
如果Redis进程上发生内存交换,那么Redis和依赖Redis上数据的应用会受到严重的性能影响。 通过查看used_memory指标可知道Redis正在使用的内存情况,如果used_memory>可用最大内存,那就说明Redis实例正在进行内存交换或者已经内存交换完毕。
2、跟踪内存使用率
若是在使用Redis期间没有开启rdb快照或aof持久化策略,那么缓存数据在Redis崩溃时就有丢失的危险。因为当Redis内存使用率超过可用内存的95%时,部分数据开始在内存与swap空间来回交换,这时就可能有丢失数据的危险。
当开启并触发快照功能时,Redis会fork一个子进程把当前内存中的数据完全复制一份写入到硬盘上。因此若是当前使用内存超过可用内存的45%时触发快照功能,那么此时进行的内存交换会变的非常危险(可能会丢失数据)。 倘若在这个时候实例上有大量频繁的更新操作,问题会变得更加严重。
通过减少Redis的内存占用率,来避免这样的问题,或者使用下面的技巧来避免内存交换发生:
尽可能的使用Hash数据结构。因为Redis在储存小于100个字段的Hash结构上,其存储效率是非常高的。
设置key的过期时间。一个减少内存使用率的简单方法就是,每当存储对象时确保设置key的过期时间。
回收key。 若是启用了Redis快照功能,应该设置“maxmemory”值为系统可使用内存的45%,因为快照时需要一倍的内存来复制整个数据集,也就是说如果当前已使用45%,在快照期间会变成95%(45%+45%+5%),其中5%是预留给其他的开销。 如果没开启快照功能,maxmemory最高能设置为系统可用内存的95%。
当内存使用达到设置的最大阀值时,需要选择一种key的回收策略,可在Redis.conf配置文件中修改“maxmemory-policy”属性值。 若是Redis数据集中的key都设置了过期时间,那么“volatile-ttl”策略是比较好的选择。但如果key在达到最大内存限制时没能够迅速过期,或者根本没有设置过期时间。那么设置为“allkeys-lru”值比较合适,它允许Redis从整个数据集中挑选最近最少使用的key进行删除(LRU淘汰算法)。Redis还提供了一些其他淘汰策略,如下:
volatile-lru:使用LRU算法从已设置过期时间的数据集合中淘汰数据。
volatile-ttl:从已设置过期时间的数据集合中挑选即将过期的数据淘汰。
volatile-random:从已设置过期时间的数据集合中随机挑选数据淘汰。
allkeys-lru:使用LRU算法从所有数据集合中淘汰数据。
allkeys-random:从数据集合中任意选择数据淘汰
no-enviction:禁止淘汰数据。
通过设置maxmemory为系统可用内存的45%或95%(取决于持久化策略)和设置“maxmemory-policy”为“volatile-ttl”或“allkeys-lru”(取决于过期设置),可以比较准确的限制Redis最大内存使用率,在绝大多数场景下使用这2种方式可确保Redis不会进行内存交换。倘若你担心由于限制了内存使用率导致丢失数据的话,可以设置noneviction值禁止淘汰数据。
Redis综合性能监控
redis键空间的状态监控:
keys(键个数 ): redis实例包含的键个数。建议控制在1kw内;单实例键个数过大,可能导致过期键的回收不及时。
keys_expires(设置有生存时间的键个数 ): 是纯缓存或业务的过期长,都建议对键设置TTL; 避免业务的死键问题. (expires字段)
avg_ttl (估算设置生存时间键的平均寿命 ): redis会抽样估算实例中设置TTL键的平均时长,单位毫秒。如果无TTL键或在Slave则avg_ttl一直为0
evicted_keys (LRU淘汰的键个数 ): 因used_memory达到maxmemory限制,并设置有淘汰策略的实例;(对排查问题重要,可不设置告警)
expired_keys (过期淘汰的键个数 ): 删除生存时间为0的键个数;包含主动删除和定期删除的个数。
Redis qps:
total_commands_processed (redis处理的命令数 ): 监控采集周期内的平均qps,
redis单实例处理达数万,如果请求数过多,redis过载导致请求堆积。
instantaneous_ops_per_sec(redis当前的qps ): redis内部较实时的每秒执行的命令数;可和total_commands_processed监控互补。
在Redis实例中,跟踪命令处理总数是解决响应延迟问题最关键的部分,因为Redis是个单线程模型,客户端过来的命令是按照顺序执行的。比较常见的延迟是带宽,通过千兆网卡的延迟大约有200μs。倘若明显看到命令的响应时间变慢,延迟高于200μs,那可能是Redis命令队列里等待处理的命令数量比较多。 如上所述,延迟时间增加导致响应时间变慢可能是由于一个或多个慢命令引起的,这时可以看到每秒命令处理数在明显下降,甚至于后面的命令完全被阻塞,导致Redis性能降低。要分析解决这个性能问题,需要跟踪命令处理数的数量和延迟时间。
Redis cmdstat_xxx
redis记录执行过的所有命令; 通过info all的Commandstats节采集数据.
cmdstat_xxx (每类命令执行的次数 ): 这个值用于分析redis抖动变化比较有用
以下表示:每个命令执行次数,总共消耗的CPU时长(单个微秒),平均每次消耗的CPU时长(单位微秒)
cmdstat_set:calls=6,usec=37,usec_per_call=6.17
cmdstat_lpush:calls=4,usec=32,usec_per_call=8.00
cmdstat_lpop:calls=4,usec=33,usec_per_call=8.25
Redis Keysapce hit ratio
redis键空间请求命中率监控,通过此监控来度量redis缓存的质量,如果未命中率或次数较高,可能因热点数据已大于redis的内存限制,导致请求落到后端存储组件,可能需要扩容redis缓存集群的内存容量。当然也有可能是业务特性导致。
keyspace_hits(请求键被命中次数 ): redis请求键被命中的次数
keyspace_misses (请求键未被命中次数 ): redis请求键未被命中的次数;当命中率较高如95%,如果请求量大,未命中次数也会很多。可参考Baron大神写的 Why you should ignore MySQL’s key cache hit ratio
keyspace_hit_ratio(请求键的命中率 ):使用keyspace_hits/(keyspace_hits+keyspace_misses)计算所得,是度量Redis缓存服务质量的标准
Redis fork
redis在执行BGSAVE,BGREWRITEAOF命令时,redis进程有 fork 操作。而fork会对redis进程有个短暂的卡顿,这个卡顿redis不能响应任务请求。所以监控fork阻塞时长,是相当重要。
如果你的系统不能接受redis有500ms的阻塞,那么就要监控fork阻塞时长的变化,做好容量规划。
latest_fork_usec (最近一次fork阻塞的微秒数 ): 最近一次Fork操作阻塞redis进程的耗时数,单位微秒。
redis network traffic
redis一般单机多实例部署,当服务器网络流量增长很大,需快速定位是网络流量被哪个redis实例所消耗了; 另外redis如果写流量过大,可能导致slave线程“客户端输出缓冲区”堆积,达到限制后被Maser强制断开连接,出现复制中断故障。所以我们需监控每个redis实例网络进出口流量,设置合适的告警值。
说明:网络监控指标 ,需较高的版本才有,应该是2.8.2x以后
total_net_input_bytes:redis网络入口流量字节数
total_net_output_bytes:redis网络出口流量字节数
instantaneous_input_kbps:redis网络入口kps
instantaneous_output_kbps:redis网络出口kps
前两者是累计值,根据监控平台1个采集周期(如1分钟)内平均每秒的流量字节数。
Redis持久化监控
redis存储场景的集群,就得 redis持久化 保障数据落地,减少故障时数据丢失。这里分析redis rdb数据持久化的几个监控指标。
rdb_last_bgsave_status 最近一次rdb持久化是否成功:如果持久化未成功,建议告警,说明备份或主从复制同步不正常。或redis设置有”stop-writes-on-bgsave-error”为yes,当save失败后,会导致redis不能写入操作
rdb_last_bgsave_time_sec最近一次成功生成rdb文件耗时秒数 ):rdb生成耗时反应同步时数据是否增长; 如果远程备份使用redis-cli –rdb方式远程备份rdb文件,时间长短可能影响备份线程客户端输出缓冲内存使用大小。
rdb_changes_since_last_save离最近一次成功生成rdb文件,写入命令的个数):即有多少个写入命令没有持久化,最坏情况下会丢失的写入命令数。建议设置监控告警
rdb_last_save_time离最近一次成功rdb持久化的秒数: 最坏情况丢失多少秒的数据写入。使用当前时间戳 - 采集的rdb_last_save_time(最近一次rdb成功持久化的时间戳),计算出多少秒未成功生成rdb文件
Redis复制监控
不论使用何种redis集群方案, redis复制 都会被使用。
复制相关的监控告警项:
redis_role redis角色 ):实例的角色,是master or slave
master_link_status复制连接状态 : slave端可查看它与master之间同步状态;当复制断开后表示down,影响当前集群的可用性。需设置监控告警。
master_link_down_since_seconds复制连接断开时间长度 ):主从服务器同步断开的秒数,建议设置时长告警。
master_last_io_seconds主库多少秒未发送数据到从库 ):如果主库超过repl-timeout秒未向从库发送命令和数据,会导致复制断开重连。详细分析见文章: Redis复制中断和无限同步问题 。 在slave端可监控,建议设置大于10秒告警
slave_lag从库多少秒未向主库发送REPLCONF命令 : 正常情况从库每秒都向主库,发送REPLCONF ACK命令;如果从库因某种原因,未向主库上报命令,主从复制有中断的风险。通过在master端监控每个slave的lag值。
slave_read_only从库是否设置只读 ):从库默认只读禁止写入操作,监控从库只读状态;
如果关闭从库只读,有写入数据风险。关于主从数据不一致,见文章分析: Redis复制主从数据不-致
connected_slaves主库挂载的从库个数 ):主库至少保证一个从库,不建议设置超过2个从库。
repl_backlog_active:复制积压缓冲区是否开启 :主库默认开启复制积压缓冲区,用于应对短时间复制中断时,使用 部分同步 方式。
repl_backlog_size复制积压缓冲大小 :主库复制积压缓冲大小默认1MB,因为是redis server共享一个缓冲区,建议设置100MB.
说明: 关于根据实际情况,设置合适大小的复制缓冲区。可以通过master_repl_offset指标计算每秒写入字节数,同时乘以希望多少秒内闪断使用“部分同步”方式。
Redis集群监控
这里所写 redis官方集群方案 的监控指标
数据基本通过cluster info和info命令采集。
cluster_enabled实例是否启用集群模式 ): 通过info的cluster_enabled监控是否启用集群模式。
clusster_state集群健康状态 :如果当前redis发现有failed的slots,默认为把自己cluster_state从ok个性为fail, 写入命令会失败。如果设置cluster-require-full-coverage为NO,则无此限制。
cluster_slots_assigned集群数据槽slots分配情况 :集群正常运行时,默认16384个slots
cluster_slots_fail检测下线的数据槽slots个数 :集群正常运行时,应该为0. 如果大于0说明集群有slot存在故障。
cluster_size集群的分片数 ):集群中设置的分片个数
cluster_known_nodes集群的节点数 ):集群中redis节点的个数
2.4、Redis响应时间监控
响应时间 是衡量一个服务组件性能和质量的重要指标。使用redis的服务通常对响应时间都十分敏感,比如要求99%的响应时间达10ms以内。
因redis的慢查询日志只计算命令的cpu占用时间,不会考虑排队或其他耗时。
respond_time_max最长响应时间 ):最长响应时间的毫秒数
respond_time_99_max :99%的响应时间长度 ):
respond_time_99_avg:99%的平均响应时间长度 ():
respond_time_95_max 95%的响应时间长度 ():
respond_time_95_avg 95%的平均响应时间长度 ():
响应时间监控的方式建议,最简单方法,使用 Percona tcprstat
Redis慢查询监控
redis慢查询 是排查性能问题关键监控指标。因redis是单线程模型(single-threaded server),即一次只能执行一个命令,如果命令耗时较长,其他命令就会被阻塞,进入队列排队等待;这样对程序性能会较大。
redis慢查询保存在内存中,最多保存slowlog-max-len(默认128)个慢查询命令,当慢查询命令日志达到128个时,新慢查询被加入前,会删除最旧的慢查询命令。因慢查询不能持久化保存,且不能实时监控每秒产生的慢查询个数。
我们建议的慢查询监控方法:
slowlog-log-slower-than 设置合理慢查询日志阀值,, 建议1ms(如果平均1ms, redis qps也就只有1000)
slowlog-max-len 设置全理慢查询日志队列长度,建议大于1024个,因监控采集周期1分钟,建议,避免慢查询日志被删除;另外慢查询的参数过多时,会被省略,对内存消耗很小
slowlog len 每次采集使用获取慢查询日志个数
slowlog get 1024 每次彩集使用获取所慢查询,并转存储到其他地方,如MongoDB或MySQL等,方便排查问题;并分析当前慢查询日志最长耗时微秒数。
slowlog reset 然后使用把慢查询日志清空,下个采集周期的日志长度就是最新产生的。
redis慢查询的监控项:
redis慢查询日志个数 (slowlog_len):每个采集周期出现慢查询个数,如1分钟出现10次大于1ms的慢查询
redis慢查询日志最长耗时值 (slowlog_max_time):获取慢查询耗时最长值,因有的达10秒以下的慢查询,可能导致复制中断,甚至出来主从切换等故障。
Redis中的slowlog命令可以让我们快速定位到那些超出指定执行时间的慢命令,默认情况下命令若是执行时间超过10ms就会被记录到日志。slowlog只会记录其命令执行的时间,不包含io往返操作,也不记录单由网络延迟引起的响应慢。通常1gb带宽的网络延迟,预期在200μs左右,倘若一个命令仅执行时间就超过10ms,那比网络延迟慢了近50倍。 想要查看所有执行时间比较慢的命令,可以通过使用Redis-cli工具,使用slowlog get命令查看,返回结果的第三个字段以微妙位单位显示命令的执行时间。
图中字段分别意思是:
1)、日志的唯一标识符
2)、被记录命令的执行时间点,以 UNIX 时间戳格式表示
3)、查询执行时间,以微秒为单位
4)、执行的命令,以数组的形式排列。完整命令是config get *
三、Redis info详细详解
redis_version:3.2.3 #redis版本号
redis_git_sha1:00000000 #git sha1摘要值
redis_git_dirty:0 #git dirty标识
redis_build_id:443e50c39cbcdbe0 #redis构建id
redis_mode:standalone #运行模式:standalone、sentinel、cluster
os:Linux 3.10.0-514.16.1.el7.x86_64 x86_64 #服务器宿主机操作系统
arch_bits:64 服务器宿主机CUP架构(32位/64位)
multiplexing_api:epoll #redis IO机制
gcc_version:4.8.5 #编译 redis 时所使用的 GCC 版本
process_id:1508 #服务器进程的 PID
run_id:b4ac0f9086659ce54d87e41d4d2f947e19c28401 #redis 服务器的随机标识符 (用于 Sentinel 和集群)
tcp_port:6380 #redis服务监听端口
uptime_in_seconds:520162 #redis服务启动以来经过的秒数
uptime_in_days:6 #redis服务启动以来经过的天数
hz:10 #redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次
lru_clock:16109450 #自增的时钟,用于LRU管理,该时钟100ms(hz=10,因此每1000ms/10=100ms执行一次定时任务)更新一次
executable:/usr/local/bin/redis-server
config_file:/data/redis-6380/redis.conf 配置文件的路径
connected_clients:2 #已连接客户端的数量(不包括通过从属服务器连接的客户端)
client_longest_output_list:0 #当前连接的客户端当中,最长的输出列表
client_biggest_input_buf:0 #当前连接的客户端当中,最大输入缓存
blocked_clients:0 #正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量
used_memory:426679232 #由 redis 分配器分配的内存总量,以字节(byte)为单位
used_memory_human:406.91M #以可读的格式返回 redis 分配的内存总量(实际是used_memory的格式化)
used_memory_rss:443179008 #从操作系统的角度,返回 redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps等命令的输出一致
used_memory_rss_human:422.65M # redis 的内存消耗峰值(以字节为单位)
used_memory_peak:426708912
used_memory_peak_human:406.94M
total_system_memory:16658403328
total_system_memory_human:15.51G
used_memory_lua:37888 # Lua脚本存储占用的内存
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:1.04 # used_memory_rss/ used_memory
mem_allocator:jemalloc-4.0.3
loading:0 #服务器是否正在载入持久化文件,0表示没有,1表示正在加载
rdb_changes_since_last_save:3164272 #离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化
rdb_bgsave_in_progress:0 #服务器是否正在创建rdb文件,0表示否
rdb_last_save_time:1559093160 #离最近一次成功创建rdb文件的时间戳。当前时间戳 - rdb_last_save_time=多少秒未成功生成rdb文件
rdb_last_bgsave_status:ok #最近一次rdb持久化是否成功
rdb_last_bgsave_time_sec:-1 #最近一次成功生成rdb文件耗时秒数
rdb_current_bgsave_time_sec:-1 #如果服务器正在创建rdb文件,那么这个域记录的就是当前的创建操作已经耗费的秒数
aof_enabled:0 #是否开启了aof
aof_rewrite_in_progress:0 #标识aof的rewrite操作是否在进行中
aof_rewrite_scheduled:0 #rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite
aof_last_rewrite_time_sec:-1 #最近一次aof rewrite耗费的时长
aof_current_rewrite_time_sec:-1 #如果rewrite操作正在进行,则记录所使用的时间,单位秒
aof_last_bgrewrite_status:ok #上次bgrewriteaof操作的状态
aof_last_write_status:ok #上次aof写入状态
total_connections_received:10 #服务器已经接受的连接请求数量
total_commands_processed:9510792 #redis处理的命令数
instantaneous_ops_per_sec:1 #redis当前的qps,redis内部较实时的每秒执行的命令数
total_net_input_bytes:1104411373 #redis网络入口流量字节数
total_net_output_bytes:66358938 #redis网络出口流量字节数
instantaneous_input_kbps:0.04 #redis网络入口kps
instantaneous_output_kbps:3633.35 #redis网络出口kps
rejected_connections:0 #拒绝的连接个数,redis连接个数达到maxclients限制,拒绝新连接的个数
sync_full:0 #主从完全同步成功次数
sync_partial_ok:0 #主从部分同步成功次数
sync_partial_err:0 #主从部分同步失败次数
expired_keys:0 #运行以来过期的key的数量
evicted_keys:0 #运行以来剔除(超过了maxmemory后)的key的数量
keyspace_hits:87 #命中次数
keyspace_misses:17 #没命中次数
pubsub_channels:0 #当前使用中的频道数量
pubsub_patterns:0 #当前使用的模式的数量
latest_fork_usec:0 #最近一次fork操作阻塞redis进程的耗时数,单位微秒
migrate_cached_sockets:0 #是否已经缓存了到该地址的连接
role:master #实例的角色,是master or slave
connected_slaves:0 #连接的slave实例个数
master_repl_offset:0 #主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟,与master_replid可被用来标识主实例复制流中的位置
repl_backlog_active:0 #复制积压缓冲区是否开启
repl_backlog_size:1048576 #复制积压缓冲大小
repl_backlog_first_byte_offset:0 #复制缓冲区里偏移量的大小
repl_backlog_histlen:0 #此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小
used_cpu_sys:507.00 #将所有redis主进程在核心态所占用的CPU时求和累计起来
used_cpu_user:280.48 #将所有redis主进程在用户态所占用的CPU时求和累计起来
used_cpu_sys_children:0.00 #将后台进程在核心态所占用的CPU时求和累计起来
used_cpu_user_children:0.00 将后台进程在用户态所占用的CPU时求和累计起来
cluster_enabled:0
db0:keys=5557407,expires=362,avg_ttl=604780497
db15:keys=1,expires=0,avg_ttl=0
https://blog.csdn.net/hguisu/article/details/90763207
https://toutiao.io/posts/ho5wpe/preview
https://www.w3cschool.cn/redis_all_about/redis_all_about-xzio26xj.html
https://www.cnblogs.com/me115/p/8516838.html