公司某个业务总是发现机器CPU偏高,要求运维扩容,结果扩容后,过了一段时间CPU还是涨了上来,又要求扩容。运维觉得老是这么扩容不是办法,就拉我一起看看问题出在哪儿。

问题定位

在另外一篇文章里,介绍了如何使用perf排查cpu偏高的问题,接到问题后,马上使用perf map agent看了看,很明显就找出来耗CPU的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Samples: 1M of event 'cpu-clock', Event count (approx.): 23964712980
40.41% perf-31427.map [.] Lcom/weidian/wdp/slf4j/utils/CommonUtil;::clearMethodPerformanceMap ◆
38.06% perf-31427.map [.] Lcom/weidian/wdp/slf4j/utils/CommonUtil;::getResourceInvokeInfo ▒
1.80% libjvm.so [.] StringTable::intern(Handle, unsigned short*, int, Thread*) ▒
1.17% libjvm.so [.] java_lang_String::equals(oopDesc*, unsigned short*, int) ▒
1.01% libjvm.so [.] Symbol::as_klass_external_name() const ▒
0.71% libjvm.so [.] Method::line_number_from_bci(int) const ▒
0.70% libjvm.so [.] UTF8::convert_to_unicode(char const*, unsigned short*, int) ▒
0.53% [kernel] [k] _spin_unlock_irqrestore ▒
0.43% libjvm.so [.] UTF8::unicode_length(char const*) ▒
0.40% libjvm.so [.] java_lang_StackTraceElement::create(Handle, int, int, int, int, Thread*) ▒
0.35% libjvm.so [.] BacktraceBuilder::push(Method*, int, Thread*) ▒
0.34% libjvm.so [.] java_lang_Throwable::fill_in_stack_trace(Handle, methodHandle, Thread*) ▒
0.31% libjvm.so [.] InstanceKlass::method_with_orig_idnum(int, int) ▒
0.29% libjvm.so [.] UTF8::unicode_length(char const*, int) ▒
0.28% libjvm.so [.] CollectedHeap::common_mem_allocate_init(KlassHandle, unsigned long, Thread*) ▒
0.26% [kernel] [k] iowrite16 ▒
0.23% libjvm.so [.] Symbol::as_unicode(int&) const ▒
0.21% libpthread-2.12.so [.] pthread_getspecific ▒
0.20% libjvm.so [.] CodeHeap::find_start(void*) const ▒
0.20% perf-31427.map [.] jlong_disjoint_arraycopy ▒
0.20% perf-31427.map [.] jshort_disjoint_arraycopy ▒
0.19% libjvm.so [.] resource_allocate_bytes(unsigned long, AllocFailStrategy::AllocFailEnum) ▒
0.19% libjvm.so [.] objArrayOopDesc::obj_at(int) const

然后,看了具体的代码,发现代码里面有一个Map,这个Map里面存放了每次请求的打点,但是,有地方误用了这个map,导致请求完之后,打点的数据没有清理,结果Map越来越大,每次遍历map也越来越耗CPU。

一点思考

问题的定位比较简单,花了不到半个小时。 但是,事后跟运维聊起来,觉得整个过程还是要吸收一下教训。

CPU偏高的这个问题,在线上已经持续了好几周了,业务的机器扩容了将近三分之一。而如果这个问题不解决,业务还要继续扩容下去。运维中间要求业务排查,但是没有找出问题,因为担心影响其他业务,于是只能先扩容。

程序开发人员平常思考的方向都是快速响应业务,保证接口返回正确;但是,从整个公司从面,一个很小的修改,可能会节省很大的成本(可以降低1/3的机器数),所以,一个好的程序开发人员,一方面要面向客户优化接口,保证业务的正确和稳定,另一方面,也要积极面向运维优化自己的项目,用最小的成本满足客户的需求。