P95陷阱

想象这个场景:

一位测试同事走到你的座位旁,说:“接到客户(上游系统)反馈,说我们系统有个Rest接口响应慢。我看了监控上的P95响应时间,都一秒多了,帮忙看看吧。”

又来活了。

你喜欢解决问题带来的成就感,但你更享受这个过程,从现象开始一步步抽丝剥茧,直到找出问题根因并解决它,就像是一个侦探游戏。

侦探需要亲自在犯罪现场搜集证据。你打开监控页面,查看网关统计的P95。你调整采样周期查询多次,发现问题接口的P95都基本保持在1秒多。可见问题描述无误。

网关监控数据最多保留一个月,可能是一个月前改出来的?你简单阅读了代码,并查看对应的修改历史,发现最近几个月都没有动过,于是否定了这个猜想。也许这个接口慢问题存在很久了。

但为什么客户现在才提出来?你联系上游系统,对方告知,最近他们有些接口准备提供给外部客户使用,正在逐个梳理,以确定SLA。在此过程中,对方发现了他们调用我们这个接口慢的问题,联系了我方测试。

问题起源基本澄清了,接下来看代码。经过阅读,你发现这个接口还算简单,它不是批量处理接口,也不涉及复杂计算,主要耗时估计在一次数据库查询和三次串行的外部系统接口调用上面:

入口--->数据库查询--->调外部系统A接口--->调外部系统B接口--->调外部系统C接口--->出口

分析数据库查询,发现SQL语句比较简单,数据表不大且查询条件有索引,返回的数据量很少。暂时先排除。

也许我们是被这些外部系统给拖累了?但我们没有对发出的请求进行统计,该怎么证明?

你想到了APM,公司最近统一在生产环境部署了skywalking,虽然采样率只有10%,但是在这种“统计类”问题上还是能起到作用。

你打开APM性能监控网站,进入跟踪查询页面,输入对应接口地址,选择时间段,并将查询结果按响应时长倒序排列。

一共得到了600页的结果,乘以 5%就是30。跳转到第30页,可以看到,该页及其相邻页中列出采样的接口响应时间,与网关监控给出的P95时间基本差不多。

进入其中一条跟踪日志,发现大部分时间都耗费在等待系统A接口的响应上。换一条,发现时间浪费在等待系统B。再换一条,发现又是系统C拖了后腿......

就这样快速浏览了附近的几十上百条跟踪,并简单记录之后,心里基本有底了:这些慢响应是下游系统造成的。

于是你分别给系统A、B、C的同事发消息,内容都差不多:“你们系统的XX接口有点慢,影响了我们给上游的响应时间,看看啥时能帮忙给解决。” 并附上了从APM平台记录下的一些响应时间,都是数百毫秒级别的。

你在座椅上伸个懒腰。问题已定位,接下来跟踪就行。

不一会,你收到了系统A同事回复:“哥们,我们接口没那么慢的,看看你们自己有没有别的问题吧。”他还附上了权威的网关监控截图,系统A接口的P95竟然只有几十毫秒。很快系统B和系统C同事也回复了类似的消息和截图,他们给出的P95也都是几十毫秒。

怎么回事,难道定位有误?

你重新挑选了一个时间段,再次对其中超过95%的上百个慢响应进行了统计,发现耗时仍然在下游系统。你请系统A、B、C的同事选择该时间段的网关统计数据再截图发给你,但你收到的P95都还只是几十毫秒。

三个系统的P95都是几十毫秒,加起来都不到200,离1000差得远。是哪里错了?

你陷入了沉思。

这好像是个机率问题。假如每个系统95%的响应都很快,而剩余的5%响应都非常慢。调用这三个独立系统时,有时AB快但C慢,而有时AC快但B慢...,正如前面在APM平台上看到的。所以我们错在以“每个系统同时响应很快,同时响应很慢”为前提,得到“本接口P95为三个下游接口P95之和”这个错误结论。实际上,三个独立系统都“快速响应”的概率是95%的三次方,即85.7%,而剩下的14.3%都会慢。

14.3%是5%的将近三倍,可见本系统的慢响应区间被放大很多!

可以写个程序来简单模拟一下

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public enum Sys {

        A(10, 100),
        B(20, 200),
        C(40, 400),
        ;

        int p95;
        int p99;
        List<Integer> rsList;

        Sys(int p95, int p99) {
            this.p95 = p95;
            this.p99 = p99;
            rsList = new ArrayList<>(1000);
            //粗略假设95%的响应都一样快,剩下的5%响应都一样慢
            for (int i = 0; i < 1000; i++) {
                if (i < 950) {
                    rsList.add(p95);
                } else {
                    rsList.add(p99);
                }
            }
            System.out.printf("%s p95 = %d, p99 = %d\n", 
                this, rsList.get(950-1), rsList.get(990-1));
        }


    public static void main(String[] args) {
        //打乱
        for (Sys sys : Sys.values()) {
            Collections.shuffle(sys.rsList);
        }

        List<Integer> totalRs = new ArrayList<>(1000);
        for (int i = 0; i < 1000; ++i) {
            int rs = 0;
            for (Sys sys : Sys.values()) {
                rs += sys.rsList.get(i);
            }
            totalRs.add(rs);
        }
        Collections.sort(totalRs);
        System.out.printf("new P95 = %d\n", totalRs.get(950-1));
        System.out.printf("new P85 = %d\n", totalRs.get(850-1));
    }

}

得到

c:\>java Sys
A p95 = 10, p99 = 100
B p95 = 20, p99 = 200
C p95 = 40, p99 = 400
new P95 = 340
new P85 = 70

用图表来简单表示(D表示按顺序简单相加的错误结果,E为响应时间随机打乱后的正确结果),可以看到在这个非常简陋的模型下,P95比三个系统P95之和都高很多,P99则比三者之和低不少。

总之,原因还是在于调下游系统接口。但是要怎么解决呢?

1、要求下游系统提升性能。

如果他们的P99时间能减少到现在的P95时间,那么我们的P95就肯定不会超过三个系统现在的P95之和(99%的三次方约等于97%)。不过这个要求实在不合理,下游系统不可能接受,现在的P95指标已经不错了,继续提升的成本太高,没人会买单。

2、改成并行调用。

这样我们接口的P95就约等于最慢系统的P95,也在几十毫秒内。。。

不对不对!差点又再次掉入思维陷阱。不管是串行并行,5%的响应慢区间还是会扩散为14.3%,差别只是从三者的和变成了三者的最大值而已。而且不存在所谓的最慢系统,除非某个系统的P95都远远大于其他系统的P99。

可以用代码来模拟。把前面的

            for (Sys sys : Sys.values()) {
                rs += sys.rsList.get(i);
            }

改成

            for (Sys sys : Sys.values()) {
                if (rs < sys.rsList.get(i)) {
                    rs = sys.rsList.get(i);
                }
            }

可以得到

c:\>java Sys
A p95 = 10, p99 = 100
B p95 = 20, p99 = 200
C p95 = 40, p99 = 400
new P95 = 200
new P85 = 40

在这个非常简陋的模型下,新的P95响应时间和各系统的P99响应时间在一个数量级,并没有太显著的提高。

除了并行调用增加的复杂度,根本的问题是这些接口在业务上存在逻辑关系,只能先后调用,所以这个方案看起来不可行。

3、减少接口超时时间。

既然下游能保证95%的接口在几十毫秒内返回,那我们就把超时时间缩短成 (P95 + x毫秒),而不是现在的500毫秒甚至1秒。如果响应时长不幸进入了5%的慢区间,则我们代码快速失败,并重新发起一次调用。两次进入慢区间的概率是0.25%,已经非常低了。把三个系统一起算,得到的P95大概是......(此时一个恶魔在你耳边低语:你数学不行,放弃吧)

你接受了恶魔的建议,放弃了数学推导,改用代码来做实验。将最早的

            for (Sys sys : Sys.values()) {
                rs += sys.rsList.get(i);
            }

改为

            for (Sys sys : Sys.values()) {
                if (sys.rsList.get(i) > sys.p95) {
                    rs += sys.p95 * 2; 
                } else {
                    rs += sys.rsList.get(i);
                }
            }

得到

c:\>java Sys
A p95 = 10, p99 = 100
B p95 = 20, p99 = 200
C p95 = 40, p99 = 400
new P95 = 100
new P85 = 70

这100看来比最初的340有相对不错的提升(当然是在这种简陋模型下)。

这个方案还有其他问题,比如下游接口的幂等性,对自己和下游系统及网络负荷的影响,超时时长的选择等等。如果某个时间段正好出现一次网络拥塞,而太短的超时接口可能导致该时间段所有请求都重试,反而又加重了网络的恶化。

三个方案都不够好,你再次陷入了沉思。这个慢响应区间放大实在太恶心,它应该是业界都会遇到的问题,会不会有成熟的解决方案?

放大,放大......你隐约记得在哪里看到过类似说法。对了,在神书DDIA看到过,还有参考文献:

Even if only a small percentage of backend calls are slow, the chance of getting a slow call increases if an end-user request requires multiple backend calls, and so a higher proportion of end-user requests end up being slow (an effect known as tail latency amplification [24]).

[24]Jeffrey Dean and Luiz André Barroso: “The Tail at Scale,” Communications of the ACM, volume 56, number 2, pages 74–80, February 2013. doi: 10.1145/2408776.240879

阅读优秀的论文使眼界开阔,但感觉 The Tail at Scale 里面的有些方案太复杂,不适合在系统应用层实现,而属于底层提供的能力(gRPC库好像就有对冲策略)。

在实现复杂度和收益之间找到一个平衡点,做出合适的方案,会是你接下来的工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/151842.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

langchain 之 Tools 多案例使用(一)

原文&#xff1a;langchain 之 Tools 多案例使用&#xff08;一&#xff09; - 简书 ATTENTION: 如果采用 openai 的接口&#xff0c;需要走代理&#xff0c;本文采用 proxychains 进行设置。开启 debug 模式后&#xff0c;能看到更多的输出信息。 import langchain langcha…

ROC 曲线:健康背景下的应用和解释

一、介绍 在医疗保健领域&#xff0c;做出明智的决策对于改善患者治疗结果、有效分配资源和设计有效的诊断测试至关重要。受试者工作特征 (ROC) 曲线是一个强大的工具&#xff0c;在评估诊断测试的性能、区分健康个体和患病个体以及优化医疗保健干预方面发挥着至关重要的作用。…

第07章 面向对象编程(进阶)

一 关键字&#xff1a;this 1.1 this是什么&#xff1f; 在Java中&#xff0c;this关键字不算难理解&#xff0c;它的作用和其词义很接近。 它在方法&#xff08;准确的说是实例方法或非static的方法&#xff09;内部使用&#xff0c;表示调用该方法的对象。它在构造器内部使…

【android】install android NDK

目录 1 下载NDK 2 解压 3 android-ndk的配置 1 下载NDK 下载网址&#xff1a;NDK 下载 | Android NDK | Android Developers 如果没有所需要的版本&#xff0c;则点击页面下面 不受支持的 NDK 下载需要的版本。 2 解压 将压缩文件&#xff08;例如 android-ndk-r25c-…

(五)什么是Vite——冷启动时vite做了什么(依赖、预构建)

vite分享ppt&#xff0c;感兴趣的可以下载&#xff1a; ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录&#xff1a; &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff0…

一看就会的jni,不会你来打我!

环境配置 Android Studio&#xff0c;这个不多说了。 简单说一下NDK的下载和环境变量&#xff0c;方便在Terminal里使用命令(mac版)。 下载 1.可以通过Android Studio内置的Settings-Android SDK-SDK Tools安装NDK&#xff0c;下载目录为 /Users/mac-xxx(Username)/Library…

VF01 bapi BAPI_BILLINGDOC_CREATEMULTIPLE修改付款方

系统标准通过函数SD_PARTNER_READ&#xff0c;读取VBPA表销售订单对应的伙伴。 调整通过源代码增强LV60AA01最后位置。

《QT从基础到进阶·二十九》QT,opencv源码调试

有时候我们在使用VS调试程序的bug&#xff0c;但发现程序崩溃的地方并不在我们写的程序中&#xff0c;我们通过调用堆栈发现程序崩溃的地方出现在QT或者opencv等源码中&#xff0c;那么我们怎么能把断点打到这些开源库中&#xff0c;下面提供一种办法&#xff1a; 解决方案–右…

单日充值破6000万、8天收入破亿,小程序短剧的商业真相

进入2023年以来&#xff0c;短剧发展的速度相当惊人。无论是从短视频平台的用户规模来说&#xff0c;还是从短剧内容的商业效益来看&#xff0c;都进入了双增长的狂飙模式。 小程序指的是在一些APP的小程序平台上&#xff08;多为微信端&#xff0c;抖音、快手等平台也有&…

使用requests库解决Session对象设置超时的问题

在requests库的IRC频道中&#xff0c;提出了一个问题&#xff0c;即Session对象在requests库中没有一个可以全局设置的timeout属性&#xff0c;而是需要为每个请求传递timeout值&#xff0c;或者创建一个自定义子类来实现。 为了解决这个问题&#xff0c;可以向Session对象添加…

Apache阿帕奇安装配置

目录 一、下载程序 1. 点击Download 2. 点击Files for Microsoft Windows 3. 点击Apache Lounge 4. 点击httpd-2.4.54-win64-VSI6.zip ​编辑​ 5. 下载压缩包 6.解压到文件夹里 二、配置环境变量 1. 右键我的电脑 - 属性 2. 高级系统设置 3. 点击环境变量 4. 点击系统…

中国芯片金字塔成形,商业化拐点将至

其作始也简&#xff0c;其将毕也钜。 传说埃及用时30年建成左赛尔金字塔&#xff0c;成为亘古不灭的世界奇迹。在今天&#xff0c;中国芯片产业走过8年“国产替代”历程&#xff0c;国产芯片的“金字塔”体系业已初具雏形&#xff0c;展现出蓬勃的发展潜力。 2023年是补全自主…

Linux系统进程与进程间通信

Linux是一个多用户、多任务的操作系统&#xff0c;支持多个进程同时运行。进程是Linux系统中的基本单元&#xff0c;它们负责执行各种任务&#xff0c;如网页浏览、文件下载、程序运行等。在Linux中&#xff0c;进程是由一个或多个线程组成的&#xff0c;线程是进程的基本执行单…

浅谈安科瑞无线测温产品在巴西某工厂的应用

摘 要&#xff1a;高压开关设备是变电站和配电站中保证电力系统安全运行的重要设备之一,因此,开关柜的稳定运行对于整个电力系统有非常重要的意义。设备老化、长期高负荷运行都可能使设备局部温度过高而发生火灾&#xff0c;因此,对变电站内的敏感设备进行温度检测变得尤为重要…

Java实现简单的俄罗斯方块游戏

一、创建新项目 1.首先新建一个项目&#xff0c;并命名为俄罗斯方块。 2.其次新建一个类&#xff0c;命名为Main&#xff0c;或其他的。 二、运行代码 代码如下&#xff1a; package 俄罗斯方块;import java.awt.BorderLayout; import java.awt.Color; import java.awt.Gr…

2024有哪些免费的mac苹果电脑内存清理工具?

在我们日常使用苹果电脑的过程中&#xff0c;随着时间的推移&#xff0c;可能会发现设备的速度变慢了&#xff0c;甚至出现卡顿的现象。其中一个常见的原因就是程序占用内存过多&#xff0c;导致系统无法高效地运行。那么&#xff0c;苹果电脑内存怎么清理呢&#xff1f;本文将…

【机器学习8】采样

1 均匀分布随机数 均匀分布是指整个样本空间中的每一个样本点对应的概率&#xff08;密度&#xff09; 都是相等的。 根据样本空间是否连续&#xff0c; 又分为离散均匀分布和连续均匀分布。编程实现均匀分布随机数生成器一般可采用线性同余法&#xff08;Linear Congruential…

大数据-之LibrA数据库系统告警处理(ALM-12046 网络写包丢包率超过阈值)

告警解释 系统每30秒周期性检测网络写包丢包率&#xff0c;并把实际丢包率和阈值&#xff08;系统默认阈值0.5%&#xff09;进行比较&#xff0c;当检测到网络写包丢包率连续多次&#xff08;默认值为5&#xff09;超过阈值时产生该告警。 用户可通过“系统设置 > 阈值配置…

全功能知识付费变现小程序系统源码 自带流量主 轻松帮你赚钱 带完整搭建教程

大家好啊&#xff0c;今天罗峰要来给大家分享一款全功能知识付费变现小程序源码系统 。近年来互联网技术的快速发展&#xff0c;以及人们对知识付费的需求不断增长。全功能知识付费变现小程序系统源码的出现为大家提供一个全面、高效、安全的解决方案&#xff0c;帮助用户实现知…

阿里云的99元服务器和腾讯云的88元云服务器选择哪个?怎么选?

近日&#xff0c;阿里云宣布在2023年双十一优惠活动中推出了一系列降价措施&#xff0c;使得同配置的云服务器比腾讯云更具竞争力。这一消息不仅在云计算领域引起了轰动&#xff0c;更为广大互联网用户提供了更为实惠的选择。 阿里云推出99元一年的服务器&#xff0c;续费价格…