当我增加CPU内核数时,为什么使用Freemarker的spring webapp会变慢?

时间:2019-12-13 10:52:14

标签: java performance tomcat freemarker scalability

我正在使用Freemarker在Spring Webapp上工作,当我增加服务器上的CPU内核数量时,我遇到了性能/可伸缩性问题。

在生产中,我们的服务器具有以下配置:

  • 英特尔至强E5-2690
  • 8个物理核心
  • 使用Turbo Boost的2.9GHz至3.8GHz
  • 启用超线程

我们购买了新服务器,以使用以下配置替换旧服务器:

  • 英特尔至强金牌6254
  • 18个物理核心
  • 使用Turbo Boost的3.4GHz至4GHz
  • 禁用超线程

问题是:当我们使用500个jmeter线程加载服务器时,在具有18个物理核心的新服务器中,我们的webapp会比在具有16个内核(8个物理+超线程)的旧服务器中慢。

因此,我们使用Yourkit分析了我们的应用程序,并观察到当freemarker处理模板并写入输出时,大多数tomcat ajp线程被阻塞,试图获取tomcat SynchronizedQueue(或SynchronizedStack)上的锁:

Thread blocked on this stack

问题是:为什么要放置这些锁?我们如何避免或限制它们对性能的影响?

版本:

  • Tomcat 9.0.14
  • 春季4.3.22
  • Freemarker 2.3.28

1 个答案:

答案 0 :(得分:0)

我找到了两种解决方案来减少Webapp上明显被阻塞的线程:

  • 我禁用了freemarker自动刷新调用setAutoFlush(false)see more
  • 我优化了处理自由标记模板的方式。在此之前,我的processTemplate方法是:
public void processTemplate(Template template, Map<String, Object> model, Writer writer) throws TemplateException, IOException {
  model.put(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, new RequestContext(request, response, servletContext, model));
  template.process(model, writer);
}

每次处理模板时,都会初始化一个新的RequestContext:在调用构造函数时放置一个锁。

现在,在我的FreemarkerProcessor的构造函数中,每个请求的RequestContext仅初始化一次:

public FreeMarkerProcessor(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, freemarker.template.Configuration freemarkerConfiguration) {
  this.freemarkerConfiguration = freemarkerConfiguration;
  requestContext = new RequestContext(request, response, servletContext, new HashMap<>());
}

public void processTemplate(Template template, Map<String, Object> model, Writer writer) throws TemplateException, IOException {
  requestContext.getModel().clear();
  requestContext.getModel().putAll(model);
  model.put(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, requestContext);
  template.process(model, writer);
}