在集群环境中访问 Mojarra JSF Flash 时线程阻塞

Rob*_*sen 6 java jsf jboss infinispan mojarra

我在最近从 JBoss EAP 6 升级到 JBoss EAP 7.0.4 的双节点 JBoss 集群上运行大型 Java EE 7 应用程序。应用程序间歇性地遇到访问变得非常缓慢的问题,直至应用程序变得难以访问。几分钟后,问题自动解决,操作恢复正常。

线程转储分析表明该事件是由以下行为引起的:

  1. 一个线程A试图写入会话缓存,但必须等待获得锁的缓存条目。它被置于TIMED_WAITING状态。
  2. 一个线程B试图获得JSFFlash同一会话。这样做时,它会尝试更新会话,但由于线程 A已经锁定了会话,因此它被置于BLOCKED状态。
  3. 所有 试图获取 JSF (对于任何会话)的线程 C都被置于BLOCKED状态,从而使应用程序无响应。Flash

第 1 步和第 2 步是由我们使用同步分布式 Infinispan 会话缓存这一事实引起的,并且不会真正引起问题,因为锁定是一个用户会话的本地锁定。然而,第 3 步非常有问题,因为对一个用户会话的阻止更新会突然影响所有用户

所有线程 C的堆栈跟踪都是相同的:

stackTrace:
java.lang.Thread.State: BLOCKED (on object monitor) at com.sun.faces.context.flash.ELFlash.getFlash(ELFlash.java:318)

  • 等待锁定 <0x00007ef80a75e260> (一个io.undertow.servlet.spec.ServletContextImpl

现在这对我来说非常令人惊讶。为什么会试图获得JSF闪光灯的所有请求被阻塞ServletContext首位,而其有望成为全球的应用程序?原因可以在 MojarraELFlash源代码中找到(第 318 行包含synchronized语句):

if (appMap.get(EnableDistributable.getQualifiedName()) != null) {
    synchronized (extContext.getContext()) {
        if (extContext.getSession(false) != null) {
            SessionHelper sessionHelper = SessionHelper.getInstance(extContext);
            if (sessionHelper == null) {
                sessionHelper = new SessionHelper();
            }
            sessionHelper.update(extContext, flash);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

简而言之,这里发生的事情是在 Mojarra 2.2.x 开发过程中的某个时刻,开发人员决定如果 JSFFlash在分布式环境中运行,则Flash需要将状态存储在会话中(如果存在),以便它可以跨集群复制。

虽然我不一定对此有异议,但我有以下问题:

  1. 在全球同步背后的原因是什么ServletContext?这似乎过于宽泛,可能会阻止大量请求。
  2. 有什么方法可以改变这种行为,特别是对于Flash? 我仔细研究了源代码,但除了<distributable/>从 中删除web.xml或可能取消设置com.sun.faces.enableDistributable上下文参数之外,似乎找不到其他方法。这两个选项的影响对于我的用例来说太大了。

更新

我在 Mojarra 问题跟踪器上打开了一个关于此行为的 GitHub 问题 ( #4376 )。