redis性能优化——⽣产中实际遇到的问题排查总结⽬录
背景
redis-K,V数据库,因其⾼性能的操作性和⽀持丰富的数据结构,⽬前⼤量被⽤于衔接应⽤层和关系数据库中间的缓存层。随着使⽤的场景越来越多,和数据量快速的递增,在⽣产环境中经常会遇到相关的性能瓶颈问题。这时候就需要借助⼀些外部的⼿段来分析瓶颈根源在哪,对症下药提升性能。
常见性能问题及问题分析过程
1、⽣产系统刚开始运⾏阶段,系统稳定。但是运⾏⼀段时间后,发现部分时间段系统接⼝响应变慢。查看客户端⽇志经常会出现这样的错误:
redis.ptions.JedisConnectionException: java.SocketTimeoutException: Read timed out。
2、⽣产环境长时间的运⾏后,经常会有接⼝返回数据失败的情况,或者是从监控上发现数据库压⼒某⼀时间暴增。查看客户端⽇志发现这样的错误:
redis.ptions.JedisConnectionException: Could not get a resource from the pool
3、突然间服务不能访问,返回错误:
redis.ptions.JedisDataException: MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
当然在实际⽣产情况中,还有各种各样的异常情况,但是在客户端普遍表现为上⾯⼏种场景,下⾯我们来⼀步步分析上⾯的问题。
问题⼀:
⾸先从客户端反馈的⽇志,怀疑是服务器和客户端间的⽹络问题。为排除这个问题,我们编写脚本,在客户端定时ping服务端(redis服务),持续运⾏⼀段后,发现未有丢包的情况,排除⽹络问题。
查看redis服务端⽇志,未发现有异常情况。查看redis服务器资源监控,发现每⼏分钟左右,IO会有⼀波峰值,但是CPU和带宽压⼒都在正常范围。
这⾥介绍下我们的redis部署模式:⼀主⼀从通过redis⾃带的sentinal做HA,主从均有开启持久化。初步怀疑间隔性IO操作占⽤资源导致redis读写变慢(在此,抛出⼀个问题:在服务器资源CPU和带宽均未达到瓶颈的情况下,持续的IO⾼峰操作是否会影响物理内存的读写)。接下来采取的措施是:关闭主
库的持久化,⽤从库来做持久化,但是这种模式下存在⼀个问题,如果主发⽣故障,sentinal做主从切换后问题同样存在,⼤家有更好的建议可以指点下。
运⾏⼀段时候,发现问题有所改善,但是依然还是会有time out的情况,只有继续排查问题。由于redis操作采⽤单线程,考虑会不会有某些慢查询导致time out。执⾏slowlog查看慢查询语句,发现有⼤量的keys命令操作,keys命令在⼤量并发情况下性能⾮常差,结合官⽅给出的warning
正式环境中,尽量避免使⽤keys,接下来出使⽤keys的代码做优化,⾄此,time out问题解决。
问题⼆:
从错误⽇志看,是提⽰⽆法获取连接。有两种情况:redis支持的数据结构
1、客户端的连接池满了,⽆法创建新的连接
检查客户端连接最⼤限制maxActive是否⾜够
2、redis服务端连接溢出,⽆法分配新的连接
检查服务端tcp连接:netstat -nat|grep -i "6379"|wc -l
检查服务端连接是否达到最⼤值:查看服务端⽀持的最⼤连接:CONFIG GET maxclients,查看当前服务端建⽴的  连接:connected_clients
通过上述检查后,发现redis服务端connected_clients连接数持续过⾼,经常在最⼤值徘徊。但是结合客户端配置的最⼤连接配置maxActive,计算出所有客户端连接占满的情况下最⼤的连接数也达不到connected_clients的连接数。
执⾏client list命令,发现⼤量的client的idle时间特别长:
正常的client连接,在持续使⽤的情况下,是不可能空闲这么长时间,连接长时间空闲,客户端也会关闭连接。
查看redis服务端下⾯两项配置:
timeout:client连接空闲多久会被关闭(这个配置容易被误导为:连接超时和操作执⾏超时)
tcp-keepalive:redis服务端主动向空闲的客户端发起ack请求,以判断连接是否有效
检查上述配置发现 timeout和tcp-keepalive均未启⽤(均为0),这种情况下,redis服务端没有有效的机制来确保服务端已经建⽴的连接是否已经失效。当服务器和客户端⽹络出现闪断,导致tcp连接中断,这种情况下的client将会⼀直被redis服务端所持有,就会出现上⾯我们看到的idle时间特长的client连接。
接下来设置timeout和tcp-keepalive来清理失效的连接。
上⾯问题中提到的数据库某⼀时间压⼒暴增,是由于在缓存模式下,redis请求失败,请求的压⼒瞬间集中到数据库。
问题三:
从错误提⽰,可以看出是向磁盘保存数据失败。引起这个问题的原因⼀般是内存不⾜,但是⽣产环境我们⼀般都会为系统分配⾜够的内存运⾏,⽽且查看内存情况也显⽰还有可⽤内存。
查看redis⽇志,发现有这个错误:Can’t save in background: fork: Cannot allocate memory
redis在保存内存的数据到磁盘时,为了防⽌主进程假死,会Fork⼀个⼦进程来完成这个保存操作。但是这个Fork的⼦进程会需要分配和主进程相同的内存,这时候就相当于需要的内存double了,如果这时候可⽤内存不⾜以分配需要的内存,将会导致Fock⼦进程失败⽽⽆法保存数据到磁盘。
修改linux内核参数:vm.overcommit_memory=1。⾄此,问题解决。
overcommit_memory有三种取值:0, 1, 2
0::检查是否有⾜够的可⽤内存供进程使⽤;有则允许申请,否则,内存申请失败,并把错误返回给应⽤进程;
1:表⽰内核允许分配所有的物理内存,⽽不管当前的内存状态如何;
2:表⽰内核允许分配超过所有物理内存和交换空间总和的内存。
优化措施总结
1、结合实际使⽤场景,考虑是否需要⽤到redis的持久化,如果单纯⽤来做应⽤层的缓存(在缓存未命中的情况下访问数据库),可以关闭持久化。
2、缓存模式下,尽量为每块缓存设置时效性,避免冷数据长时间占⽤资源。
3、⽣产环境中尽量避免使⽤keys操作,由于redis是单线程模式,⼤量的keys操作会阻塞其他的命令执⾏。
4、设置合理的内存回收策略,保证内存可⽤性的同时能适当的提供缓存的命中率。
5、提前计算出系统可能会⽤的内存⼤⼩,合理的分配内存。需要注意在开启持久化模式下,需要预留更多的内存提供给Fock的⼦进程做数据磁盘flush操作。
深⼊探讨研究
如果redis服务端未设置timeout,客户端会如何处理长时间未使⽤的连接?
这个问题可以从分析redis的sdk源码查答案,不过这个过程会⽐较枯燥。
接下来我们直接通过抓取客户端和服务端的tcp数据包来获取答案:
这⾥我⽤wireshark来抓取中间的tcp数据包,下⾯是抓取了⼀个完整的redis连接(从发起到结束)的tcp数据包
从上⾯可以看到,从tcp3次握⼿建⽴连接,到最后客户端发送reset包给服务端终⽌了这个连接。
追踪整个tcp的数据流:
*2
$4
AUTH
$8
password
+OK
*1
$4
PING
+PONG
*1
$4
PING
+PONG
*1
$4
QUIT
+OK
从tcp数据流可以看出,整个tcp连接中间经历的操作:
1、客户端发送密码建⽴连接,服务端响应OK
2、客户端发送PING命令校验连接,服务端响应PONG表⽰成功
3、客户端再次发送PING命令校验连接,服务端响应PONG表⽰成功
4、客户端发送QUIT命令退出连接,服务端响应OK表⽰退出成功
当服务端响应QUIT命令OK后,客户端发送RESET的tcp包终⽌整个tcp连接。中间客户端发起了两次PING命令校验连接和⼀次QUIT命令来退出连接,每次间隔30s,加起来整个连接存活了90s。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。