logo
logo

可观测性实战:快速定位 Redis 应用高时延问题

林嘉炜 2023-10-31

经验丰富的工程师都知道,在一个应用软件中,连接外部数据库的过程中,创建、获取、销毁连接是一个非常耗时的过程,如果极端情况下有几百毫秒的时延,软件整体性能就会大打折扣。所以我们一般会使用连接池来管理连接,使用连接池有以下几个优势:1. 提前创建连接,在应用真正需要连接数据库时无需耗费额外的建连时间,使高频操作节省了一大部分时间;2. 统一内存管理,如果每个开发人员都需要手动创建、手动销毁连接,如果某个地方出错,没有正确销毁连接,不仅会产生潜在的内存泄露风险、产生长连接也会消耗服务端可用的资源数量,应用服务长久运行之下会导致负荷越来越大,最终不得不重启应用,造成可用性降低。但使用连接池同时也会带来弊端:一个连接池组件通常会引入很多繁杂的配置,不合理的配置往往会造成性能隐患,并进而导致生产故障,而这样的问题也往往难以排查,在本文中,我们将从一个应用高时延问题入手,体验使用 DeepFlow 层层分析并定位根因,帮助团队找到隐患。

0x0: 背景介绍

某日,应用团队正在对一个基础服务做压测,但是性能表现不尽如人意,在没有用较大 QPS 压测的情况下时延仍然比较高,经过初步排查我们发现其中一个请求的客户端时延较高,但由于Redis 没法插桩,缺少了 Redis 服务端的响应时间做对比,难以判断问题点,于是我们打开 DeepFlow ,使用 DeepFlow 零侵扰全栈的可观测体系来分析问题。

0x1: 使用全景图分析

先对应用做个简单了解:首先,我们打开服务列表,可以看到这个应用做了 OTel 插桩,从集群中得到的采集数据来自三个信号源:DeepFlow 采集的 Packet 数据、eBPF 数据、应用插桩的 OTel 数据。概览请求,发现在过去一段时间内,P99 达到500 多毫秒,这个服务部署在一个三节点 8C16G 配置的集群中,这样的性能表现不太理想。

服务列表服务列表

打开服务拓扑,先查看一下这个应用的流量走向。从客户端视角的业务拓扑,我们了解到这个业务包含三个服务,一个客户端(IP: 10.X.X.X)从集群外访问到集群内,拓扑如下:

业务拓扑-客户端视角业务拓扑-客户端视角

从服务端视角的业务拓扑,我们了解到这个业务还访问了 MySQL(ticket-mysql-757b796cf9-jdnrm)、Redis(redis-master-0) 服务作为外部存储,拓扑如下:

业务拓扑-服务端视角业务拓扑-服务端视角

其次,从服务拓扑的链路指标来看,我们发现访问 bar-svc 与 bar-svc 访问 Redis 两者的请求量有较大差异,每个业务请求都会对 Redis 产生大量请求,应用上或许是个设计不合理的地方,这可能是一个瓶颈点。从 Redis 服务的平均时延表现来看,瓶颈基本集中在 bar-svc 与 Redis 这段链路。

To-barTo-bar

To-RedisTo-Redis

把我们需要的信息简化后,应用拓扑和问题点整理为下图:

拓扑拓扑

0x2: 使用分布式追踪定界

从上面的信息初步了解之下,我们猜测瓶颈可能发生在访问 Redis 的这段链路上。先看一下 Redis 整体情况:打开 Redis Monitoring 面板,查看这段时间 Redis 服务的请求量及响应时延分布,我们发现,Redis 的请求量维持在 4k 上下,但平均时延只有 1~2ms:

Redis-请求量与时延分布Redis-请求量与时延分布

平均时延指标与我们之前看到的请求时延不一致,初步分析有可能是某些较高的时延被拉平了,说明这可能是在某些场景下才能触发,而不是所有请求都慢,才导致高时延占比不大。

我们继续下钻分析:查看调用链追踪,从火焰图中得到了更多的信息:有些请求的整体时延达到 800ms,光是 Redis 的请求也有将近 150ms,这可能就是我们想要找的慢请求,但 Redis 服务自身的时延不高,整个请求的时延基本集中在客户端进程,说明问题就发生在客户端应用处,也就是 bar-svc 这个应用,和 Redis 服务没有关系。

Redis-调用链追踪Redis-调用链追踪

0x3: 使用持续剖析定位根因

我们还能继续往下分析吗?当然,得益于 DeepFlow 实现了基于 eBPF 的 Continuous Profile 功能(企业版),可以层层下钻继续进行分析:选择 bar-svc 应用,查看它的完整调用堆栈,找到了消耗时间最大的函数,这个函数消耗的 CPU 时间在过去几分钟内达到了 65%。

bar-svc 堆栈分析bar-svc 堆栈分析

继续下钻,最下方的函数由非常多内核函数调用组成,而属于用户代码的部分,我们发现核心瓶颈就是请求 Redis 获取数据的函数,这更是进一步证明了我们上述的分析,通过几个简单的步骤,我们快速定位到了根因:核心访问瓶颈集中在 bar-svc 业务应用内,且就是访问 Redis 获取数据的这个函数。

bar-svc 堆栈函数分析bar-svc 堆栈函数分析

得到这个结论之后,我们把内存对象 dump 出来检查,发现与曾经看到过的一篇文章 的案例有相似之处,二者问题点是一致的。场景是:应用客户端使用了连接池管理 Redis 连接,当客户端突发大量请求时,就会频繁从连接池中获取、归还连接对象,当归还连接到连接池中时,连接池需要检查空闲连接数是否大于最大允许值,也即统计 idle 数量是否大于 max-idle,如果大于则直接销毁对象。而由于应用把 max-idle 参数设置得较小,导致客户端创建、销毁连接太频繁了,没有充分利用机器性能资源,CPU 浪费在创建对象、建连、销毁上,终致应用性能表现不佳。

HeapDumpHeapDump

明确问题之后,我们迅速调整了连接池配置,扩大 max-idle 参数,调整与 max-active 一致为 100。作用是减少连接的创建与销毁,保持连接池中有稳定数量的对象,这样即使应用突发大量请求,也不会让时延剧增。

更新了应用后,在测试维持同样压力水平的情况下重新测试,我们找出核心链路的请求时延 P99 进行对比,发现 P99 得到了大幅降低:

服务 数据源 修改前(P99) 修改后(P99) 下降比
bar-svc eBPF 450.22ms 263.65ms 58.5%
foo-svc eBPF 494.43ms 310.47ms 62.8%

服务列表-修改连接池配置后服务列表-修改连接池配置后

经此一役,应用团队体验到了使用 DeepFlow 快速分析、定界、定位的丝滑,并继续以 DeepFlow 作为统一入口观测实时压测结果,开启下一轮优化之路。

0x4: 什么是 DeepFlow

DeepFlow 是云杉网络开发的一款可观测性产品,旨在为复杂的云基础设施及云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了应用性能指标、分布式追踪、持续性能剖析等观测信号的零侵扰Zero Code)采集,并结合智能标签SmartEncoding)技术实现了所有观测信号的全栈Full Stack)关联和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。

GitHub 地址:https://github.com/deepflowio/deepflow

访问 DeepFlow Demo,体验零插桩、全覆盖、全关联的可观测性。