背景介绍业务介绍 在某学习APP浏览文章,客户端会将浏览的文章信息上传到服务端,服务端将浏览信息最终存储到HBase; 在某学习APP首页点击【我的】【历史】,会展示用户浏览文章的历史记录。技术介绍 服务端的服务是【阅读历史离线服务】,从metaq消费用户阅读文章的信息,解析、处理相关业务逻辑,最后存储到HBase。问题现象ECS监控 两台机器【xxxxxxxxxxxxxxxxxx6、xxxxxxxxxxxxxxxxx1】在早高峰的时候Load很高,CPU使用率正常。 metaq监控 造成消息消费的慢,每天早上都有大量消息堆积,导致用户看不到自己的阅读历史。问题分析基本情况 【阅读历史离线服务】共有x台ECS,每台ECS配置是8c16g。其中x台机器正常,2台机器不正常。排查思路找不同 分析不正常机器与正常机器有哪些差异:对比了【应用程序版本】、【应用程序配置】、【JVM配置参数】、【JDK版本】、【操作系统版本】,发现【JDK版本】不一致。 正常机器:openjdkversion1。8。0171OpenJDKRuntimeEnvironment(build1。8。0171b10)OpenJDK64BitServerVM(build25。171b10,mixedmode) 异常机器:openjdkversion1。8。0222OpenJDKRuntimeEnvironment(build1。8。0222b10)OpenJDK64BitServerVM(build25。222b10,mixedmode) 到此初步定位不同机器运行状态不一致的现象是由于【JDK版本】不一致造成的,所以将【问题机器的JDK版本】替换为【正常机器的JDK版本】问题就可以解决了。定位问题代码 但是问题的根因还需要尝试排查一下,既然是【JDK版本】不一致造成的,那么会不会是【1。8。0222】这个版本中有BUG,刚好我们写的程序触发了这个BUG? 所以接下来需要弄清楚程序运行过程中执行了哪些业务逻辑、这些业务逻辑涉及到了哪些JDKAPI,直接想到的工具是arthasprofiler,下面是抓到的热点方法。 通过对比【异常机器】与【正常机器】的热点方法,发现Runtime。getRuntime()。availableProcessors()很可疑: 业务相关代码:CompletableFutureResultcompletableFuture业务逻辑,调用hbaseclient中apicompletableFuture。whenCompleteAsync((result,t){业务逻辑处理},Pool。getSubmitPool())。exceptionally((t){业务逻辑处理})。get(); CompletableFuture相关代码:Waitsifnecessaryforthisfuturetocomplete,andthenreturnsitsresult。returntheresultvaluethrowsCancellationExceptionifthisfuturewascancelledthrowsExecutionExceptionifthisfuturecompletedexceptionallythrowsInterruptedExceptionifthecurrentthreadwasinterruptedwhilewaitingpublicTget()throwsInterruptedException,ExecutionException{Objectr;returnreportGet((rresult)null?waitingGet(true):r);}Returnsrawresultafterwaiting,ornullifinterruptibleandinterrupted。privateObjectwaitingGet(booleaninterruptible){Signallerqnull;booleanqueuedfalse;intspins1;Objectr;while((rresult)null){if(spins0)spins(Runtime。getRuntime()。availableProcessors()1)?18:0;Usebriefspinwaitonmultiprocessorselseif(spins0){if(ThreadLocalRandom。nextSecondarySeed()0)spins;}elseif(qnull)qnewSignaller(interruptible,0L,0L);elseif(!queued)queuedtryPushStack(q);elseif(interruptibleq。interruptControl0){q。threadnull;cleanStack();returnnull;}elseif(q。thread!nullresultnull){try{ForkJoinPool。managedBlock(q);}catch(InterruptedExceptionie){q。interruptControl1;}}}if(q!null){q。threadnull;if(q。interruptControl0){if(interruptible)rnull;reportinterruptionelseThread。currentThread()。interrupt();}}postComplete();returnr;}猜测验证publicclassProcessors{publicstaticvoidmain(String〔〕args){intavailableProcessorsRuntime。getRuntime()。availableProcessors();System。out。println(AvailableProcessors:availableProcessors);for(inti0;iavailableProcessors;i){ThreadtnewThread((){while(true){try{intpsRuntime。getRuntime()。availableProcessors();Thread。sleep(1L);}catch(Exceptione){e。printStackTrace();}}});t。start();}}} 将验证代码在【JDK版本】为【1。8。0222】的机器上运行,随即复现了线上问题。定位根因 那么【1。8。0222】与【1。8。0171】版本在Runtime。getRuntime()。availableProcessors()的实现上有什么差别呢?【1。8。0222】增加了容器环境的逻辑,比【1。8。0171】复杂了很多。 最后我们看看在https:bugs。openjdk。java。net的解释吧。 总结: Runtime。getRuntime()。availableProcessors()在不同JDK版本上的实现是没有问题的,CompletableFuture。waitingGet在【1。8。0222】版本上,没有测试到Runtime。getRuntime()。availableProcessors()对性能的影响,导致了性能问题。解决方法 openjdk在1。8。01911。8。0222之间的版本都存在问题,换成1。8。0191之前,或1。8。0232及以后的版本可以。问题根因 CompletableFuture。get()的实现方式在一些jdk版本存在缺陷, 详情见:〔JDK8227018〕CompletableFutureshouldnotcallRuntime。availableProcessorsonfastpathJavaBugSystem