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

半夜数据库突然宕机是Redis引起的?

时间:2023-03-13 21:08:54 科技观察

谁能想到,凌晨12点过后,用户激增,技术故障,用户无法下单。当时,老板怒了!查找后发现redis报Couldnotgetaresourcefromthepool。获取不到连接资源,集群中单个Redis的连接数高。大量流量丢失了Redis缓存的响应,直接打到MySQL,最后导致数据库宕机……于是对最大连接数和等待连接数进行了各种改动。尽管错误消息出现的频率有所降低,但仍会继续报告错误。后来经过离线测试,发现Redis中存储的字符数据非常大,平均1s返回数据。可以发现,一旦Redis的延迟过高,就会引发各种问题。今天就和大家一起分析下如何判断Redis存在性能问题以及解决方法。内容:1.延迟基线测量2.慢指令监控和慢日志功能延迟监控3.网络通信造成的延迟4.慢指令造成的延迟5.Fork产生的RDB造成的延迟6.透明巨页7.交换:操作系统分页获取Redis实例的pid解决方法8、AOF和磁盘I/O引起的延迟9、expires消除过期数据解决方法10、bigkey搜索bigkey解决方法Redis性能有问题吗?最大延迟是从客户端发送命令到客户端收到命令响应的时间。一般情况下,Redis的处理时间很短,在微秒级。当Redis的性能出现波动时,比如达到几秒到十几秒,很明显我们可以断定Redis的性能变慢了。有些硬件配置比较高。当延迟为0.6ms时,我们可以认为它很慢。在我们认为有问题之前,对于较差的硬件可能需要3毫秒。那么我们如何定义Redis真的很慢呢?因此,我们需要衡量当前环境下Redis的基线性能,即一个系统在低压力、无干扰的情况下的基本性能。当你发现Redis运行时的延迟是基线性能的2倍以上时,你就可以判断Redis性能变慢了。1.延迟基准测量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来测量客户端到服务端的网络延迟。如果网络延迟几百毫秒,说明网络上可能有其他大流量程序在运行,造成网络拥塞,需要找运维协调网络流量分配。2、慢命令监视器如何判断是否是慢命令?看操作复杂度是不是O(N)。官方文档介绍了每条命令的复杂度,并尽可能使用O(1)和O(logN)的命令。集合操作的复杂度一般为O(N),比如全量集合查询HGETALL、SMEMBERS,集合聚合操作:SORT、LREM、SUNION等。是否有监控数据可观察?代码不是我写的,不知道有没有人用过慢指令。有两种排查方法:利用Redis慢日志功能查找慢命令;latency-monitor(延迟监控)工具。此外,您还可以使用自己(top、htop、prstat等)快速查看Redismaster进程的CPU消耗。没有高流量的高CPU使用率通常表示正在使用缓慢的命令。1)慢日志功能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。2)延迟监控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的数据读写都是单线程完成的。如果主线程运行时间过长,主线程就会被阻塞。下面我们一起来分析一下哪些操作会阻塞主线程,又该如何解决呢?3、网络通信造成的延迟客户端通过TCP/IP连接或者Unix域连接方式连接Redis。1Gbit/s网络的典型延迟约为200微秒。redis客户端执行一个命令分四个过程:发送命令->命令排队->命令执行->返回结果这个过程称为Roundtriptime(简称RTT,往返时间),mgetmset有效的节省了RTT,但是大多数Commands(比如hgetall,但不是mhgetall)不支持批量操作,需要消耗N倍的RTT。这时候就需要管道来解决这个问题。Redis管道将多个命令链接在一起以减少网络响应往返。redis-pipeline4。慢指令造成的延迟根据上面的慢指令监控查询文档,找到慢查询指令。可以通过以下两种方式解决:比如在Cluster集群中,在slave上运行O(N)的聚合操作等操作,或者在client上完成。请改用高效的命令。使用增量迭代避免一次查询大量数据。具体请参考SCAN、SSCAN、HSCAN、ZSCAN命令。除此之外,KEYS命令在生产中被禁用,它只适用于调试。因为它遍历了所有的键值对,所以操作延迟很高。5.fork产生RDB延迟产生RDB快照,Redis必须fork后台进程。fork操作(在主线程中运行)本身会导致延迟。Redis利用操作系统的多进程写时复制技术COW(CopyOnWrite)实现快照持久化,减少内存占用。Copy-on-write技术保证了快照期间数据可以被修改,但是fork涉及复制大量链接对象。一个24GB的大型Redis实例需要24GB/4kB*8=48MB的页表。这涉及在执行bgsave时分配和复制48MB的内存。另外RDB从库加载过程中不能提供读写服务,所以主库的数据大小控制在2~4G左右,这样可以快速完成从库加载。6、透明大页面(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/enabled7,swap:操作系统分页当物理内存(记忆棒)不够用时,部分内存上的数据交换到swap空间,这样系统就不会因为内存不足导致oom或者更致命的情况。当进程向OS申请内存,发现内存不足时,OS会将内存中暂时不用的数据换出,放到SWAP分区中。此过程称为换出。当进程再次需要数据时,OS发现还有空闲的物理内存,就会将SWAP分区中的数据交换回物理内存。这个过程称为换入。内存交换是操作系统中的一种机制,用于在内存和磁盘之间交换内存数据,涉及磁盘读写。哪些情况会触发掉期?对于Redis,有两种常见的情况:Redis使用的内存超过可用内存;与Redis运行在同一台机器上的其他进程在进行大量的文件读写I/O操作(包括产生大文件的RDB文件和AOF后台线程),文件读写占用内存,导致减少Redis获取内存,触发swap。如何排查是否由于swap导致性能下降?Linux提供了很好的工具来解决这个问题,所以当您怀疑由于交换导致延迟时,只需按照下面的故障排除步骤操作即可。1)获取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。不同的内存块换出到不同大小的磁盘。切中要害!如果Swapeverything为0kb或零星的4k,则一切正常。当出现几百MB甚至GB的swapsize时,说明此时Redis实例的内存压力非常大,很可能会变慢。2)解决方法是增加机器内存;在单独的机器上运行Redis,避免在同一台机器上运行需要大量内存的进程来满足Redis的内存需求;增加Cluster集群的数量以共享数据量并减少每个实例所需的内存量。8、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操作。9.expires剔除过期数据Redis有两种剔除过期数据的方式:惰性删除:当收到请求时发现key已经过期,则进行删除;定时删除:每100毫秒删除一些过期的键。定时删除算法如下:随机抽取A个CTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个key,删除所有过期key;如果发现超过25%的key已经过期,则执行步骤1。ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默认设置为20,每秒执行10次。删除200个key问题不大。如果触发了第二项,Redis会不断删除过期数据释放内存。删除是阻塞的。代码大哥,触发条件是什么?即大量按键设置相同的时间参数。同一秒内大量key过期,需要多次删除才能降低到25%以下。简而言之:大量密钥同时过期会导致性能波动。1)解决方案如果一批key确实同时过期,可以在EXPIREAT和EXPIRE的过期时间参数上加上一个一定大小范围内的随机数,保证key在附近的时间范围内被删除,也避免了同时呼气带来的压力。10.bigkey我们通常称一个包含大量数据或大量成员或列表的Key为bigkey。下面我们将通过几个实际例子来描述大key的特点:一个STRING类型的key,其值为5MB(数据太大)一个LIST类型的Key,有10000个列表(列表太多)一个ZSET类型的Keywith10,000members(toomanymembers)aHASHformat虽然它的成员数量只有1000,但是这些成员的总值是10MB(成员的大小太大)。Bigkey带来的问题如下:Redis内存不断增长导致OOM,或者达到maxmemory设置值导致写阻塞或者criticalkey被逐出;RedisCluster中某个节点的内存比其他节点大很多,但是由于RedisCluster中数据迁移的最小粒度是Key,所以节点上的内存无法均衡;bigkey的读请求占用带宽过多,变慢,影响服务器其他服务;删除一个bigkey会导致主库长时间阻塞导致同步中断或主从切换;1)找到bigkey,使用redis-rdb-tools工具找出bigkeykey。2)解决方案是拆分一个大的key,比如将一个有几万个成员的HASHKey拆分成多个HASHKey,并保证每个key的成员数在合理范围内。在RedisCluster结构中,大key的拆分对节点间的内存平衡可以起到很大的作用。大key的异步清理Redis从4.0开始提供了UNLINK命令,可以以非阻塞的方式慢慢地逐步清理传入的Key。通过UNLINK,你可以安全的删除大Key甚至超大Key。总结了以下检查清单,帮助您在Redis性能下降时高效解决问题:获取当前Redis的基线性能;开启慢命令监控,定位慢命令导致的问题;查找慢速命令并使用扫描;实例的数据大小控制在2-4GB,避免加载过大的RDB文件时阻塞主从复制;禁用大内存页并使用大内存页。RDB生成时,即使客户端修改的数据只有50B的数据,Redis也需要复制2MB的Hugepages。当写入的指令较多时,会造成大量的拷贝,导致性能变慢。Redis使用的内存是否过大导致swap;AOF配置是否合理,可以将配置项no-appendfsync-on-rewrite设置为yes,避免AOF重写和fsync争抢磁盘IO资源,导致Redis延迟增加。Bigkey会带来一系列的问题。我们需要拆分,防止bigkey出现,通过UNLINK异步删除。

猜你喜欢