4. Redis的噩梦:阻塞
- 一、总览
- 二、发现阻塞
- 2.1 应用方报警
- 2.2 Redis监控系统
- 三、内因
- 3.1 API或数据结构使用不合理
- 1.如何发现慢查询
- 2.如何发现大对象
- 3.2 CPU饱和的问题
- 3.3 持久化相关的阻塞
- 1.fork阻塞
- 2.AOF刷盘阻塞
- 3.HugePage写操作阻塞
- 3.1 API或数据结构使用不合理
- 四、外因
-
- 4.1 CPU竞争
- 1.进程竞争
- 2·绑定CPU
- 4.2 内存交换
- 4.3 网络问题
- 1.连接拒绝
- 2.网络延时
- 3.网卡软中断
-
- 五 总结回顾
- 六、参考文献
由于公司使用redis集群是4.0以下版本,故本文也是在此版本基础上进行论述。
一、总览
Redis是典型的单线程架构,所有的读写操作都是在一条主线程中完成的。当Redis用于高并发场景时,这条线程就变成了它的生命线。如果出现阻塞,哪怕是很短时间,对于我们的应用来说都是噩梦。导致阻塞问题的场
景大致分为内在原因和外在原因:
·内在原因包括:不合理地使用API或数据结构、CPU饱和、持久化阻塞等。
·外在原因包括:CPU竞争、内存交换、网络问题等。本章我们聚焦于Redis阻塞问题,通过学习本章可掌握快速定位和解决Redis阻塞的思路和技巧。
二、发现阻塞
2.1 应用方报警
当Redis阻塞时,线上应用服务应该最先感知到,这时应用方会收到大量Redis超时异常,比如Jedis客户端会抛出JedisConnectionException异常。处理方式主要有两种:
1.常见的做法是在应用方加入异常统计并通过邮件/短信/微信报警,以便及时发现通知问题。
2.由于Redis调用API会分散在项目的多个地方,每个地方都监听异常并加入监控代码必然难以维护。这时可以借助于日志系统(借助日志系统统计异常的前提是,需要项目必须使用日志API进行异常统一输出)。
2.2 Redis监控系统
当监控系统检测到Redis运行期的一些关键指标出现不正常时会触发报警。Redis相关的监控系统开源的方案有很多,比如:redisLive、redis-faina、redis-stat等。
监控系统所监控的关键指标有很多,如命令耗时、慢查询、持久化阻塞、连接拒绝、CPU/内存/网络/磁盘使用过载等。
三、内因
3.1 API或数据结构使用不合理
通常Redis执行命令速度非常快,但也存在例外,如对一个包含上万个元素的hash结构执行hgetall操作,由于数据量比较大且命令算法复杂度是O(n),这条命令执行速度必然很慢。这个问题就是典型的不合理使用API和数据结构。对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度超过O(n)的命令。
1.如何发现慢查询
对应命令:slowlog get {n}
发现慢查询后,开发人员需要作出及时调整。可以按照以下两个方向去调整:
1)修改为低算法度的命令,如hgetall改为hmget等,禁用keys、sort等命令。
2)调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。大对象拆分过程需要视具体的业务决定,如用户好友集合存储在Redis中,有些热点用户会关注大量好友,这时可以按时间或其他维度拆分到多个集合中。
2.如何发现大对象
对应命令:redis -cli -h {ip} -p {port} bigkeys
内部原理采用分段进行scan操作,把历史扫描过的最大对象统计出来便于分析优化。
3.2 CPU饱和的问题
单线程的Redis处理命令时只能使用一个CPU。而CPU饱和是指Redis把单核CPU使用率跑到接近100%。使用top命令很容易识别出对应Redis进程的CPU使用率。CPU饱和是非常危险的,将导致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压力。
过度的内存优化也有可能导致cpu饱和这种情况有些隐蔽,需要我们根据infocommandstats统计信息分析出命令不合理开销时间。
3.3 持久化相关的阻塞
1.fork阻塞
fork操作发生在RDB和AOF重写时,Redis主线程调用fork操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果fork操作本身耗时过长,必然会导致主线程的阻塞。
可以执行info stats命令获取到latest_fork_usec指标,表示Redis最近一次fork操作耗时,如果耗时很大,比如超过1秒,则需要做出优化调整,如避免使用过大的内存实例和规避fork缓慢的操作系统等。
2.AOF刷盘阻塞
当我们开启AOF持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对AOF文件做fsync操作。当硬盘压力过大时,fsync操作需要等待,直到写入完成。如果主线程发现距离上一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成。这种阻塞行为主要是硬盘压力引起,可以查看Redis日志识别出这种情况。也可以查看info persistence统计中的aof_delayed_fsync指标,每次发生fdatasync阻塞主线程时会累加。
当开启AOF持久化时,常用的同步硬盘的策略是everysec,用于平衡性能和数据安全性。对于这种方式,Redis使用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时,会造成Redis主线程阻塞,如图3-1所示。
fig 3-1 使用everysec做刷盘策略的流程
3.HugePage写操作阻塞
子进程在执行重写期间利用Linux写时复制技术降低内存开销,因此只有写操作时Redis才复制要修改的内存页。对于开启Transparent HugePages的操作系统,每次写命令引起的复制内存页单位由4K变为2MB,放大了512倍,会拖慢写操作的执行时间,导致大量写操作慢查询。例如简单的incr命令也会出现在慢查询中。
四、外因
4.1 CPU竞争
1.进程竞争
Redis是典型的CPU密集型应用,不建议和其他多核CPU密集型服务部署在一起。当其他进程过度消耗CPU时,将严重影响Redis吞吐量。可以通过top、sar等命令定位到CPU消耗的时间点和具体进程,这个问题比较容易发现,需要调整服务之间部署结构。
2·绑定CPU
部署Redis时为了充分利用多核CPU,通常一台机器部署多个实例。常见的一种优化是把Redis进程绑定到CPU上,用于降低CPU频繁上下文切换的开销。
PS:当Redis父进程创建子进程进行RDB/AOF重写时,如果做了CPU绑定,会与父进程共享使用一个CPU。子进程重写时对单核CPU使用率通常在90%以上,父进程与子进程将产生激烈CPU竞争,极大影响Redis稳定性。因此对于开启了持久化或参与复制的主节点不建议绑定CPU。
4.2 内存交换
内存交换(swap)对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。识别Redis内存交换的检查方法如下:
1.查询Redis进程号
# redis-cli -p 6383 info server | grep process_id
process_id:4476
2.根据进程号查询内存交换信息
# cat /proc/4476/smaps | grep Swap
Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
.....
如果交换量都是0KB或者个别的是4KB,则是正常现象,说明Redis进程内存没有被交换。
4.3 网络问题
1.连接拒绝
当出现网络闪断或者连接数溢出时,客户端会出现无法连接Redis的情况。我们需要区分这三种情况:网络闪断、Redis连接拒绝、连接溢出。
连接溢出
1) 进程限制
2) backlog队列溢出
系统对于特定端口的TCP连接使用backlog队列保存
2.网络延时
网络延迟取决于客户端到Redis服务器之间的网络环境。主要包括它们之间的物理拓扑和带宽占用情况。常见的物理拓扑按网络延迟由快到慢可分为:同物理机>同机架>跨机架>同机房>同城机房>异地机房。但它们容灾性正好相反,同物理机容灾性最低而异地机房容灾性最高。Redis提供了测量机器之间网络延迟的工具,在redis-cli-h{host}-p{port}命令后面加入如下参数进行延迟测试:
·--latency:持续进行延迟测试,分别统计:最小值、最大值、平均值、采样次数。
·--latency-history:统计结果同--latency,但默认每15秒完成一行统计,可通过-i参数控制采样时间。
·--latency-dist:使用统计图的形式展示延迟统计,每1秒采样一次。
网络延迟问题经常出现在跨机房的部署结构上,对于机房之间延迟比较和Redi严重的场景需要调整拓扑结构,如把客户端部署在同机房或同城机房。
3.网卡软中断
网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在同一个CPU,导致无法充分利用多核CPU的情况。网卡软中断瓶颈一般出现在网络高流量吞吐的场景。
五 总结回顾
1)客户端最先感知阻塞等Redis超时行为,加入日志监控报警工具可快速定位阻塞问题,同时需要对Redis进程和机器做全面监控。
2)阻塞的内在原因:确认主线程是否存在阻塞,检查慢查询等信息,发现不合理使用API或数据结构的情况,如keys、sort、hgetall等。关注CPU使用率防止单核跑满。当硬盘IO资源紧张时,AOF追加也会阻塞主线程。
3)阻塞的外在原因:从CPU竞争、内存交换、网络问题等方面入手排查是否因为系统层面问题引起阻塞。
六、参考文献
1.《Redis开发与运维》,付磊,张益军编著。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统