老卫带你学---redis阻塞问题及其处理

老卫带你学—redis阻塞问题及其处理

本文将分成两个方面来介绍redis阻塞

内在原因

  • API或数据结构使用不合理
  • CPU饱和
  • 持久化相关的阻塞

1. API或数据结构使用不合理
通常Redis执行命令速度非常快,但也存在例外,如对一个包含上万个元素的hash结构执行hgetall操作,由于数据量比较大且命令算法复杂度是O(n),这条命令执行速度必然很慢。这个问题就是典型的不合理使用API和数据结构。对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度超过O(n)的命令。
1.1 如何发现慢查询
Redis原生提供慢查询统计功能,执行slowlog get{n}命令可以获取最近的n条慢查询命令,默认对于执行超过10毫秒的命令都会记录到一个定长队列中,线上实例建议设置为1毫秒便于及时发现毫秒级以上的命令。如果命令执行时间在毫秒级,则实例实际OPS只有1000左右。慢查询队列长度默认128,可适当调大。慢查询更多细节见第3章。慢查询本身只记录了命令执行时间,不包括数据网络传输时间和命令排队时间,因此客户端发生阻塞异常后,可能不是当前命令缓慢,而是在等待其他命令执行。需要重点比对异常和慢查询发生的时间点,确认是否有慢查询造成的命令阻塞排队。
发现慢查询后,开发人员需要作出及时调整。可以按照以下两个方向去调整:

  • 修改为低算法度的命令,如hgetall改为hmget等,禁用keys、sort等命令。
  • 调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。大对象拆分过程需要视具体的业务决定,如用户好友集合存储在Redis中,有些热点用户会关注大量好友,这时可以按时间或其他维度拆分到多个集合中。

1.2 如何发现大对象
Redis本身提供发现大对象的工具,对应命令:redis-cli-h{ip}-p{port} bigkeys。内部原理采用分段进行scan操作,把历史扫描过的最大对象统计出来便于分析优化。

# redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'ptc:-571805194744395733' with 17 bytes
[00.00%] Biggest string found so far 'RVF#2570599,1' with 3881 bytes
[00.01%] Biggest hash found so far 'pcl:8752795333786343845' with 208 fields
[00.37%] Biggest string found so far 'RVF#1224557,1' with 3882 bytes
[00.75%] Biggest string found so far 'ptc:2404721392920303995' with 4791 bytes
[04.64%] Biggest string found so far 'pcltm:614' with 5176729 bytes
[08.08%] Biggest string found so far 'pcltm:8561' with 11669889 bytes
[21.08%] Biggest string found so far 'pcltm:8598' with 12300864 bytes
.. 忽略更多输出 ...
-------- summary -------
Sampled 3192437 keys in the keyspace!
Total key length in bytes is 78299956 (avg len 24.53)
Biggest string found 'pcltm:121' has 17735928 bytes
Biggest hash found 'pcl:3650040409957394505' has 209 fields
2526878 strings with 954999242 bytes (79.15% of keys, avg size 377.94)
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
665559 hashs with 19013973 fields (20.85% of keys, avg size 28.57)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

根据结果汇总信息能非常方便地获取到大对象的键,以及不同类型数据结构的使用情况。
2. CPU饱和
单线程的Redis处理命令时只能使用一个CPU。而CPU饱和是指Redis把单核CPU使用率跑到接近100%。使用top命令很容易识别出对应Redis进程的CPU使用率。CPU饱和是非常危险的,将导致Redis无法处理更多的命令严重影响吞吐量和应用方的稳定性。对于这种情况,首先判断当前Redis的并发量是否达到极限,建议使用统计命令redis-cli-h{ip}-p{port}--stat获取当前Redis使用情况,该命令每秒输出一行统计信息,运行效果如下:

# redis-cli --stat
------- data ------ --------------------- load -------------------- - child -
keys    mem   clients  blocked requests            connections
3789785 3.20G 507      0       8867955607 (+0)     555894
3789813 3.20G 507      0       8867959511 (+63904) 555894
3789822 3.20G 507      0       8867961602 (+62091) 555894
3789831 3.20G 507      0       8867965049 (+63447) 555894
3789842 3.20G 507      0       8867969520 (+62675) 555894
3789845 3.20G 507      0       8867971943 (+62423) 555894

以上输出是一个接近饱和的Redis实例的统计信息,它每秒平均处理6万+的请求。对于这种情况,垂直层面的命令优化很难达到效果,这时就需要做集群化水平扩展来分摊OPS压力。如果只有几百或几千OPS的Redis实例就接近CPU饱和是很不正常的,有可能使用了高算法复杂度的命令。还有一种情况是过度的内存优化,这种情况有些隐蔽,需要我们根据info commandstats统计信息分析出命令不合理开销时间,例如下面的耗时统计:

cmdstat_hset:calls=198757512,usec=27021957243,usec_per_call=135.95

查看这个统计可以发现一个问题,hset命令算法复杂度只有O(1)但平均耗时却达到135微秒,显然不合理,正常情况耗时应该在10微秒以下。这是因为上面的Redis实例为了追求低内存使用量,过度放宽ziplist使用条件(修改了hash-max-ziplist-entries和hash-max-ziplist-value配置)。进程内的hash对象平均存储着上万个元素,而针对ziplist的操作算法复杂度在O(n)到O(n2)之间。虽然采用ziplist编码后hash结构内存占用会变小,但是操作变得更慢且更消耗CPU。ziplist压缩编码是Redis用来平衡空间和效率的优化手段,不可过度使用。

3. 持久化阻塞
对于开启了持久化功能的Redis节点,需要排查是否是持久化导致的阻塞。持久化引起主线程阻塞的操作主要有:fork阻塞、AOF刷盘阻塞、HugePage写操作阻塞。
fork阻塞
AOF刷盘阻塞
HugePage写操作阻塞

外在原因

排查Redis自身原因引起的阻塞原因之后,如果还没有定位问题,需要
排查是否由外部原因引起。围绕以下三个方面进行排查:

  • CPU竞争
  • 内存交换
  • 网络问题

1. CPU竞争
进程竞争:Redis是典型的CPU密集型应用,不建议和其他多核CPU迷集型服务部署在一起。当其他进程过度消耗CPU时,将严重影响Redis吞吐量。可以通过top、sar等命令定位到CPU消耗的时间点和具体进程,这个问题比较容易发现,需要调整服务之间部署结构。
绑定CPU:部署Redis时为了充分利用多核CPU,通常一台机器部署多个实例。常见的一种优化是把Redis进程绑定到CPU上,用于降低CPU频繁上下文切换的开销。这个优化技巧正常情况下没有问题,但是存在例外情况,当Redis父进程创建子进程进行RDB/AOF重写时,如果做了CPU绑定,会与父进程共享使用一个CPU。子进程重写时对单核CPU使用率通常在90%以上,父进程与子进程将产生激烈CPU竞争,极大影响Redis稳定性。因此对于开启了持久化或参与复制的主节点不建议绑定CPU。
2. 内存交换
内存交换(swap)对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。识别Redis内存交换的检查方法如下:

  • 查询Redis进程号:
# redis-cli -p 6383 info server | grep process_id
process_id:4476
- 根据进程号查询内存交换信息
```
# cat /proc/4476/smaps | grep Swap

Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
.....
```
如果交换量都是0KB或者个别的是4KB,则是正常现象,说明Redis进程内存没有被交换。预防内存交换的方法有:

  • 保证机器充足的可用内存。
  • 确保所有Redis实例设置最大可用内存(maxmemory),防止极端情况
    下Redis内存不可控的增长。
  • 降低系统使用swap优先级,如echo10>/proc/sys/vm/swappiness

3. 网络问题
网络问题经常是引起Redis阻塞的问题点。常见的网络问题主要有:连接拒绝、网络延迟、网卡软中断等。
3.1 连接拒绝
当出现网络闪断或者连接数溢出时,客户端会出现无法连接Redis的情况。我们需要区分这三种情况:网络闪断、Redis连接拒绝、连接溢出。
第一种情况:网络闪断
一般发生在网络割接或者带宽耗尽的情况,对于网络闪断的识别比较困难,常见的做法可以通过sar-n DEV查看本机历史流量是否正常,或者借助外部系统监控工具(如Ganglia)进行识别。具体问题定位需要更上层的运维支持,对于重要的Redis服务需要充分考虑部署架构的优化,尽量避免客户端与Redis之间异地跨机房调用。
第二种情况:Redis连接拒绝
Redis通过maxclients参数控制客户端最大连接数,默认10000。当Redis连接数大于maxclients时会拒绝新的连接进入,info statsrejected_connections统计指标记录所有被拒绝连接的数量:

# redis-cli -p 6384 info Stats | grep rejected_connections
rejected_connections:0

Redis使用多路复用IO模型可支撑大量连接,但是不代表可以无限连接。客户端访问Redis时尽量采用NIO长连接或者连接池的方式。当Redis用于大量分布式节点访问且生命周期比较短的场景时,如比较典型的在Map/Reduce中使用Redis。因为客户端服务存在频繁启动和销毁的情况且默认Redis不会主动关闭长时间闲置连接或检查关闭无效的TCP连接,因此会导致Redis连接数快速消耗且无法释放的问题。这种场景下建议设置tcp-keepalive和timeout参数让Redis主动检查和关闭无效连接。
第三种情况:连接溢出
这是指操作系统或者Redis客户端在连接时的问题。这个问题的原因比较多,下面就分别介绍两种原因:进程限制、backlog队列溢出。

  • 进程限制
    客户端想成功连接上Redis服务需要操作系统和Redis的限制都通过才可以。操作系统一般会对进程使用的资源做限制,其中一项是对进程可打开最大文件数控制,通过ulimit-n查看,通常默认1024。由于Linux系统对TCP连接也定义为一个文件句柄,因此对于支撑大量连接的Redis来说需要增大这个值,如设置ulimit-n65535,防止Too many open files错误。

  • backlog队列溢出
    系统对于特定端口的TCP连接使用backlog队列保存。Redis默认的长度为511,通过tcp-backlog参数设置。如果Redis用于高并发场景为了防止缓慢连接占用,可适当增大这个设置,但必须大于操作系统允许值才能生效。当Redis启动时如果tcp-backlog设置大于系统允许值将以系统值为准,Redis打印如下警告日志:

    # WARNING: The TCP backlog setting of 511 cannot be enforced 
    because /proc/sys/
    net/core/somaxconn is set to the lower value of 128.
    

    系统的backlog默认值为128,使echo511>/proc/sys/net/core/somaxconn命令进行修改。可以通过netstat-s命令获取因backlog队列溢出造成的连接拒绝统计,如下:

    # netstat -s | grep overflowed
    663 times the listen queue of a socket overflowed
    

    如果怀疑是backlog队列溢出,线上可以使用cron定时执行netstat-s|grep
    overflowed统计,查看是否有持续增长的连接拒绝情况。

3.2 网络延迟
网络延迟取决于客户端到Redis服务器之间的网络环境。主要包括它们之间的物理拓扑和带宽占用情况。常见的物理拓扑按网络延迟由快到慢可分为:同物理机>同机架>跨机架>同机房>同城机房>异地机房。但它们容灾性正好相反,同物理机容灾性最低而异地机房容灾性最高。Redis提供了测量机器之间网络延迟的工具,在redis-cli-h{host}-p{port}命令后面加入如下参数进行延迟测试:
--latency:持续进行延迟测试,分别统计:最小值、最大值、平均值、采样次数。
--latency-history:统计结果同--latency,但默认每15秒完成一行统计,可通过-i参数控制采样时间。
--latency-dist:使用统计图的形式展示延迟统计,每1秒采样一次。网络延迟问题经常出现在跨机房的部署结构上,对于机房之间延迟比较严重的场景需要调整拓扑结构,如把客户端和Redis部署在同机房或同城机房等。
带宽瓶颈通常出现在以下几个方面:机器网卡带宽、架交换机带宽、机房之间专线带宽。
带宽占用主要根据当时使用率是否达到瓶颈有关,如频繁操作Redis的大对象对于千兆网卡的机器很容易达到网卡瓶颈,因此需要重点监控机器流量,及时发现网卡打满产生的网络延迟或通信中断等情况,而机房专线和交换机带宽一般由上层运维监控支持,通常出现瓶颈的概率较小。
3.3 网卡软中断
网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在同一个CPU,导致无法充分利用多核CPU的情况。网卡软中断瓶颈一般出现在网络高流量吞吐的场景,如下使用“top+数字1”命令可以很明显看到CPU1的软中断指标(si)过高:

# top
Cpu0 : 15.3%us, 0.3%sy, 0.0%ni, 84.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 16.6%us, 2.0%sy, 0.0%ni, 47.1%id, 3.3%wa, 0.0%hi, 31.0%si, 0.0%st
Cpu2 : 13.3%us, 0.7%sy, 0.0%ni, 86.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 14.3%us, 1.7%sy, 0.0%ni, 82.4%id, 1.0%wa, 0.0%hi, 0.7%si, 0.0%st
.....
Cpu15 : 10.3%us, 8.0%sy, 0.0%ni, 78.7%id, 1.7%wa, 0.3%hi, 1.0%si, 0.0%st
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值