当前位置: 首页 > 科技观察

Redis突然变慢如何排查和解决?

时间:2023-03-12 13:48:01 科技观察

Redis通常是我们业务系统中的一个重要组件,比如:缓存、账号登录信息、排行榜等,一旦Redis请求的延迟增加,就可能造成业务系统的“雪崩”。我在一家单身红娘互联网公司工作,双十一那天,我发起了一个下单送女朋友的活动。谁能想到,凌晨12点过后,用户激增,技术故障,用户无法下单。当时,老板怒了!查找后发现redis报Couldnotgetaresourcefromthepool。获取不到连接资源,集群中单个Redis的连接数高。大量流量丢失了Redis缓存的响应,直接打到MySQL,最后导致数据库宕机……于是对最大连接数和等待连接数进行了各种改动。尽管错误消息出现的频率有所降低,但仍会继续报告错误。后来经过离线测试,发现Redis中存储的字符数据非常大,平均1s返回数据。可以发现,一旦Redis的延迟过高,就会引发各种问题。今天“码哥”就和大家一起分析一下如何判断Redis存在性能问题以及解决方法。Redis性能有问题吗?最大延迟是从客户端发送命令到客户端收到命令响应的时间。一般情况下,Redis的处理时间是极短的,在微秒级别。当Redis的性能出现波动时,比如达到几秒到十几秒,很明显我们可以断定Redis的性能变慢了。有些硬件配置比较高。当延迟为0.6ms时,我们可以认为它很慢。在我们认为有问题之前,对于较差的硬件可能需要3毫秒。那么我们如何定义Redis真的很慢呢?因此,我们需要衡量当前环境下Redis的基线性能,即一个系统在低压、无干扰的情况下的基本性能。当你发现Redis运行时的延迟是基线性能的2倍以上时,你就可以判断Redis性能变慢了。延迟基线测量redis-cli命令提供了--intrinsic-latency选项来监控和统计测试期间的最大延迟(以毫秒为单位),可以作为Redis的基线性能。redis-cli--latency-h`host`-p`port`例如执行以下命令:redis-cli--intrinsic-latency100Maxlatencysofar:4microseconds.Maxlatencysofar:18microseconds.Maxlatency到目前为止:41微秒。到目前为止的最大延迟:57微秒。迄今为止的最大延迟:78微秒。到目前为止的最大延迟:170微秒。迄今为止的最大延迟:342微秒。到目前为止的最大延迟:3079微秒。每次运行2.2209微秒/2220.89纳秒)。最差运行时间比平均延迟长1386倍。注意:参数100是测试将执行的秒数。我们运行测试的时间越长,我们就越有可能发现延迟峰值。通常运行100秒通常是合适的,足以发现延迟问题。当然,我们可以选择在不同的时间运行几次,以避免出错。“代码兄弟”运行的最大延迟为3079微秒,因此基准性能为3079(3毫秒)微秒。需要注意的是,我们是运行在Redis服务器上,而不是客户端。这样可以避免网络对基线性能的影响。可以通过-hhost-pport连接到服务器。如果想监控网络对Redis的性能影响,可以使用Iperf来测量客户端到服务端的网络延迟。如果网络延迟几百毫秒,说明网络上可能有其他大流量程序在运行,造成网络拥塞,需要找运维协调网络流量分配。慢指令监控如何判断是否为慢指令?看操作复杂度是不是O(N)。官方文档介绍了每条命令的复杂度,并尽可能使用O(1)和O(logN)的命令。集合操作的复杂度一般为O(N),比如全量集合查询HGETALL、SMEMBERS,集合聚合操作:SORT、LREM、SUNION等。有监控数据可以观察吗?代码不是我写的。不知道有没有人用过慢指令。有两种排查方法:利用Redis慢日志功能查找慢命令;latency-monitor(延迟监控)工具。此外,还可以使用自身(top、htop、prstat等)快速查看Redismaster进程的CPU消耗。没有高流量的高CPU使用率通常表示正在使用缓慢的命令。慢日志功能Redis中的slowlog命令可以让我们快速定位到那些超过指定执行时间的慢命令。默认情况下,如果命令执行时间超过10ms,就会记录在日志中。slowlog只会记录其命令的执行时间,不包括io往返操作,也不会单独记录网络延迟导致的慢响应。我们可以根据基线性能自定义慢命令的标准(配置为基线性能最大延迟的两倍),调整触发记录慢命令的阈值。可以在redis-cli中输入如下命令配置命令记录6毫秒以上:redis-cliCONFIGSETslowlog-log-slower-than6000也可以在Redis.config配置文件中设置,单位为微秒。查看所有执行时间慢的命令,可以使用Redis-cli工具,输入slowlogget命令查看。返回结果的第三个字段以微秒为单位显示命令的执行时间。如果只需要查看最近2条慢命令,输入slowlogget2。例子:获取最后两条慢查询命令127.0.0.1:6381>SLOWLOGget21)1)(integer)62)(integer)14587342633)(integer)743724)1)"hgetall"2)"max.dsp.blacklist"2)1)(integer)52)(integer)14587342583)(integer)54110754)1)"keys"2)"max.dsp.blacklist"以第一个HGET命令为例,每个Aslowlog实体一共有4个字段:字段1:1个整数,表示这个slowlog的序号,服务器启动后递增,目前为6。字段2:表示执行查询时的Unix时间戳。Field3:表示查询执行微秒,目前为74372微秒,约74ms。字段4:表示查询的命令和参数。如果参数较多或较大,则只显示部分参数。当前命令是hgetallmax.dsp.blacklist。延迟监控Redis在2.8.13版本引入了延迟监控功能,用于以秒为粒度监控各种事件发生的频率。启用延迟监视器的第一步是以毫秒为单位设置延迟阈值。只会记录超过此阈值的时间,例如我们根据基线性能(3毫秒)的3倍设置阈值9毫秒。可以通过redis-cli或在Redis.config中设置;CONFIGSETlatency-monitor-threshold9工具记录的相关事件详情请参考官方文档:https://redis.io/topics/latency-monitorlatestlatency127.0.0.1:6379>debugsleep2OK(2.00s)127.0.0.1:6379>latencylatest1)1)"command"2)(integer)16453306163)(integer)20034)(integer)2003event名称;事件发生的最近延迟的Unix时间戳;以毫秒为单位的时间延迟;此事件的最大延迟。Redis变慢怎么解决?Redis的数据读写都是单线程完成的。如果主线程运行时间过长,主线程就会被阻塞。下面我们一起来分析一下哪些操作会阻塞主线程,又该如何解决呢?网络通信造成的延迟客户端使用TCP/IP连接或者Unix域连接来连接Redis。1Gbit/s网络的典型延迟约为200微秒。redis客户端执行一个命令分为四个过程:发送命令->命令排队->命令执行->返回结果。这个过程称为往返时间(RTT,简称RTT,往返时间)。命令(如hgetall,但不是mhgetall)不支持批量操作,需要消耗N倍的RTT。这时候就需要管道来解决这个问题。Redis管道将多个命令链接在一起以减少网络响应往返。redis-pipeline慢指令导致的延迟根据上面慢指令监控查询文档,查询到慢查询指令。可以通过以下两种方式解决:比如在Cluster集群中,在slave上运行O(N)的聚合操作等操作,或者在client上完成。请改用高效的命令。使用增量迭代避免一次查询大量数据。具体请参考SCAN、SSCAN、HSCAN、ZSCAN命令。除此之外,KEYS命令在生产中被禁用,它只适用于调试。因为它遍历了所有的键值对,所以操作延迟很高。Fork产生RDB延迟产生RDB快照,Redis必须fork后台进程。fork操作(在主线程中运行)本身会导致延迟。Redis利用操作系统的多进程写时复制技术COW(CopyOnWrite)实现快照持久化,减少内存占用。Copy-on-write技术保证了快照期间数据可以被修改,但是fork涉及复制大量的链接对象。一个24GB的大型Redis实例需要24GB/4kB*8=48MB的页表。这涉及在执行bgsave时分配和复制48MB的内存。另外RDB从库加载过程中不能提供读写服务,所以主库的数据大小控制在2~4G左右,这样可以快速完成从库加载。大内存页(transparenthugepages)常规内存页按照4KB分配。Linux内核从2.6.38开始支持内存大页机制,支持分配2MB大小的内存页。Redis通过fork生成RDB进行持久化,提供数据可靠性保障。Redis在生成RDB快照时,使用了**copy-on-write**技术,这样主线程仍然可以接收到客户端的写请求。即当数据被修改时,Redis会先将数据复制一份,然后再进行修改。使用大内存页。RDB生成时,即使客户端修改的数据只有50B,Redis也需要复制2MB的大页。当写入的指令较多时,会造成大量的拷贝,导致性能变慢。使用以下命令禁用Linux内存大页面:echonever>/sys/kernel/mm/transparent_hugepage/enabledswap:operatingsystempaging当物理内存(记忆棒)不够时,将部分内存上的数据交换到swapspace使系统不会因为内存不足而导致oom或更致命的情况。当进程向OS申请内存,发现内存不足时,OS会将内存中暂时不用的数据换出,放到SWAP分区中。此过程称为换出。当进程再次需要数据时,OS发现还有空闲的物理内存,就会将SWAP分区中的数据交换回物理内存。这个过程称为换入。内存交换是操作系统中的一种机制,用于在内存和磁盘之间交换内存数据,涉及磁盘读写。哪些情况会触发掉期?对于Redis,有两种常见的情况:Redis使用的内存超过可用内存;与Redis运行在同一台机器上的其他进程在进行大量的文件读写I/O操作(包括RDB文件和产生大文件的AOF后台线程),文件读写占用内存,导致内存减少Redis获取并触发swap。如何解决因交换导致的性能下降问题?Linux提供了很好的工具来解决这个问题,所以当您怀疑由于交换造成的延迟时,只需按照以下步骤操作即可。获取Redis实例pid$redis-cliinfo|grepprocess_idprocess_id:13160进入本进程的/proc文件系统目录:cd/proc/13160这里有一个smaps文件,描述了Redis进程的内存布局,运行如下命令,使用grep查找其中的Swap字段全部文件。$猫地图|egrep'^(Swap|Size)'Size:316kBSwap:0kBSize:4kBSwap:0kBSize:8kBSwap:0kBSize:40kBSwap:0kBSize:132kBSwap:0kBSize:720896kBSwap:12kB每行大小表示Redis实例使用的一块内存的大小,Size下面的Swap对应size-sized的内存区域有多少数据被换出到磁盘。如果Size==Swap,说明数据已经完全换出。可以看到有720896kB的内存大小,12kB换出到磁盘(只换出12kB),没有问题。Redis本身使用了很多不同大小的内存块,所以可以看到有很多Size行,有的小,4KB,有的大,比如720896KB。不同的内存块换出到不同大小的磁盘。重要的是要注意,如果Swap中的所有内容都是0kb,或者零星的4k,那么一切都很好。当出现几百MB甚至GB的swapsize时,说明此时Redis实例的内存压力非常大,很可能会变慢。解决方法是增加机器的内存;在单独的机器上运行Redis,避免在同一台机器上运行需要大量内存的进程来满足Redis的内存需求;增加Cluster集群的数量来共享数据量,减少每个实例所需的内存数据量。AOF和磁盘I/O带来的延迟为了保证数据的可靠性,Redis使用AOF和RDB快照来实现快速恢复和持久化。可以使用appendfsync配置将AOF配置为以三种不同的方式在磁盘上执行写入或fsync(可以在运行时使用CONFIGSET命令修改此设置,例如:redis-cliCONFIGSETappendfsyncno)。no:Redis不执行fsync,唯一的延迟来自write调用,write只需要在返回前将日志记录写入内核缓冲区。everysec:Redis每秒执行一次fsync。fsync操作是使用后台子线程异步完成的。最多丢失1s的数据。always:每次写操作都会执行fsync,然后回复客户端一个OK码(实际上Redis会尝试把很多同时执行的命令聚合成一个fsync),不会丢失数据。在这种模式下性能通常很低,强烈建议使用快速磁盘和可以在短时间内fsync的文件系统实现。我们通常使用Redis进行缓存。数据丢失完全是恶意从数据中获取的,对数据可靠性要求不高。建议设置为no或everysec。另外,为了避免AOF文件过大,Redis会进行AOF重写,生成一个缩小的AOF文件。可以将配置项no-appendfsync-on-rewrite设置为yes,即AOF重写时不进行fsync操作。也就是说,Redis实例将write命令写入内存后,直接返回,不调用后台线程进行fsync操作。expires清除过期数据Redis有两种清除过期数据的方法:惰性删除:当收到请求时发现key已经过期,则执行删除;定时删除:每100毫秒删除一些过期的键。定时删除算法如下:随机抽取ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP键,删除所有过期键;如果发现超过25%的key已经过期,则执行步骤1。ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默认设置为20,每秒执行10次。删除200个key问题不大。如果触发了第二项,会导致Redis不断删除过期数据释放内存。删除是阻塞的。Code兄,触发条件是什么?即大量按键设置了相同的时间参数。同一秒内大量key过期,需要多次删除才能降低到25%以下。简而言之:大量密钥同时过期会导致性能波动。解决方案如果一批key确实同时过期,可以在EXPIREAT和EXPIRE的过期时间参数中加上一个一定大小范围内的随机数,保证key在相邻的时间范围内被删除,避免同时呼气造成的压力。bigkey我们通常把数据量大或者成员数量多或者列表多的Key称为bigKey。下面我们将通过几个实际例子来描述一个大Key的特点:一个STRING类型的Key,它的值5MB(数据太大)一个LIST类型的Key有10,000个列表(列表太多)一个ZSET类型的Key有10,000个成员(toomanymembers)一个HASH格式的Key,虽然它的成员数只有1000个,但是这些成员的总值是10MB(成员的大小太大了)。Bigkey带来的问题如下:Redis内存不断增长导致OOM,或者达到maxmemory设置值导致写阻塞或者重要的Key被阻塞Evicted;RedisCluster中某个节点的内存比其他节点大很多,但是由于RedisCluster中数据迁移的最小粒度是Key,所以节点上的内存无法均衡;bigkey的读请求占用带宽过多,速度变慢,同时影响服务器其他服务;删除一个bigkey导致主库长时间阻塞导致同步中断或主从切换;要找到bigkey,请使用redis-rdb-tools工具以自定义方式找出bigkey。解决方案是拆分一个大的key,比如将一个有几万个成员的HASHKey拆分成多个HASHKey,并保证每个key的成员数在一个合理的范围内。它们之间的内存平衡可以发挥重要作用。大key的异步清理Redis从4.0开始提供了UNLINK命令,可以以非阻塞的方式慢慢地逐步清理传入的Key。通过UNLINK,你可以安全的删除大Key甚至超大Key。总结了以下checklist,帮助您在遇到Redis性能低下时高效解决问题。获取Redis当前的基线性能;开启慢命令监控,定位慢命令导致的问题;查找慢速命令并使用扫描;控制实例数据大小在2-4GB,避免主从复制加载RDB文件过大导致阻塞;禁用大内存页并使用大内存页。RDB生成时,即使客户端修改的数据只有50B,Redis也需要复制2MB大页。当写入的指令较多时,会造成大量的拷贝,导致性能变慢。Redis使用的内存是否过大导致swap;AOF配置是否合理,可以将配置项no-appendfsync-on-rewrite设置为yes,避免AOF重写和fsync争抢磁盘IO资源,导致Redis延迟增加。Bigkey会带来一系列的问题。我们需要拆分,防止bigkey出现,通过UNLINK异步删除。本文转载自微信公众号《码哥字节》