作者:京东科技 孙亮

微电平台

微电平台是集电销、企业微信等于一体的综合智能SCRM SAAS化零碎,涵盖多渠道治理、全客户生命周期治理、私域营销经营等次要性能,目前曾经有60+京东各业务线入驻,专一于为业务提供职场外包式的一站式客户治理及一体化私域经营服务。

导读

本文介绍电销零碎在遇到【客户名单离线打标】问题时,从排查、重复验证到最终解决问题并额定晋升50%吞吐的过程,适宜所有服务端研发同学,提供生产环遇到一些简单问题时排查思路及解决方案,正确应用京东外部例如sgm、jmq、jsf等工具抓到问题根因并彻底解决,用技术为业务倒退保驾护航~

上面开始介绍电销零碎理论生产环境遇到的离线回绝营销打标流程吞吐问题。

案例背景

1、概述

每日凌晨1~8点会应用80台机器为电销零碎上亿客户名单进行回绝营销打标,均匀95万名单/分钟,回绝营销jsf服务总tps约2万,tp99在100~110ms,若夜间没有实现标记加工操作,会导致白天职场无奈失常作业,并且存在客户骚扰隐患、升高职场经营效率的问题,因内部接口依赖数量较多打标程序只能凌晨启动和完结。

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,流量失常了,即**未产生线程期待问题时能够失常提供服务**,如图:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/261c1e2d0bf74403ab25a953182804f5~tplv-k3u1fbpfcp-zoom-1.image)**印证推断**:通过堆栈定位到具体代码,代码逻辑如下:

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**的信息:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c6336fa9fff4a44b5eefcca26e75db3~tplv-k3u1fbpfcp-zoom-1.image)**问题**:通过上述堆栈进而排查代码发现一个服务链路中的3个办法应用了**同一把锁**,性能不升高都怪了 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/157d2e94214f414992fd4a5bab6e889c~tplv-k3u1fbpfcp-zoom-1.image) **解决**:通过引入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架构师达成统一,所以此类场景想要重试,须要在客户端程序中想方法,实现比较简单,这里不贴样例了。# 事件回顾![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/77d21de841d84f2d9c788f72dd9db423~tplv-k3u1fbpfcp-zoom-1.image) 解决问题后对以前的景象和相似问题做了进一步开掘,梳理出了如上图的整个链路(问题代码同学早已不在、大可疏忽问题人,此处从上帝视角复盘整个事件),通过2月24号的问题,岂但彻底解决了问题,还对影响性能的因素做了优化,最终成果有:1、解决回绝营销jsf服务线程期待安全隐患、去掉同步锁晋升吞吐,tps从**2万晋升至3万**的状况下,tp99从**100ms升高至65ms**;2、jmq重试期待及延迟时间调优,躲避重试时吞吐大幅升高:tp99从**1100ms升高至300ms**;3、jsf负载平衡算法调优,躲避机器故障时依然大量申请打到机器上,成果是服务绝对**稳固**;**最终从8点多执行完提前至5点前实现,整体工夫缩减了57%,并且即便机器呈现“问题”也不会大幅升高整体吞吐,收益比拟显著。**优化后的运行图如下:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1690b5abf7ef4e5cb1e956b72d1e1263~tplv-k3u1fbpfcp-zoom-1.image)# 写在最初微电平台虽说不在黄金链路,但场景复杂度(业务复杂度、rpa等机器人用户复杂度)以及流量量级使咱们常常面临各种挑战,好在咱们都解决了,这里共勉一句话:“**在后退的路上总会有各种意想不到的状况,然而,都会拨云见日**”。