一次夜间接口超时的解决过程

发表于 2年以前  | 总阅读数:318 次

背景

闲鱼某关键应用A依赖类目系统富客户端(下文简称类目客户端),旨在为闲鱼商品域其他应用提供各类商品类目及属性数据(下文简称CPV数据)查询服务。

每天凌晨,该应用所依赖的类目富客户端执行新老版本数据包切换时,应用提供的服务抖动非常明显,表现为大量接口超时(耗时100ms -> 3-5s),服务成功率明显下降(100% -> ~92%),RPC线程池活跃线程数上涨(50 -> ~100),抖动最长可持续20s,影响到商品发布、商品详情页等依赖CPV数据的关键业务场景;且夜间发生抖动,时间点不固定,抖动发生时开发同学也难以关注到,影响面较为不可控,因此需要排查并彻底解决此问题。

排查过程

其实这是一个表象很简单,但是根因藏得比较深的问题,笔者在排查过程中也走了一些弯路,也一并写出来供读者作为前车之鉴的参考。

堆空间不够?

结构化应用线上原先使用的是4C8G的标准规格容器,分配4G内存作为堆内存,截取部分JVM启动参数如下:

-Xms4g -Xmx4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+UnlockExperimentalVMOptions -XX:G1MaxNewSizePercent=65 -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=55 -XX:G1HeapRegionSize=16m -XX:G1NewSizePercent=25 -XX:MaxGCPauseMillis=120 -XX:+ParallelRefProcEnabled   -XX:MaxDirectMemorySize=1g -XX:+TraceG1HObjAllocation -XX:ReservedCodeCacheSize=512m -XX:+UseCodeCacheFlushing

据反馈接口抖动的同学描述,在接口抖动的时间点,请求失败的机器发生了FGC。

undefined 由于本人先前在排查类似的FGC问题时,经常能在Heap Dump的支配树中看到类目客户端相关对象长期占据高位,是内存占用大户。所以难免主观印象先入为主,初步以为是切换版本的过程中,在老版本数据包卸载之前,可能会存在短暂的堆内空间占用double的情况。

而启动参数配置Eden区下限为25%。可能存在 老年代常驻占用 + 新数据包 > 4G 的情况引发FGC。

因此觉得既然空间占用double不可避免,老年代常驻的堆空间短时间内又不可能显著下降,Eden区为了解决之前该应用发生Mixed GC时Eden区反常缩小导致YGC异常,又必须固定一个下限值,那只能是把容器内存规格扩大看看能否缓解

于是将应用容器基线升级到4C16G,设置在16G容器环境下,分配10G内存给堆空间,并逐步升级线上服务的容器规格。

结果花了两天时间,升级了线上大部分的容器后,查看监控,发现大规格的容器确实没有FGC了,且得益于超大的堆内存(10G),从GC监控上看甚至看不出有切包的动作。但是,RPC成功率还是会下跌(100% -> 97%),且RPC线程池线程数还是有少量上涨(50 -> ~71)。

由此可见,FGC并不是切包抖动的核心成因,背后还有更加深层次的原因需要挖掘。

容器CPU高触发自适应限流?

再次询问受影响的业务方,通过下游应用的日志发现推包抖动的时间点,发生了不少sentinel限流日志。而结构化应用里面并没有针对接口主动配置限流,只有集团内的sentinel中间件,可能因为检测到某一时间点CPU利用率过高(超过80%),自动执行限流策略。

sentinel为集团内的流控中间件,能够以流量为切入点,从限流、流量整形、熔断降级、系统自适应保护、热点防护等多个维度来帮助业务保障微服务的稳定性。

登陆发生限流的服务Provider对应机器,确实看到了限流日志。

$cat cpu-sampling.log.1 | grep "2022-05-19 03:57"  | grep -v "0.0"
2022-05-19 03:57:12.435|||应用名|CpuSampling|0||4.151785714285714|1.0|6

可以看到在当前时刻,CPU利用率来到了 415%,可见确实在该时间点存在CPU四核全打满的现象。

但是从应用监控来看,该时刻机器CPU利用率只有20%多(100%为单位)。且sentinel的cpu-sampling日志只grep出来一条CPU打满的日志。按照限流中间件20ms采样一次CPU的频率,说明这只是一个CPU尖刺,不应该有大面积的影响。笔者对限流导致RPC成功率下跌其实是抱有些许怀疑态度的,觉得应该不是这样的问题。

不管怎么说,最简单的验证方法,就是线上找一台已经换了16G内存的机器,把sentinel中间件设置为:只记录日志,不执行实际限流动作的dry run模式。看接口成功率是否会跌。按照上面的推理,CPU尖刺最长不会超过40ms,就算CPU打满40ms,也不至于导致接口有几秒钟的超时。

结果到了当天晚上,接口成功率还是掉下来,存在接口几秒钟的超时。

源码窥密,探究类目客户端切包过程中具体动作

发现单纯的提升容器规格并不能解决切包抖动的问题后,笔者复盘了一下手头上已经掌握的线索:

1. FGC不是引起切包抖动的核心原因,这是升级容器,花了钱之后得到的结论。那么,如果不花这个钱,我们能不能单纯从监控上得出结论?其实是可以的。如果目前线上容器规格完全不能满足切包所需的内存消耗,理论上集群每台机器切包的时候都会FGC,但是实际上切包过程中全集群产生的FGC量并不算多,最多在20次左右,且只有1/6的机器发生FGC,剩下的机器也只是YGC略微上涨,然后很快恢复到正常水位。而即使是YGC量上涨,也只是分钟YGC耗时从70ms涨到200ms,根本不至于把大量接口打到超时的。

2. sentinel检测到CPU尖刺,自动执行了限流,确实会拉低服务成功率。但即使把限流关闭,接口还会存在几秒钟超时,说明限流不是造成接口成功率下跌的主要原因。

3. 类目客户端到底把类目数据存在磁盘,还是存在堆里面,亦或者是在堆外内存,四处问了一圈,各有各的说法。 然而,不同的存储方式可能最终会导向完全不同的结论。为了搞清楚这个问题,最精确的方法就是去把类目客户端切包的源码读一遍。于是笔者花了点时间,

类目服务切包流程

纵观整个切包过程,都是在类目客户端自带的netty ChannelHandler回调线程中单线程执行完成的,没有任何多线程并行加载的动作。在初始化类目服务入口的时候,虽然有不少构建索引之类的看起来重计算的动作,但是由于底层是单线程执行,理论上CPU最多打到100%(一个核心),不会有打到400%的情况出现。凌晨结构化应用的业务CPU占用率很低,不到10%,理论上切包+业务不至于导致CPU打满。更不太可能引起长达几秒钟的服务不响应现象。

随着进一步梳理切包和错误发生的时间点、日志信息,一个更奇怪的现象浮出水面。

反常现象:切包完毕后接口开始超时

观察以下全链路日志:

com.taobao.idle.modulet.ItemKgraphServiceApi@getSpuPublishSearchV3~SMM 接口有长达8s的超时。注意这个错误的发生时间 2022-05-24 01:38:19.523

平时这个接口RT在400m左右,底层是使用线程池并行转调各种搜索服务,设置了1s的总体超时时间,大量try catch包裹,基本不会报错。

再看这个时间点附近的类目树日志:

可以看到, 01:38:00 的时候,类目数据就已经切换完了。01:38:14的时候,更是连旧文件都删完了。切包已经结束了。

然而,到了 01:38:27 的时候,类目服务的其中一个Logger, 从kGraphCommonFixedThreadFactory-10-thread-95 线程打印出两行日志:

2022-05-24 01:38:27.715 [kGraphCommonFixedThreadFactory-10-thread-95] [] WARN  类目服务Logger - Load the cache of std_category_property_value store finished. It takes 7820ms,cached 278 blocks.
2022-05-24 01:38:31.236 [kGraphCommonFixedThreadFactory-10-thread-95] [] WARN  类目服务Logger - Load the cache of std_category_property_value store finished. It takes 11341ms,cached 109 blocks.

而这个线程池,正是运行 getSpuPublishSearchV3 接口底层转调各种搜索的执行线程,且代码为异步执行的Future设置了1s超时,如果执行超过一秒,接口服务的代码也不会继续等待,而是直接往下执行了。

更巧的是:从01:38:19到01:38:27,整整8秒,恰好和上面监控的超时对的上。看到这里,问题似乎变得更加复杂。

1. 刚刚我们才提到,切包是在类目客户端的netty ChannelHandler线程里进行,怎么现在又跑到了业务线程池上,甚至能Hang住了接口?

2. 为什么类目服务都都跑在别的线程池上面了,且线程池超时设置1s了,为什么接口还会有8s的超时?

拨云见雾,逐个击破

其实回答问题1不难。因为日志已经打出来了,只需要全局搜 Load the cache of,就能找到唯一一个类目树打日志的地方,再往上倒着找调用栈。会发现类目属性类目属性值这两个存储是懒加载的。只有切完包之后,第一次请求读取类目的属性及属性值,才会触发 通过mmap的方式将store文件映射到Java进程的一段连续虚拟地址,并把数据从磁盘读上内存的动作。

核心代码:

MappedByteBuffer mappedBuffer = storeFile.getFileChannel().map(MapMode.READ_ONLY, segmentPosition, segmentSize); // 创建mmap映射
mappedBuffer.load(); // 把数据读上内存
Buffer buffer = FixedByteBuffer.wrap(mappedBuffer);
this.bufferCacheArray[t] = new BufferCache(buffer, ((t == 0) ? meta.getHeaderSize() : 0), blockCountThisSegment, meta.getBlockSize());
for (int i = ((t == 0) ? 0 : blockIdFlags[t - 1]); i < blockIdFlags[t]; i++) {
       getReadBlock(i); // 提前创建 blockId -> block buffer的映射
}

因此,加载完成后的日志自然是在业务线程池打的。

Page Fault的“威力”

问题2就不好回答了。因为有一个十分明显的矛盾点:本次Store加载是在业务线程池加载,对所有Future都只等待1s,1s加载不完立刻就返回了,不可能有8s的耗时。而且机器已经是16G容器,又没有FGC导致的STW。 那就只有进程级别的hang会影响到多个线程了。

为了确认是否存在进程hang,笔者再次去看自适应的cpu-sampling日志。不看不知道,一看吓一跳:

20ms采样一次CPU并打日志的线程也被hang住了!整整到了01:38:27才恢复日志打印。而这段时间正好是store在load的时间,也和接口超时时间吻合

那基本只有一种可能,那就是第一次读取mmap映射虚拟地址段的数据时,因为数据在硬盘里,还没在内存上,会触发page fault,使得JVM进程陷入内核态,执行从磁盘加载数据到内存的page fault handler,导致进程挂起直到回到用户态

参考 《Understanding the Linux kernel》一书 9.4.2 节 Handling a Faulty Address Inside the Address Space。其中有一段话是这么写的:

The handle_mm_fault( ) function returns VM_FAULT_MINOR or VM_FAULT_MAJOR if it succeeded in allocating a new page frame for the process. The value VM_FAULT_MINOR indicates that the Page Fault has been handled without blocking the current process; this kind of Page Fault is called minor fault.

The value VM_FAULT_MAJOR indicates that the Page Fault forced the current process to sleep (most likely because time was spent while filling the page frame assigned to the process with data read from disk); a Page Fault of the current process is called a major fault.

The function can also return VM_FAULT_OOM (for not enough memory) or VM_FAULT_SIGBUS (for every other error).

关注加粗的部分,在处理内存page fault事件时,如果出现了Major Page fault,那么这种Page fault会强制使得当前进程休眠(基本上发生在在需要把数据从磁盘读到进程地址空间内的页块这种耗时场景下)。

再看 MappedByteBuffer#load() 方法Javadoc:

可以看到,Javadoc上写明:Invoking this method may cause some number of page faults and I/O operations to occur.调用这个方法很可能会产生一些page fault。

那么,只要确认在load store的过程中,JVM进程产生了大量的Major Page Fault, 就能解释清楚为什么会出现跨多线程Hang的的情况了

这里笔者主要使用两个命令: pmapsar

pmap能够展示进程的所有连续地址空间,通过查看地址空间的文件引用情况,以及RSS列的值,可以表明当前连续内存空间中有多少个页面被进程引用。

在日常机器上复现场景,可以看到,在执行load store方法之前,当前进程没有引用到任何有关类目文件的虚拟地址空间。

调用查询属性及属性值的方法后,可以看到

类目树的属性、属性值store被加载上来,且RSS值 == KBytes,说明整个文件都被读到了内存里。

还有一个重要的抓手是 sar命令。sar命令可以以指定的采样率去采样当前整个操作系统的Page Fault发生指标。

可以看到,initCache过程中,触发了大量的major page fault,同时笔者连接到服务器上的Arthas命令行也hang死无任何反应。进一步验证了上述情况。

Arthas的命令行底层是termd库,纯Java实现。而Arthas又是以java agent的形式附加到目标JVM上的,如果连Arthas的控制台输入命令都没有回显了,基本上只有JVM进程整体Hang死一种可能,该现象也进一步验证了上文提及的进程陷入内核态Hang死的假设。

那为什么会hang住好几秒的时间?通过测量线上容器的IO速度,发现容器的磁盘读写速度在100MB左右,而类目属性值的store文件非常大,达到1.7G。加载一遍这个文件非常耗时。

至此,我们终于可以给出结论。

结论

结构化应用夜间类目客户端推包完成新老数据包版本切换后,存储类目属性及属性值的数据不会立即加载到内存。会等到第一个查询类目属性、属性值的请求到来才会懒加载数据到内存。由于类目属性值store非常大,达到1.7个G。在把1.7G数据整体加载到内存的过程中触发大量Major Page Fault,导致进程进入内核态处理缺页异常。而由于业务容器IO性能较差,完整读取文件十分耗时,最终导致Java进程hang住十几秒的现象。

最终解法

带着问题去咨询类目服务相关同学,他们给出的建议是升级到最新版本的客户端。笔者对比了新老版本客户端的代码,主要有两处改进。

1. 在切包之前,会去load一把store,而不是等到请求进来再懒加载。

2. load store的时候,不是一口气load整个文件,而是把文件切分为64M的小块,每次load 64M,然后sleep一会。再继续load下一块,避免一次性load超大文件块导致进程长时间处于内核态无法对外提供服务。

回过头来思考一些前文提出的问题。既然接口超时是主要是加载大文件引起的,那升16G容器还有必要吗?

笔者认为还是有必要的。原因有两点

1. 现在新版的类目客户端会在切版本、释放老包资源(包括虚拟地址空间)之前先load一把store,虽然store在堆外内存,但是仍然需要占用进程的地址空间,而且实际上还是需要把数据读上内存。因此在切包过程中,从整个Java进程视角来看,确实是会产生double的store空间占用。而目前一个类目store就要1.7G了,两份就是 3.4G。还有4G的空间划分给了JVM的堆。剩下不到1G的内存空间,8G容器内存非常吃紧,很容易在切包过程中,load新store的时候产生容器OOM导致容器被杀。

2. 虽然store数据都是存在堆外的,但是store的一些索引对象和元数据还是存在堆里面的,大约占用堆6-700M空间。而无论是新版还是旧版的类目客户端,索引都是在切包之前build的。因此在构建新包索引的时候,峰值也会产生2倍的堆内空间占用,很容易堆内存不足产生YGC甚至FGC。

总结

一个看上去表象非常像GC引起的接口超时,底层却隐藏着更深层次的原因。通过这个案例,也提醒我们,作为技术人,遇到问题,无论表象多么明显,和之前遇到过的场景多么相似,都需要避免被所谓经验蒙蔽双眼、先入为主的坏习惯。大胆推断,小心论证。多问一些为什么,才能一步一步的接近真相。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/g_Y323V4fvUKqvTGhs5VyQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237236次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8071次阅读
 目录