Redis是非常高性能的内存型数据库,在我们整个分布式系统中扮演着非常重要的角色。从用户会话管理到任务调度管理,都离不开Redis服务。然而,高性能并不表示可以滥用,本文将分享一个实际案列,讲述工程师是如何错误使用Redis查询语句,导致Redis服务器CPU占用率达到100%。并且通过这个实际案例,同时给大家演示当遇到此类问题时,应该如何一步一步地排查并最终找到问题的根结和正确的解决方案。
Redis服务器CPU占用100%
由于我们多个功能节点都用了Redis服务器,因此当Redis服务器的CPU占用到达100%时,几乎所有的服务都非常吃力。其实只是发现Web网络服务非常慢,而且时常登陆的用户被登出。于是我们查看了日志文件发现,我们的Web服务器总是出现连接Redis服务器Timeout的情况。首先在排查网络连接没有问题的情况下,我们远程登陆Redis服务器,发现服务器CPU被Redis服务占用了100%。在这种情况下,我们断定是Redis出了问题。于是,下一步就是排查究竟是什么操作使Redis服务占用了CPU。
排查Redis服务CPU高占用问题
首先我们在Redis服务器上直接使用以下命令登陆Redis:
./redis-cli -h host -p port -a password
查看连接数
登陆Redis终端以后,首先检查当前连接数,可以使用以下命令:
info clients
输出以下结果:
# Clients
connected_clients:5265
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
当前有约5265个客户端连接。Redis服务是可以配置允许的最大连接数的,同时也可以在终端中输入以下命令查看:
CONFIG GET maxclients
输出以下结果:
1) “maxclients”
2) “10000”
显然连接数并没有超过最大连接数。另外我们可以通过以下命令显示当前连接主机列表:
CLIENT LIST
由于主机信息比较敏感,这里就不把结果贴出来了。有兴趣的朋友可以在自己的Redis服务器上试一试。另外,如果大家想把当前的连接记录全部导出到文件中的话,可以使用以下命令:
./redis-cli -h 127.0.0.1 CLIENT LIST > connection.log
查看Redis操作耗时
我们可以通过以下命令显示Redis服务器上所有操作指令的执行次数,总耗时和平均耗时:
INFO commandstats
输出以下结果:
cmdstat_pfadd:calls=2206706,usec=5800915,usec_per_call=2.63
cmdstat_scan:calls=507,usec=4291326,usec_per_call=8464.15
cmdstat_command:calls=5,usec=5341,usec_per_call=1068.20
cmdstat_keys:calls=3565447,usec=265030154274,usec_per_call=74332.94
cmdstat_srandmember:calls=739,usec=2481,usec_per_call=3.36
cmdstat_sadd:calls=5651056,usec=13508446,usec_per_call=2.39
cmdstat_del:calls=36,usec=930,usec_per_call=25.83
cmdstat_get:calls=6315046,usec=15701886,usec_per_call=2.49
cmdstat_client:calls=13,usec=16616,usec_per_call=1278.15
cmdstat_ttl:calls=4,usec=9,usec_per_call=2.25
cmdstat_set:calls=21518596,usec=69428738,usec_per_call=3.23
cmdstat_multi:calls=7829070,usec=2660577,usec_per_call=0.34
cmdstat_zcard:calls=8116459,usec=10563561,usec_per_call=1.30
cmdstat_info:calls=13805,usec=3474906,usec_per_call=251.71
cmdstat_expire:calls=26395235,usec=46668225,usec_per_call=1.77
cmdstat_scard:calls=4,usec=10,usec_per_call=2.50
cmdstat_incrby:calls=375705,usec=1810433,usec_per_call=4.82
cmdstat_exists:calls=565760,usec=878200,usec_per_call=1.55
cmdstat_spop:calls=29666,usec=303851,usec_per_call=10.24
cmdstat_zadd:calls=17442361,usec=200538702,usec_per_call=11.50
cmdstat_smembers:calls=4816880,usec=10635731,usec_per_call=2.21
cmdstat_zremrangebyrank:calls=2609627,usec=7831660,usec_per_call=3.00
cmdstat_zremrangebyscore:calls=379690939,usec=992702478,usec_per_call=2.61
cmdstat_unwatch:calls=9600313,usec=7707536,usec_per_call=0.80
cmdstat_sscan:calls=5,usec=250,usec_per_call=50.00
cmdstat_srem:calls=44371,usec=228398,usec_per_call=5.15
cmdstat_sismember:calls=982702,usec=1530387,usec_per_call=1.56
cmdstat_type:calls=5,usec=15,usec_per_call=3.00
cmdstat_host::calls=1,usec=34,usec_per_call=34.00
cmdstat_exec:calls=7829070,usec=69072098,usec_per_call=8.82
cmdstat_select:calls=17,usec=18,usec_per_call=1.06
cmdstat_pfcount:calls=247164,usec=384297,usec_per_call=1.55
cmdstat_watch:calls=9600324,usec=14632606,usec_per_call=1.52
cmdstat_zrange:calls=2609627,usec=6270043,usec_per_call=2.40
cmdstat_ping:calls=1486,usec=1621,usec_per_call=1.09
cmdstat_config:calls=2,usec=51,usec_per_call=25.50
做了简单的分析以后,我们可以很清晰的发现,总耗时和平均耗时最长的都是keys操作。一般来说,这种情况很可能是调用keys命令并使用通配符造成的。最终,我们在分布式自服务中发现,有一个工程师偷懒,写了大量的keys操作来判断指定key是否存在。对于这种情况,我们在Redis中维护了一个set用来保存需要跟踪的key。这样当我们需要判断指定key是否存在时,直接在set中查看即可,速度会比keys操作快很多。
Redis服务操作性能
以下是一些非常影响Redis服务器性能的操作指令:
- keys:键值枚举,可能会导致Redis服务器阻塞。
- smembes:当set中有大量数据时,会导致服务器阻塞。正确做法是先确认set对象中的数据量,取部分数据。
- zrange key 0 -1:当zset中有大量数据时,该操作有阻塞风险。
- hgetall:当Hash表中有大量数据,该操作有阻塞风险。
- lrange key 0 -1:当List列表中有大量数据,该操作有阻塞风险。

扫码联系船长