javacpu问题排查步骤(Java程序CPU)

我们线上的java系统,时不时会发生 CPU 负载过高的现象,今天小编就来说说关于javacpu问题排查步骤?下面更多详细答案一起来看看吧!

javacpu问题排查步骤(Java程序CPU)

javacpu问题排查步骤

问题现象:CPU 负载过高

我们线上的java系统,时不时会发生 CPU 负载过高的现象。

问题排查

应对 Java 程序导致的 CPU 使用率过高这一问题,GitHub 上有现成的解决方案:show-busy-java-threads。

https://github.com/oldratlee/useful-scripts wget --no-check-certificate https://github.com/oldratlee/useful-scripts/archive/release-2.x.zip unzip release-2.x.zip

登录上机器,在 CPU 使用率高时候,执行 show-busy-java-threads 脚本

./show-busy-java-threads

摘选其中的一些输出如下:

The stack of busy(25.0%) thread(20239/0x4f0f) of java process(248927) of user(jenkins): "Handling GET /job/jenkins-test-job/api/json from 172.168.1.1 : qtp1641808846-3127" #3127 prio=5 os_prio=0 tid=0x00007f7380014000 nid=0x4f0f runnable [0x00007f722c392000] java.lang.Thread.State: RUNNABLE at java.util.Arrays.copyOfRange(Arrays.java:3664) at java.lang.String.<init>(String.java:207) at java.lang.String.substring(String.java:1933) at net.sf.json.util.JSONTokener.matches(JSONTokener.java:110) at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:912) at net.sf.json.JSONObject.fromObject(JSONObject.java:156) at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348) at net.sf.json.JSONArray._fromJSONTokener(JSONArray.java:1131) at net.sf.json.JSONArray.fromObject(JSONArray.java:125) at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:351) at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955) at net.sf.json.JSONObject.fromObject(JSONObject.java:156) at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348) at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955) at net.sf.json.JSONObject.fromObject(JSONObject.java:156) at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348) at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955) at net.sf.json.JSONObject.fromObject(JSONObject.java:156) at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348) at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955) at net.sf.json.JSONObject._fromString(JSONObject.java:1145) at net.sf.json.JSONObject.fromObject(JSONObject.java:162) at net.sf.json.JSONObject.fromObject(JSONObject.java:132) at sam.Sam.sendRequestReturnJson(Sam.java:517) at sam.Sam.getPermissionByUser(Sam.java:225) at sam.Sam.checkUserPermissionLocal(Sam.java:243) at com.michelin.cio.hudson.plugins.rolestrategy.PermissionCache.getPermissionSam(RoleMap.java:155) at com.michelin.cio.hudson.plugins.rolestrategy.PermissionCache.getPermission(RoleMap.java:106) at com.michelin.cio.hudson.plugins.rolestrategy.RoleMap.hasPermission(RoleMap.java:220) at com.michelin.cio.hudson.plugins.rolestrategy.RoleMap.access$000(RoleMap.java:166) at com.michelin.cio.hudson.plugins.rolestrategy.RoleMap$AclImpl.hasPermission(RoleMap.java:569) at hudson.security.SidACL._hasPermission(SidACL.java:70)

从上面的输出可以看到,25.0% 的 CPU 资源在处理 Handling GET/job/jenkins-test-job/api/json from 172.168.1.1 这个请求。

运维同学根据这个 ip ,定位到发起请求的是某同学 A。这个同学在跑一些定时任务,定时拉取 job 的执行结果。

问题是当我直接访问这个接口:/job/jenkins-test-job/api/json 时,返回并不慢,几乎很快就可以返回。问题应该不是这个接口的问题。

我们接着从 ./show-busy-java-threads 输出往下看:看到其中有问题的调用栈:

at net.sf.json.JSONObject.fromObject(JSONObject.java:132) at sam.Sam.sendRequestReturnJson(Sam.java:517) at sam.Sam.getPermissionByUser(Sam.java:225) at sam.Sam.checkUserPermissionLocal(Sam.java:243)

看起来是这个 Sam 校验用户权限导致的 CPU 使用率过高,而接着看上面的代码net.sf.json.JSONObject.fromObject,这个是在做 json 的反序列化。

通常来说,json 的序列化、反序列化都是比较费 CPU 的,更糟糕的是,这里用到的 json 序列化框架是 net.sf.json,而不是 Java 常用的 jackson 和 gson 等。

直觉告诉我,肯定是这个 net.sf.json 反序列化引起的 CPU 使用率过高问题。

备注:

通过跟之前维护 jenkins 的同学了解到,他们基于 role-strategy 插件,重写了 jenkins 权限验证逻辑,用的就是 Sam 权限。翻看 sam 权限插件的代码,确实有用 net.sf.json 做 json 反序列化。

到这里,定位到大概率是 Sam 权限插件的 net.sf.json 反序列化引起的问题。

问题复现

为了验证这个问题,我们拿到 Sam 权限插件的代码。找到出问题的关键代码:

public void getPermissionByUser(String email) { JSONObject params = new JSONObject(); params.put("user_email", email); params.put("subsystem_id", SAM_JENKINS_SUM_SYSTEM_ID); JSONObject res = sendRequestReturnJson(URL, "GET", params); if (res.get("success").equals(true)) { cacheUserPermission(params.getString("user_email"), res.getJSONObject("permission").getJSONObject(email).getJSONObject("SERVICE")); } } public static JSONObject sendRequestReturnJson(String endpoint, String method, JSONObject params) { if (method.equals("POST")) { return JSONObject.fromObject(sendPostRequest(endpoint, params)); } else if (method.equals("GET")) { return JSONObject.fromObject(sendGetRequest(endpoint, params)); } return new JSONObject(); }

可以看到,这段代码会根据用户邮箱,发送 http 请求调用 Sam 系统,获取用户的权限数据,然后将数据反序列化成 JSONObject,即: JSONObject.fromObject(sendGetRequest(endpoint, params, token))

在本地,通过复现 A 同学的请求,发现这个请求确实比较慢,而且费 CPU。通过 debug 得知,这个用户返回的 json 数据有 1M 左右,json 反序列化 CPU 打满。

而通过其他用户请求,发现处理很快,返回的 json 数据也比较小。

到这里,确认就是 net.sf.json 框架的反序列化性能问题,引起的 CPU 使用率过高。我们需要替换成其他高性能的 json 序列化框架。

备选有:gson、jackson、fastjson等。fastjson 因为经常出安全漏洞,暂不考虑,我们考虑从 gson、jackson 选择一个。

替换成 gson

将之前的代码替换成 gson,代码如下:

public void getPermissionByUser(String email) { JSONObject params = new JSONObject(); params.put("user_email", email); params.put("subsystem_id", SAM_JENKINS_SUM_SYSTEM_ID); JsonObject res = sendRequestReturnJsonV2(URL, "GET", params); if (res.get("success").getAsBoolean()) { cacheUserPermission(params.getString("user_email"), res.getAsJsonObject("permission").getAsJsonObject(email).getAsJsonObject("SERVICE")); } } public static JsonObject sendRequestReturnJsonV2(String endpoint, String method, JSONObject params) throws IOException { if (method.equals("POST")) { return GSON.fromJson(sendPostRequest(endpoint, params, token), JsonObject.class); } else if (method.equals("GET")) { return GSON.fromJson(sendGetRequest(endpoint, params, token), JsonObject.class); } return new JsonObject(); }

重新编译权限插件后上线,再次查看 CPU 负载监控,发现 CPU 负载确实降下来了(05/13晚上 0 点左右上线的)。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页