前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis超时原因系统性排查

redis超时原因系统性排查

作者头像
BGBiao
发布2018-02-26 11:52:50
7.9K0
发布2018-02-26 11:52:50
举报
文章被收录于专栏:容器云生态容器云生态

1.计算延迟时间: 使用–latency参数  以下参数表示平均超时时间0.03ms。

代码语言:javascript
复制
redis-cli --latency -h 127.0.0.1 -p 6800
min: 0, max: 4, avg: 0.03 (12235 samples)

注意:由于使用的是本机的回环地址,所以这样其实忽略了带宽上的延迟 使用redis内部的延迟检测子系统测试:见上一篇文章中“启用延迟监控系统“部分。

2.延迟标准: 使用–intrinsic-latency参数  需要运行在redis server本身,用来检测redis本身是否有延迟,在这种模式下,redis-cli不会连接redis server,它将衡量运行redis-cli进程本身最大的延迟时间。即redis的内部延迟

代码语言:javascript
复制
./redis-cli --intrinsic-latency 100
Max latency so far: 4 microseconds.
Max latency so far: 7 microseconds.
Max latency so far: 13 microseconds.
Max latency so far: 28 microseconds.
Max latency so far: 30 microseconds.
Max latency so far: 39 microseconds.

32863264 total runs (avg 3 microseconds per run).
Worst run took 13.00x times the avarege.

注意:后面的参数100表示100s 由测试结果可以看出来,redis内部延迟仅为39微秒(0.039毫秒),这会是一个比较好的消息,因为内部延迟不超过100微秒性能都是相当好的(生产环境中,数据量比较大的时候内部延迟超过100也是很正常的,不会对用户的读写性能造成影响)不过需要注意的是,内部延迟严重依赖于cpu的load,如果你的系统有其他应用在共享cpu,那么对不起,你的内部延迟一定很大。

不信?你可以尝试在一台耗cpu的机器上跑redis并进行测试。

3.由网络和通信造成的延迟: 当用户连接到Redis通过TCP/IP连接或Unix域连接,千兆网络(1Gbit/s)的典型延迟大概200us,而Unix域socket可能低到30us。这完全基于你的网络和系统硬件。在通信本身之上,系统增加了更多的延迟(线程调度,CPU缓存,NUMA替换等等)。系统引起的延迟在虚拟机环境远远高于在物理机器环境。

1.实际情况是即使Redis处理大多数命令在微秒之下,客户机和服务器之间的交互也必然消耗系统相关的延迟。  2.一个高效的客户机因而试图通过捆绑多个命令在一起的方式减少交互的次数。服务器和大多数客户机支持这种方式。聚合命令象MSET/MGET也可以用作这个目的。

在这里会有几个比较好的建议:

*如果你负担的起,尽可能的使用物理机而不是虚拟机来做服务器 *不要经常的connect/disconnect与服务器的连接(尤其是对基于web的应用),尽可能的延长与服务器连接的时间。 *如果你的客户端和服务器在同一台主机上,则使用Unix域套接字 *尽量使用聚合命令(MSET/MGET)或可变参数命令而不是pipelining *如果可以尽量使用pipelining而不是序列的往返命令。 *针对不适合使用原始pipelining的情况,如某个命令的结果是后续命令的输入,在以后的版本中redis提供了对服务器端的lua脚本的支持,实验分支版本现在已经可以使用了。

注意:在Linux上,你可以通过process placement(taskset)、cgroups、real-time priorities(chrt)、NUMA配置(numactl)或使用低延迟内核的方式来获取较低的延迟。请注意Redis 并不适合被绑到单个CPU核上。redis会在后台创建一些非常消耗CPU的进程,如bgsave和AOF重写,这些任务是绝对不能和主事件循环进程放在一个CPU核上的。然而,大多数情况下上述的优化方法是不需要的,除非你确实需要并且你对优化方法很熟悉的情况下再使用上述方法。

4.redis的单线程天性:

代码语言:javascript
复制
    redis使用单线程设计的,这就意味着单个进程需要使用一种多路复用的技术来服务所有的客户端请求。意思就是在某一时刻,redis可以服务于一个请求,然后其他的请求会被频繁的处理。这个和node.js工作模式很相似,然而,redis和node的处理机制都不会让人感知到很慢。
    这是因为,短时间内redis就会完成单个请求,但是最重要的是这些产品被设计成系统调用时不能阻塞,比如一些在socket中读数据或者写数据操作。

5.慢命令造成的延迟: 单线程的一个结果是,当一个请求执行得很慢,其他的客户端调用就必须等待这个请求执行完毕。当执行GET、SET或者 LPUSH 命令的时候这不是个问题,因为这些操作可在很短的常数时间内完成。然而,对于多个元素的操作,像SORT, LREM, SUNION 这些,做两个大数据集的交叉要花掉很长的时间。

官方文档(http://redis.io/commands)提到了所有操作的算法复杂性。 在使用一个你不熟悉的命令之前系统的检查它会是一个好办法。  如果你对延迟有要求,那么就不要执行涉及多个元素的慢操作,你可以使用Redis的replication功能,把这类慢操作全都放到replica上执行。  此外,你可以用你喜欢的进程监控程序(top, htop, prstat, 等…)来快速查看Redis进程的CPU使用率。如果traffic不高而CPU占用很高,八成说明有慢操作。

重要提示:  一般来说,一个普遍的延迟原因都是使用了慢命令查询,比如使用keys等命令(生产环境慎用),自从redis2.8之后,系统自带一些命令可以进行key的迭代,比如scan,sscan,hscan和zscan等

6.由fork产生的延迟: Redis不论是为了在后台生成一个RDB文件,还是为了当AOF持久化方案被开启时重写Append Only文件,都会在后台fork出一个进程这是redis唯一一个生成的子进程

代码语言:javascript
复制
    fork操作(在主线程中被执行)本身会引发延迟。在大多数的类unix操作系统中,fork是一个很消耗的操作,因为它牵涉到复制很多与进程相关的对象。而这对于分页表与虚拟内存机制关联的系统尤为明显。对于运行在一个linux/AMD64系统上的实例来说,内存会按照每页4KB的大小分页。为了实现虚拟地址到物理地址的转换,每一个进程将会存储一个分页表(树状形式表现),分页表将至少包含一个指向该进程地址空间的指针。所以一个空间大小为24GB的redis实例,需要的分页表大小为 24GB/4KB*8 = 48MB。
    当一个后台的save命令执行时,实例会启动新的线程去申请和拷贝48MB的内存空间。这将消耗一些时间和CPU资源,尤其是在虚拟机上申请和初始化大块内存空间时,消耗更加明显

在不同系统中的Fork时间:

代码语言:javascript
复制
下面的列表比较了不同Redis实例的fork时间。数据包含正在执行的BGSAVE,并通过INFO指令查看thelatest_fork_usecfiled。
Linux beefy VM on VMware 6.0GB RSS forked 77 微秒 (每GB 12.8 微秒 ).
Linux running on physical machine (Unknown HW) 6.1GB RSS forked 80 微秒(每GB 13.1微秒)
Linux running on physical machine (Xeon @ 2.27Ghz) 6.9GB RSS forked into 62 微秒 (每GB 9 微秒).
Linux VM on 6sync (KVM) 360 MB RSS forked in 8.2 微秒 (每GB 23.3 微秒).
Linux VM on EC2 (Xen) 6.1GB RSS forked in 1460 微秒 (每GB 239.3 微秒).
Linux VM on Linode (Xen) 0.9GBRSS forked into 382 微秒 (每GB 424 微秒).

7.透明大页(transport huge pages)引起的延迟:

代码语言:javascript
复制
透明大页,默认是always,启用是为了可以管理更多的内存地址空间。可以使用never关闭,之后会使用系统软件的算法管理内存映射。

通常linux会将透明大页开启,在fork被调用后,redis产生的延迟被用来持久化到磁盘。内存大页会引起一下问题:  1.fork被调用,共享内存大页的两个进程被创建  2.在一个系统比较活跃的实例里,部分循环时间运行需要几千个内存页,期间引起的copy-on-write 会消耗几乎所有的内存  3.这个将导致更大的延迟和使用更多的内存  因此,redis官方建议需要确保禁掉内存大页:  echo never > /sys/kernel/mm/transparent_hugepage/enabled

8.swapping (操作系统分页)引起的延迟:

代码语言:javascript
复制
Linux (以及其他一些操作系统) 可以把内存页存储在硬盘上,反之也能将存储在硬盘上的内存页再加载进内存,这种机制使得内存能够得到更有效的利用。

如果内存页被系统移到了swap文件里,而这个内存页中的数据恰好又被redis用到了(例如要访问某个存储在内存页中的key),系统就会暂停redis进程直到把需要的页数据重新加载进内存。这个操作因为牵涉到随机I/O,所以很慢,会导致无法预料的延迟。

系统之所以要在内存和硬盘之间置换redis页数据主要因为以下三个原因:
代码语言:javascript
复制
    系统总是要应对内存不足的压力,因为每个运行的进程都想申请更多的物理内存,而这些申请的内存的数量往往超过了实际拥有的内存。简单来说就是redis使用的内存总是比可用的内存数量更多。
    redis实例的数据,或者部分数据可能就不会被客户端访问,所以系统可以把这部分闲置的数据置换到硬盘上。需要把所有数据都保存在内存中的情况是非常罕见的。
    一些进程会产生大量的读写I/O。因为文件通常都有缓存,这往往会导致文件缓存不断增加,然后产生交换(swap)。请注意,redis RDB和AOF后台线程都会产生大量文件。

所幸Linux提供了很好的工具来诊断这个问题,所以当延迟疑似是swap引起的,最简单的办法就是使用Linux提供的工具去确诊。

代码语言:javascript
复制
首先查到redis进城的pid
cd /proc/5454
在这里你会发现一个名为smaps 的文件,它描述了redis进程的内存布局 (假定你使用的是Linux 2.6.16或者更新的版本)。这个文件包括了很多进程所使用内存的细节信息,其中有一项叫做Swap的正是我们所关心的。不过仅看这一项是不够的,因为smaps文件包括有redis进程的多个不同的的内存映射区域的使用情况(进程的内存布局远不是线性排列那么简单)。

从我们对所有进程的内存交换情况感兴趣以来,我们首先要做的事情是使用grep命令显示进程的smaps文件
$ cat smaps | grep 'Swap:'
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 12 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 4 kB
假如所有的数据显示为0kb或者某些数据偶尔显示为4kb,表示当前一切正常。实际上我们的例子是一个真实的运行着Redis并每秒为数百的用户提供服务的网站,会显示更多的交换页。为了研究是否存在一个严重的问题,我们改变命令打印出分配的内存尺寸

$ cat smaps | egrep '^(Swap|Size)'
Size: 316 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 8 kB
Swap: 0 kB
Size: 40 kB
Swap: 0 kB
Size: 132 kB
Swap: 0 kB
Size: 720896 kB
Swap: 12 kB
Size: 4096 kB
Swap: 156 kB

在输出信息中,你能看到有一个720896kb的内存分配(有12kb的交换)还有一个156kb的交换是另一个进程的。基本上我们的内存只会有很小的内存交换,因此不会产生任何的问题

假如进程的内存有相当部分花在了swap上,那么你的延迟可能就与swap有关。假如redis出现这种情况那么可以用 vmstat 命令来验证一下猜测:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 3980 697932 147180 1406456 0 0 2 2 2 0 4 4 91 0
0 0 3980 697428 147180 1406580 0 0 0 0 19088 16104 9 6 84 0
0 0 3980 697296 147180 1406616 0 0 0 28 18936 16193 7 6 87 0
0 0 3980 697048 147180 1406640 0 0 0 0 18613 15987 6 6 88 0
2 0 3980 696924 147180 1406656 0 0 0 0 18744 16299 6 5 88 0
0 0 3980 697048 147180 1406688 0 0 0 4 18520 15974 6 6 88 0

输出中我们最感兴趣的两行是si 和 so,这两行分别统计了从swap文件恢复到内存的数量和swap到文件的内存数量。如果在这两行发现了非0值那么就说明系统正在进行swap。

最后,可以用iostat命令来查看系统的全局I/O行为。
$ iostat -xk 1
avg-cpu: %user %nice %system %iowait %steal %idle
13.55 0.04 2.92 0.53 0.00 82.95

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
sda 0.77 0.00 0.01 0.00 0.40 0.00 73.65 0.00 3.62 2.58 0.00
sdb 1.27 4.75 0.82 3.54 38.00 32.32 32.19 0.11 24.80 4.24 1.85
如果确认延迟是由于swap引起的,那么就需要减小系统的内存压力,要么给机器增加内存,要么不要在同一个机器上运行其他消耗内存的程序。

9.AOF和磁盘I/O造成的延迟:

代码语言:javascript
复制
    另一个延迟的根源是Redis的AOF(仅附加文件)模式。AOF基本上是通过两个系统间的调用来完成工作的。 一个是写,用来写数据到AOF, 另外一个是文件数据同步,通过清除硬盘上空核心文件的缓冲来保证用户指定的持久级别。

包括写和文件数据同步的调用都可以导致延迟的根源。 写实例可以阻塞系统范围的同步操作,也可以阻塞当输出的缓冲区满并且内核需要清空到硬盘来接受新的写入的操作。  文件数据同步对于延迟的影响非常大,因为它涉及到好几步调用,可能要花掉几毫秒以致几秒的时间,特别是在还有其他进程后也在占用I/O的情况下。

我们来看看当使用AOF的时候如何配置来降低延迟: 通过设置AOF相关的appendfsync项,可以使用三种不同的方式来执行文件同步(也可以在运行时使用config set 命令来修改这个配置)

代码语言:javascript
复制
     **appendfsync 的值设置为no,redis不执行fsync。这种情况下造成延迟的唯一原因就是写操作。这种延迟没有办法可以解决,因为redis接收到数据的速度是不可控的,不过这种情况也不常见,除非有其他的进程占用I/O使得硬盘速度突然下降。
     **appendfsync 的值设置为everysec,每秒都会执行fsync。fsync 由一个单独线程执行,如果需要写操作的时候有fsync正在执行redis就会用一个buffer来延迟写入2秒(因为在Linux如果一个fsync 正在运行那么对该文件的写操作就会被堵塞)。如果fsync 耗时过长(译者注:超过了2秒),即使fsync 还在进行redis也会执行写操作,这就会造成延迟。
     **appendfsync 的值设置为always ,fsync 会在每次写操作返回成功代码之前执行(事实上redis会积累多个命令在一次fsync 过程中执行)。这种模式下的性能表现是非常差劲的,所以最好使用一个快速的磁盘和文件系统以加快fsync 的执行。

大多数redis用户都会把这个值设成 no 或者 everysec。要减少延迟,最好避免在同一个机器上有其他耗费I/O的程序。 经验中发现其实可以把主的持久化设置rdb,从设置成aof,或者aof直接关闭。因为aof会将所以的写操作进行记录。当然用SSD也有益于降低延迟,不过即使不使用SSD,如果能有冗余的硬盘专用于AOF也会减少寻址时间,从而降低延迟。 如果你想诊断AOF相关的延迟原因可以使用strace 命令:

代码语言:javascript
复制
strace -p $(pidof redis-server) -T -e trace=fdatasync,write

上面的命令会展示redis主线程里所有的fdatasync系统调用。不包括后台线程执行的fdatasync 调用。不过因为write也会向客户端写数据,所以用上面的命令很可能会获得许多与磁盘I/O没有关系的结果。似乎没有办法让strace 只显示慢系统调用,所以要用下面的命令:

代码语言:javascript
复制
strace -f -p $(pidof redis-server) -T -e trace=fdatasync,write 2>&1 | grep -v '0.0' | grep -v unfinished

10.数据过期造成的延迟:

redis有两种方式来驱逐过期的key: lazy方式,在key被请求的时候才检查是否过期。 active方式,每0.1秒进行一次过期检查。

active过期模式是自适应的,每过100毫秒开始一次过期检查(每秒10次),每次作如下操作:

  • **根据 REDIS_EXPIRELOOKUPS_PER_CRON 的值删除已经过期的key(是指如果过期的key数量超过了REDIS_EXPIRELOOKUPS_PER_CRON 的值才会启动过期操作,太少就不必了。这个值默认为10)
  • **假如超过25%(是指REDIS_EXPIRELOOKUPS_PER_CRON这个值的25%)的key已经过期,则重复一遍检查失效的过程。
从上面的驱逐策略上看,通常在actively模式下1秒能处理100个key。在过期的key有一段时间没被访问的情况下这个清理速度已经足够了,所以 lazy模式基本上没什么用。1秒只过期100个key也不会对redis造成多大的影响。

11.常用的检查命令:

代码语言:javascript
复制
1.查看前num条慢命令,这个所谓的慢是可以通过参数slowlog-log-slower-than设定的,默认10000us,也就是10ms
>slowlog get num 
2.查看当前实例中哪些key比较占空间(redis-cli的参数)
#redis-cli --bigkeys
3.查看redis相关的监控信息
>info 相关命令(memory cpu replication stats clients)
注意:客户端连接数默认限制为10000,生产测试发现超过5000就会影响;total_commands_processed、instantaneous_ops_per_sec、net_io_in_per_sec、net_io_out_per_sec、total_commands、connected_clients、used_memory_human、used_memory_peak_human这些指标都需要关注下;
4.测试qps
>redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000 -d 2
50个并发链接,10000个请求,每个请求2kb。
写在最后:

维护生产环境中,更多需要排查的其实就是超时问题,由于造成超时原因比较多,因此会给运维同事造成很多困扰,但现实情况往往不是那样子的,因为作为一个基础服务,在上线之前就需要对一些基本环境进行优化,比如说系统层面cpu以及内存的调优,而且生产环境一般也不会用虚机去跑比较重要而且吞吐比较高的redis吧,除非是真穷了,这样说来超时的原因其实就很小了。反正在我的维护过程中,大多数情况是用户使用一些非业务操作命令,什么意思呢,就是keys啦之类的导致redis堵塞,还有一种情况就是用户对redis的命令使用不是特别熟悉,因为原始命令里支持很多聚合命令,比如mset,mget,mhget等等,还有管道的一些使用。另外还遇到过一次超时基本上时因为客户端连接数过高,当时已经到8k+,临时采取措施后,客户端连接数降下来其实就没有什么事了。 那么问题来了,为什么会这样呢?运维和用户之间的沟通太少,彼此之间你不懂我我不懂你,所以造成的redis本身的误用、滥用。等真正出现问题的时候各自都有理,运维说:你丫生产不要用keys好吧,单线程的东西,遍历一遍key业务还要不要访问数据啊;用户说:麻痹,我只管开发业务系统了,redis推荐的使用方法和规则你又没告诉我。 其实,都没有错,错在运维和用户之间那堵墙没有连接起来。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 注意:在Linux上,你可以通过process placement(taskset)、cgroups、real-time priorities(chrt)、NUMA配置(numactl)或使用低延迟内核的方式来获取较低的延迟。请注意Redis 并不适合被绑到单个CPU核上。redis会在后台创建一些非常消耗CPU的进程,如bgsave和AOF重写,这些任务是绝对不能和主事件循环进程放在一个CPU核上的。然而,大多数情况下上述的优化方法是不需要的,除非你确实需要并且你对优化方法很熟悉的情况下再使用上述方法。
  • 系统之所以要在内存和硬盘之间置换redis页数据主要因为以下三个原因:
  • 从上面的驱逐策略上看,通常在actively模式下1秒能处理100个key。在过期的key有一段时间没被访问的情况下这个清理速度已经足够了,所以 lazy模式基本上没什么用。1秒只过期100个key也不会对redis造成多大的影响。
  • 写在最后:
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档