作者:京东科技 孙亮

微电平台

微电平台是集电销、企业微信等于一体的综合智能SCRM SAAS化系统,包括多渠道办理、全客户生命周期办理、私域营销运营等主要功能,现在已经有60+京东各事务线入驻,专注于为事务供给职场外包式的一站式客户办理及一体化私域运营服务。

【微电平台】-高并发实战经验-奇葩问题解决之旅

导读

本文介绍电销系统在遇到【客户名单离线打标】问题时,从排查、重复验证到最终处理问题并额定提高50%吞吐的进程,合适一切服务端研发同学,供给生产环遇到一些复杂问题时排查思路及处理方案,正确运用京东内部例如sgm、jmq、jsf等工具抓到问题根因并彻底处理,用技能为事务开展保驾护航~

下面开始介绍电销系统实际生产环境遇到的离线回绝营销打标流程吞吐问题。

事例背景

1、概述

每日清晨18点会运用80台机器为电销系统上亿客户名单进行回绝营销打标,均匀95万名单/分钟,回绝营销jsf服务总tps约2万,tp99在100110ms,若夜间没有完结符号加工操作,会导致白天职场无法正常作业,而且存在客户骚扰危险、下降职场运营效率的问题,因外部接口依赖数量较多打标程序只能清晨发动和结束。

2、复杂度

面向事务供给千人千面的配置功能,底层根据规矩引擎设计完结,调用链路中包括众多外部接口,例如金融刷单符号、风控符号、人群画像符号、商城风控符号、商城实名符号等,包括的维度多、复杂度较高

【微电平台】-高并发实战经验-奇葩问题解决之旅

3、问题

2023年2月24日早上经过监控发现回绝营销打标没有履行完结,体现为jmq消费端tp99过高、从而下降了打标程序吞吐,经过暂时扩容、下掉“问题机器****”(天主视角:其实是程序导致的问题机器)提高吞吐,加快完结回绝营销打标。

但,为什么会频频有机器问题?为什么机器有问题会下降40%吞吐?后续怎么避免此类状况?

带着上述问题,下面敞开问题根因定位及处理之旅~

抓出暗地黑手

1、为什么几台机器出问题就会导致吞吐急剧下降?

【微电平台】-高并发实战经验-奇葩问题解决之旅

如上图所示,每有一条音讯消费报错(在本事例中是打到“问题机器”上),会本地尝试sleep并从头消费拉下来的一切音讯(本事例中jmq的batchSize=10),即每次报错产生的总耗时至少会增加一千毫秒,一共80台机器,jsf运用默许负载均衡算法,服务恳求打到4台问题机器的概率是5%,jmq一次拉下来10条音讯,只要有一条音讯命中“问题机器”就会极大下降吞吐。

【微电平台】-高并发实战经验-奇葩问题解决之旅

综上所述,少数机器有问题吞吐就会急剧下降的原因是jsf随机负载均衡算法下每个实例的命中率相同以及报错后jmq consumer重试时默许休眠1秒的机制导致。

处理:当然consumer不报错是完全能够躲避问题,那假如保证不了不报错,能够经过:

1)修正jmq的重试次数、重试延迟时刻来尽可能的削减影响

<jmq:consumer id="cusAttributionMarkConsumer" transport="jmqTransport">
<jmq:listener topic="${jmq.topic}" listener="jmqListener" retryDelay="10" maxRetryDelay="20" maxRetrys="1"/> 
</jmq:consumer>

2)修正jsf负载均衡算法

配置样例:

<jsf:consumer loadbalance="shortestresponse"/>

原理图:

【微电平台】-高并发实战经验-奇葩问题解决之旅

上图中的consumer图是从jsf wiki里摘取,上面的红字是看jsf代码提取的关键信息,总而言之便是:默许的random是完全随机算法,最快呼应时刻是根据服务恳求体现进行负载均衡,所以运用最快呼应算法能够很大程度上躲避此类问题,相似于熔断的作用(本次处理进程中也运用了jsf的实例熔断、预热能力,具体看jsf wiki,在此不过多介绍)。

2、怎么断定是实例问题、找出有问题的实例ip?

经过监控观察,耗时高的现象只存在于4台机器上,榜首反应的确是以为机器问题,但结合之前(1月份有过相似现象)的状况,觉得此事必有奇怪。

下图是榜首反应认为是机器问题的日志(对应sgm监控这台机器耗时也是接连高),下掉此类机器的确能够暂时处理问题:

【微电平台】-高并发实战经验-奇葩问题解决之旅

综上所述,当时刻段内耗时高或失败的都是某个ip,此刻能够断定该ip对应的实例有问题(例如网络、硬件等),假如是很多ip存在相似现象,断定不是机器自身的问题,本事例涉及到的问题不是机器自身问题而是程序导致的现象,持续往下看揭晓答案。

3、是什么导致机器频频假死、成为毛病机器?

经过上述剖析能够得知,问题机器报错为jsf线程池打满,机器出问题期间tps简直为0,期间有恳求过来就会报jsf线程池(非事务线程池)打满,此刻有两种可能,一是jsf线程池有问题,二是jsf线程池的线程一向被占用着,抱着信任中间件的思路,挑选可能性二持续排查。

经过行云进行如下操作:

1)dump内存对象

【微电平台】-高并发实战经验-奇葩问题解决之旅

无显着问题,内存占用也不大,符合监控上的少数gc现象,持续排查仓库

2)jstack仓库

【微电平台】-高并发实战经验-奇葩问题解决之旅

从此来看与问题机器表象共同了,根本得出结论:一切jsf线程都在waiting,所以有流量进来就会报jsf线程池满过错,而且与机器cpu、内存都很低现象相符,持续看具体栈信息,抽取两个比较有代表的jsf线程。

线程编号JSF-BZ-22000-92-T-200:

stackTrace:
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007280021f8> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.jd.jr.scg.service.common.crowd.UserCrowdHitResult.isHit(UserCrowdHitResult.java:36)
at com.jd.jr.scg.service.impl.BlacklistTempNewServiceImpl.callTimes(BlacklistTempNewServiceImpl.java:409)
at com.jd.jr.scg.service.impl.BlacklistTempNewServiceImpl.hit(BlacklistTempNewServiceImpl.java:168)

线程编号JSF-BZ-22000-92-T-199:

stackTrace:
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007286c9a68> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.jd.jr.scg.service.biz.BlacklistBiz.isBlacklist(BlacklistBiz.java:337)

揣度:线程编号JSF-BZ-22000-92-T-200在UserCrowdHitResult的36行等候,线程编号JSF-BZ-22000-92-T-199在BlacklistBiz的337行等候,查到这,根本能揣度出问题原因是线程等候,形成问题的相似代码场景是1)main线程让线程池A去履行一个使命X;2)使命X中又让同一个线程池去履行另一个使命,当第二步获取不到线程时就会一向等,然后榜首步又会一向等第二步履行完结,便是形成线程互相等候的假死现象。

小结:查到这根本能够确认问题,但因代码维护人离职以及程序扑朔迷离,此刻为验证结论先修正事务线程池A线程数:50->200(此处是为了验证没有线程等候现象时的吞吐体现),再进行验证,结论是tps会有小范围抖动,但不会呈现tps到0或是大幅下降的现象。

单机tps300~500,流量正常了,即未产生线程等候问题时能够正常供给服务,如图:

【微电平台】-高并发实战经验-奇葩问题解决之旅

印证揣度:经过仓库定位到具体代码,代码逻辑如下:

BlacklistBiz->【线程池A】blacklistTempNewService.hit
blacklistTempNewService.hit->callTimes
callTimes->userCrowdServiceClient.isHit->【线程池A】crowdIdServiceRpc.groupFacadeHit

小结:BlacklistBiz作为主线程经过线程池A履行了blacklistTempNewService.hit使命,然后blacklistTempNewService.hit中又运用线程池A履行了crowdIdServiceRpc.groupFacadeHit形成了线程等候、假死现象,与上述揣度共同,至此,问题已完结定位。

处理:办法很简单,额定新增一个线程池避免线程池嵌套运用。

4、意外收成,发现一个影响回绝营销服务功能的问题点

查看仓库信息时发现存在很多waiting to lock的信息:

【微电平台】-高并发实战经验-奇葩问题解决之旅

问题:经过上述仓库从而排查代码发现一个服务链路中的3个办法运用了同一把锁,功能不下降都怪了

【微电平台】-高并发实战经验-奇葩问题解决之旅
处理:经过引进caffeine本地缓存替换掉原来运用同步锁维护的手写本地缓存。

5、额定收成,你知道jsf线程池满的时候报RpcException客户端不会进行重试吗?

这个让我挺意外的,之前看jsf代码以及和jsf架构师交流得到的信息是:一切RpcException都会进行重试,重试的时候经过负载均衡算法从头找provider进行调用,但在实际验证进程中发现若服务端报:handlerRequest error msg:[JSF-23003]Biz thread pool of provider has bean exhausted, the server port is 22001,客户端不会建议重试,此结论最终与jsf架构师达到共同,所以此类场景想要重试,需要在客户端程序中想办法,完结比较简单,这里不贴样例了。

事情回忆

【微电平台】-高并发实战经验-奇葩问题解决之旅
处理问题后对以前的现象和相似问题做了进一步发掘,整理出了如上图的整个链路(问题代码同学早已不在、大可忽略问题人,此处从天主视角复盘整个事情),经过2月24号的问题,不但彻底处理了问题,还对影响功能的因素做了优化,最终效果有:

1、处理回绝营销jsf服务线程等候安全危险、去掉同步锁提高吞吐,tps从2万提高至3万的状况下,tp99从100ms下降至65ms

2、jmq重试等候及延迟时刻调优,躲避重试时吞吐大幅下降:tp99从1100ms下降至300ms

3、jsf负载均衡算法调优,躲避机器毛病时仍然很多恳求打到机器上,效果是服务相对安稳

最终从8点多履行完提早至5点前完结,全体时刻缩减了57%,而且即便机器呈现“问题”也不会大幅下降全体吞吐,收益比较显着。

优化后的运行图如下:

【微电平台】-高并发实战经验-奇葩问题解决之旅

写在最后

微电平台虽说不在黄金链路,但场景复杂度(事务复杂度、rpa等机器人用户复杂度)以及流量量级使咱们经常面对各种挑战,好在咱们都处理了,这里共勉一句话:“在行进的路上总会有各种意想不到的状况,可是,都会拨云见日”。

后面的路,持续尽力奔跑,祝每位尽力的研发同学都能成长为靠谱的架构师,加油~