Redis变慢了吗?业务服务器到Redis服务器的调用链路变慢的原因可能是两台业务服务器和Redis服务器之间存在网络问题,比如网络丢包,延迟比较严重Redis有一个执行本身的问题。这个时候我们就需要排查Redis的问题,但是大多数情况下是Redis服务的问题。但是如何衡量Redis的慢度呢?命令执行时间大于1s,大于2s?其实并没有固定的标准。例如,在高配置的服务器中,Redis被认为慢了0.5毫秒,而在低配置的服务器中,Redis被认为慢了3毫秒。所以我们想在自己的机器上做一个benchmark测试,看看正常情况下Redis处理命令需要多长时间?我们可以使用下面的命令来监控和统计测试过程中的最大延迟(以微秒为单位)redis-cli--latency-h`host`-p`port`比如执行下面的命令[root@VM-0-14-centossrc]#./redis-cli-h127.0.0.1-p6379--intrinsic-latency60到目前为止的最大延迟:1微秒。到目前为止的最大延迟:12微秒。到目前为止的最大延迟:55微秒。最大延迟到目前为止:124微秒。到目前为止的最大延迟:133微秒。到目前为止的最大延迟:142微秒。到目前为止的最大延迟2:98微秒。到目前为止的最大延迟:1049微秒。到目前为止的最大延迟:2366微秒。最大延迟所以远:3725微秒。52881684次总运行(平均延迟:每次运行1.1346微秒/1134.61纳秒)。最差运行时间比平均延迟60长3283倍。参数是测试执行的秒数。可以看到最大延迟为3725微秒(约3毫秒)。如果命令的执行时间远远超过3毫秒,此时Redis可能会很慢!那么Redis有哪些慢操作呢?Redis运行慢的原因有哪些?Redis的各种命令在一个线程中依次执行。如果一个命令在Redis中执行的时间过长,会影响整体的性能,因为后面的请求要等到前面的请求处理完之后才能处理。这些耗时的操作包括以下几个部分。Redis可以通过日志记录那些耗时的命令,使用如下配置。#命令执行时间超过5毫秒,记录慢日志CONFIGSETslowlog-log-slower-than5000#只保留最新的500条慢日志CONFIGSETslowlog-max-len500执行以下命令查询最新的慢日志127.0.0.1:6379>SLOWLOGget51)1)(integer)32693#慢日志ID2)(integer)1593763337#执行时间戳3)(integer)5299#执行时间(微秒)4)1)"LRANGE"#具体执行命令和参数2)"user_list:2000"3)"0"4)"-1"2)1)(integer)326922)(integer)15937633373)(integer)50444)1)"GET"2)"user_info:1000"...在上一篇文章中,我们有介绍了Redis的底层数据结构,它们的时间复杂度如下表所示名称时间复杂度dict(字典)O(1)ziplist(压缩列表)O(n)zskiplist(跳过列表)O(logN)quicklist(quicklist)O(n)intset(整数集合)O(n)“单元素操作”:对集合中的元素进行增删改查都与底层数据结构相关,如增删改查的时间复杂度,调整查字典是O(1),增删改查跳表的时间复杂度是O(logN)「范围操作」:遍历集合,比如Hash类型的HGETALL,Set类型的SMEMBERS,List类型LRANGE,ZSet类型ZRANGE,时间复杂度为O(n),避免使用,改用SCAN系列命令(hscan用于hash,sscan用于set,zscan用于zset)“聚合操作”:这类操作的时间复杂度通常大于O(n),如SORT、SUNION、ZUNIONSTORE“统计”operation”:当你想获取集合中的元素时,对数进行计数,比如LLEN或者SCARD,时间复杂度是O(1),因为它们的底层数据结构如quicklist、dict、intset保存的是元素个数.保存了链表的头尾节点,所以操作链表头尾节点的时间复杂度是O(1),比如LPOP,RPOP,LPUSH,RPUSH”当你想得到key在Redis中,避免使用keys*",Redis中存储的键值对存储在字典中(类似于Java中的HashMap,也是数组+链表实现的),key的类型为字符串,类型为ofvalue可以是string、set、list等,比如我们执行下面的命令时,redis的字典结构是这样的:setbookNameredis;rpushfruitsbananaapple;我们可以使用keys命令查询Redis中的特定key,如下所示#查询所有keykeys*#用book查询前缀keykeysbook*keys命令的复杂度为O(n),会遍历这个dict中的所有keys,如果Redis中存储了很多key,所有读写Redis的指令都会被延迟,所以不要在生产环境中不要使用这个命令(如果你准备离开,祝你玩得开心)。《既然不让用keys,那肯定有替代品,那就是scan》scan是通过游标一步步遍历,所以不会长时间阻塞redis》使用zscan遍历zset,hscan遍历hash、sscan遍历set和scan的原理命令是类似的,因为hash、set、zset底层数据结构中都有dict。操作bigkey“如果一个key对应的value很大,那么这个key就叫做bigkey。写bigkey的时候需要分配内存,需要的时间比较长。同样,删除bigkey释放内存的时间也比较长。”如果在slowlog中发现SET/DEL等复杂度不高的命令,则应排查是否是由于写了Enteringthebigkey导致的。“如何定位bigkey?”Redis提供了扫描bigkey的命令$redis-cli-h127.0.0.1-p6379--bigkeys-i0.01...------------summary------采样了829675个keysinthe键空间!以字节为单位的总密钥长度为10059825(平均长度12.13)找到的最大字符串“key:291880”有10个字节找到的最大列表“mylist:004”有40个项目找到的最大集合“myset:2386”找到的最大38哈希值“myhash:3574”'有37个字段找到的最大zset'myzset:2704'有42个成员36313个字符串,有363130个字节(04.38%的键,平均大小10.00)787393个列表,有896540个项目(94.90%的9个1keys,大小为4)有40052个成员的集合(00.24%)ofkeys,avgsize20.09)1990hasheswith39632fields(00.24%ofkeys,avgsize19.92)1985zsetswith39750members(00.24%ofkeys,avgsize20.03)你可以看到命令的输入有以下三个parts:key在内存中的个数,总占用内存,每个key平均占用内存,每种类型最大占用内存,名称o各数据类型所占比例f键,以及此命令的平均大小。原理是redis内部执行scan命令,遍历实例中的所有key,然后针对key的类型执行strlen,llen,hlen,scard,zcard命令获取string类型的长度,容器type(list,hash,set,zset)使用这个的元素的数量该命令需要注意以下两个问题。在对在线实例进行bigkey扫描时,为了避免ops(每秒操作数)突然增加,可以通过-i添加一个sleep参数,即每100个scan命令会sleep0.01s。对于容器类型(list、hash、set、zset)来说,扫描到的key是元素最多的key,但是一个key中元素多并不一定代表它占用的内存多。性能问题?”尽量避免写入bigkey。如果你使用的是redis4.0以上版本,可以使用unlink命令代替del。该命令可以释放关键内存操作并在后台线程中执行。如果你使用的是redis6。对于0以上的版本,可以开启免懒机制(lazyfree-lazy-user-delyes)。执行del命令时,也会放在后台线程执行大量的key过期。我们可以给Redis中的key设置过期时间,那么当key过期了,什么时候删除呢?《如果让我们写Redis的过期策略,我们会想到以下三种方案》定时删除,同时设置key的过期时间,创建一个定时器,当key的过期时间到来时,立即执行删除对key进行操作,惰性删除。每次获取到key都会判断key是否过期。如果过期,密钥将被删除。过一段时间再检查一次keys,把里面过期的keys删除。定时删除策略对CPU不友好。当过期key较多时,使用Redis线程删除过期key,会影响正常请求的响应。定时删除策略对CPU不利。不友好,当过期key较多时,使用Redis线程删除过期key,会影响正常请求的响应。懒惰删除读CPU比较好,但是会浪费很多内存。如果一个key设置了过期时间,放到了内存中,但是没有被访问到,那么它就会一直存在于内存中。周期性删除策略对CPU和内存更友好。redisexpiredkey的删除策略选择了以下两种惰性删除和周期性删除。“惰性删除”客户端访问key时,会检查key的过期时间,过期则立即删除。该字典用于删除过期的键。遍历策略如下:每秒执行10次过期扫描。每次从过期字典中随机选取20个键,删除这20个键中的过期键。如果过期键的比例超过1/4,则继续执行步骤1。每次扫描时间上限默认不超过25ms,避免线程卡死。“因为Redis中的过期key是主线程删除的,为了不阻塞用户的请求,删除过期key的时间是小次数。”。源码可以参考expire.c中的activeExpireCycle方法。为了防止主线程一直删除key,我们可以采用以下两种方案,给同时过期的key加上一个随机数,打散过期时间,减轻清除key的压力。如果使用redis4.0以上的redis版本可以开启lazy-free机制(lazyfree-lazy-expireyes)。删除过期key时,释放内存的操作放在后台线程执行内存限制,触发淘汰策略。Redis是一个内存数据库,当Redis使用的内存超过物理内存的限制时,内存数据会频繁的与磁盘进行交换,交换会导致Redis性能急剧下降。所以在生产环境中我们通过配置参数maxmemoey来限制使用的内存大小。当实际使用的内存超过maxmemoey时,Redis提供了以下可选策略。“Redis的淘汰策略也是在主线程中执行的。但是内存超过Redis的上限后,每次写入都需要剔除一些key,导致请求时间变长。”可以通过增加如下方法提高内存,或者将数据放在多个实例中。淘汰策略改为随机淘汰,一般来说,随机淘汰相比lru要快很多,避免存储bigkeys,减少释放内存的耗时。写AOF日志的方式总是Redis的持久化机制有RDB快照而AOF日志磁盘机制》当aof的磁盘刷新机制是always时,redis每处理一次写命令,都会将写命令刷新到磁盘中再返回。整个过程都在Redis的主线程中进行,这必然会拖慢redis的性能。”Aof的磁盘刷新机制是everysec。Redis在写完内存后返回。磁盘刷新操作是在后台线程中执行的。作为aof的刷盘机制,后台线程每1秒将内存中的数据刷到磁盘中。如果没有,宕机后可能会丢失一些数据,所以一般不用。”一般情况下,aof刷盘机制可以配置为everysec。”forktakestoolong让子进程执行的方式,主线程内存越大,阻塞时间越长。》可以通过以下方式优化控制Redis实例的内存大小,尽量控制在10g以内,因为内存越大阻塞时间越长。配置合理的持久化策略,比如在上生成RDB快照slave节点,当机器内存不足时使用swap分区,这时操作系统会将内存中的部分数据替换到磁盘上,这个磁盘区域就是Swap分区,当应用程序访问数据时再次,需要从磁盘读取,导致性能严重下降。”当Redis性能急剧下降时,可能是数据已经交换到Swap分区了。如何查看Redis数据是否已经交换到Swap分区?"#首先找到redis-server进程idps-ef|grepredis-server#查看redisswap的使用情况cat/proc/$pid/smaps|egrep'^(Swap|Size)'[root@VM-0-14-centos~]#cat/proc/2370/smaps|egrep'^(Swap|Size)'Size:1568kBSwap:0kBSize:8kBSwap:0kBSize:24kBSwap:0kBSize:2200kBSwap:0kB的大小每一行表示Redis使用的一块内存的大小,Size下面的Swap表示内存的大小,已经用了多少就换到了磁盘。如果两个值相等,则说明就是这块内存中的数据已经改到磁盘上了,我们可以通过增加机器内存,按照下面的方式对内存进行碎片整理来解决这个问题。最后总结一下Redis运行缓慢的问题。
