关于Redis(Redisson)超时问题的分析

概述

生产环境中流量高峰期会出现短时间的redis异常,主要报错如下:

  • Redis server response timeout
  • RedisTimeoutException: Command execution timeout for command: (PING)
  • Command still hasn’t been written into connection!

根据redisson官方所述,RedisTimeoutException可能是多种原因造成的:

  1. Redis服务器负载高,无法及时响应请求。
  2. 用于redis底层通信的Netty线程繁忙,也就是说Netty的线程池基本满载运行,没有多余的线程可用了。可以考虑增加netty线程池大小。
  3. Redis线程池用满了,没有空余的线程处理新的连接,导致新的redis操作一直在等待可用连接。可以考虑增加redis线程池大小。
  4. 服务器CPU限制。在某些托管环境中(如K8S)会限制服务器CPU使用,从而影响连接到Redis时的应用程序性能。
  5. 不稳定的网络和TCP数据丢失。
  6. Redis供应商限制并发连接数。

其中1,5,6点很容易确认,可以排除。接下来要考虑的就是2,3,4这几点。

Netty线程池优化

在redisson中,Netty 线程负责发送命令到 Redis 服务器并接收响应。

它们处理底层的网络 I/O 操作,包括建立连接、读取和写入数据等。Netty 线程使用非阻塞的 I/O 模型,可以高效地处理多个并发连接和请求。

Redisson 通过配置参数 nettyThreads 来控制 Netty 线程的数量。增加 nettyThreads 的值可以提供更多的线程来处理并发的网络请求,从而增加 Redisson 与 Redis 之间的通信能力。然而,过多的线程数量可能会增加系统资源的消耗,因此需要根据实际情况进行适当的调整。

尝试将以下值作为 nettyThreads 的设置:32、64、128、256。

查看redisson客户端集群配置参数发现,生产环境中nettyThreads配置为32,而线上流量确实比较高,因此考虑将其调整为64。
而redis连接池最大为64,正常是够的。

其他参数优化

根据github上redisson的#4381问题讨论,还进行了以下参数的优化:

1. 移除了fst解码器,因为此解码器是旧版本使用的,新版本使用默认的解码器就可以了
2. 设置keepAlive: true,该参数不指定的话默认为false
3. 调整了重试相关的参数,如超时时间和重试次数等

CPU限制优化

优化上线后,发现错误数量确实减少了,但还是存在少量报错。说明以上的优化是有一定效果的,但不是根本原因。最终经过多番排查发现,其实是第四点,也就是服务器CPU限制导致的。

生产环境是部署在k8s上,hpa扩容策略是根据cpu来扩容的。每次扩容后,新增的pod在刚开始启动的几分钟内,因为各种资源和配置项加载需要消耗较多的cpu,经过几分钟之后才会恢复到正常水平。在此期间,进入到该pod的请求就会由于cpu负载太高导致出现redis访问超时的问题。

出现错误日志的host和时间刚好与扩容的主机和扩容时间能对应上,这也证明了确实是此问题导致的。

CPU使用限制指标

想要判断pod的cpu是否达到了瓶颈,可以通过Prometheus的container_cpu_cfs_throttled_periods_totalcontainer_cpu_cfs_periods_total这两个指标来计算。

CFS是linux系统默认的CPU调度器,用于公平地分配CPU时间片给运行在容器中的进程。当容器的CPU使用超过其资源限制时,CPU CFS会对容器进行限制。

container_cpu_cfs_throttled_periods_total 指标表示容器在 CPU CFS 中发生 CPU 限制的总周期数。每个周期的持续时间取决于 CPU CFS 的配置和容器的限制情况。该指标可以用于监控容器是否经历了 CPU 限制,并可以帮助评估容器的 CPU 使用情况和性能。如果这个值较高或持续增长,说明容器的 CPU 使用可能接近或超出了其资源限制,可能需要调整容器的资源配置或进行性能优化。

container_cpu_cfs_periods_total指标表示容器在 CPU CFS 中获得的总周期数。

注意,这两个指标均是针对单个容器的

通过统计一段时间内CPU受限周期数占总调度周期数的比例,可以判断出在这段时间内容器的cpu使用是否正常。

这也是上文中判断新启的pod在刚开始的几分钟内CPU被打满的依据。

优化方式

分析了原因之后,那就可以想办法来优化了。

思路有两种,一是增加pod申请的CPU资源,保证新增的pod在系统初始化时有足够CPU使用。二是调整startUp探针的初始化时间,保证在刚开始的几分钟内请求不会进入到pod中(startUp探针的机制参见k8s工作负载(1))。

但这两种方案也会带来负面影响,增加CPU资源虽然会满足应用初始化时的CPU消耗,但系统平稳后太大的CPU就比较浪费了,而且会对根据CPU利用率来进行扩缩容的HPA策略有影响。增大startUp探针的初始化时间虽然可以让流量晚一点进入,但是也会降低扩容的速率。举例来说,在某个时间点需要扩容3个pod,原来经过2分钟时间就扩好了,但现在可能需要5分钟才能扩好。

总之,具体的优化措施需要结合实际的应用场景来考虑。

参考资料

[1].https://blog.csdn.net/xiaoyi52/article/details/133277904
[2].https://github.com/redisson/redisson/issues/4381

你可能感兴趣的:(redis,数据库,缓存)