调优历程: 我是怎样将一个系统的性能提高20倍的?

调优历程: 我是怎样将一个系统的性能提高20倍的?
最近从米国接收一个新的项目, 项目使用Spring + CXF 处理遵循行业规范的消息,并根据一定的规则将消息转发给其它的一个或者多个产品。
因为刚接手, 先对其进行功能的测试和测试其benchmark。 结果Beanchmark在AWS x3.xlarge机器上10个并发只能达到几十个TPS。
基于我前面的文章的对tomcat和其它框架的性能数据, 可以很自信的说, 这不科学。
经过一番紧张的基于性能考虑的调整,单台服务器终于达到了100个并发每秒能处理3000 多个的请求。

本篇文章不涉及公司内部的业务,仅仅从公开的通用的技术架构上讲述我是如何提高性能的。
本篇文章不会从产品的角度讨论调优前的性能是否合理。 这里假定它不是合理的,客户肯定不接受。

有没有提升的空间?

遇到这种现象, 首先考虑的一点是, 系统还有没有可以提升的空间?也就是说,基于当前的架构设计,系统是否的的确确只能支持当前的性能。
判断有没有提升的空间, 我们首先要review一下当前系统的架构, 并对此架构的性能基准Benchmark有一个粗略的估计, 再考虑业务逻辑对性能的影响,与当前的系统性能做一比较。

当前此系统基于Tomcat + Spring + CXF提供RESTful web service, 客户通过web services进行消息的接发。 此系统和上游系统也是通过CXF WebClient通过RESTful API进行消息的接发。 业务逻辑比较少, 但是会基于JAXB进行消息的marshall/unmarshall。

基于前面的文章, 一个最简单的Tomcat性能能达到4500 TPS, Spring MVC也能达到3000多 TPS。 另外google JAXB性能的测试,如JAXB, SAX, DOM Performance, XML unmarshalling benchmark in Java: JAXB vs STax vs Woodstox, Comparison of JAXB XML and JSON Serialization Performance, JAXB marshall/unmarshall 一个java对象也应小于1ms。
所以, 考虑到性能的影响, 这套系统的性能怎么也得能达到1000 TPS以上。
可见,此系统必然哪里出现了性能的问题。

在测试使用dstat进行监控, 内容, 网络没有问题, CPU占用非常高 (>70%)。 top -H可以看到几个JAVA 线程占用特别高。 jstack -F <pid>可以看到好多个线程都忙于JAXB 解析XML。 另外Tomcat taskQueue也处在lock状态。

寻找可以提升的点

基于以上分析, 并且分析程序的代码, 可以确定以下几个地方可以提升性能。

  • CXF配置JAXB marshalling/unmarshalling XML需要提升
  • CXF生成CXF WebClient 需要提升
  • 数据库访问需要cache
  • log日志需要优化

性能提升

先从简单的入手。

log 优化

将程序中的log一些级别如数据库访问的log级别设置为debug。
debug级别时避免不必要的字符串连接。

数据库cache

使用Spring cache框架为访问频繁的数据库加上Cache annotation。 使用simple-spring-memcached 作为底层的cache 实现。

提升CXF生成CXF WebClient的性能

1.之前每发一个request,都会生成一个webclient,这是没必要的, 生成和配置一个webclient非常耗时间。 对于相同的url请求, 我们可以将webclient缓存起来。 使用guava cache框架的callback方式。

1
2
3
4
public Cache<String, WebClient> webClients = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build();
1
2
3
4
5
6
webClients.get(webClientKey, new Callable<WebClient>() {
@Override
public WebClient call() throws Exception {
return createNewWebClient(......);
}
});

2.同时,我们可以设置WebClient超时时间等。

1
2
3
ClientConfiguration config = WebClient.getConfig(webClient);
HTTPConduit conduit = config.getHttpConduit();
......

CXF 配置 JAXB binding

简单的WebClient webClient = WebClient.create(uri)可以得到一个webclient。 但是我们需要对data binding进行设置。 所以使用JAXRSClientFactoryBean创建webclient。
并使用pooled JAXBDataBinding提高性能。
请参考:
How can i improve JaxB performance 提到;

  • You should avoid creating the same JAXBContext over and over. JAXBContext is thread safe and should be reused to improve performance.
  • Marshaller/Unmarshaller are not thread safe, but are quick to create. It's not as big a deal to reuse them.

Performance and thread-safety:
这篇文章虽然是非官方的,但是都是基于JAXB users forum整理的,非常的详细全面。 它也提到了JAXB性能提升的方式:

  1. 单例的JAXBContext
  2. 考虑线程安全: If you really care about the performance, and/or your application is going to read a lot of small documents, then creating Unmarshaller could be relatively an expensive operation. In that case, consider pooling Unmarshaller objects. Different threads may reuse one Unmarshaller instance, as long as you don't use one instance from two threads at the same time.

经过以上的性能的提升, 现在系统可以达到3000 TPS, 基本可以满足客户性能的需求。