Tomcat集群环境的架构问题

时间:2012-12-23 11:57:42

标签: java tomcat web-applications architecture cluster-computing

我正在开发一个我们拥有身份验证机制的项目。我们在认证机制中遵循以下步骤。

  1. 用户打开浏览器并在文本框中输入他/她的电子邮件,然后单击登录按钮。
  2. 请求转到服务器。我们生成一个随机字符串(例如,123456)并向用户的Android / iPhone发送通知,并使用wait()方法使当前线程等待。
  3. 用户在他/她的手机上输入密码,然后点击他/她手机上的提交按钮。
  4. 一旦用户点击提交按钮,我们就会在服务器上点击Web服务并传递先前生成的字符串(例如,123456)和密码。
  5. 如果密码对于先前输入的电子邮件是正确的,我们将notify()方法调用到先前等待的线程并发送成功作为响应,并且用户进入我们的系统。
  6. 如果密码对于先前输入的电子邮件不正确,我们将notify()方法调用到先前等待的线程并发送 failed 作为响应,并向用户显示无效的凭据消息
  7. 一切正常,但最近我们搬到了集群环境。我们发现即使在用户回复并且无限制的等待时间之后,某些线程也不会得到通知。

    对于服务器,我们使用的是Tomcat 5.5,我们正在关注 The Apache Tomcat 5.5 Servlet/JSP Container 来创建tomcat集群环境。

    答案::可能的问题和解决方案

    可能的问题是群集环境中的多个JVM。现在,我们还将聚集的Tomcat URL与生成的字符串一起发送到用户Android应用程序。

    当用户点击回复按钮时,我们将发送生成的字符串以及聚集的Tomcat URL,因此在这种情况下,两个请求都将转到同一个JVM,并且它可以正常工作。

    但我想知道是否有针对上述问题的单一解决方案。

    此解决方案存在问题。 如果群集Tomcat崩溃会发生什么?负载均衡器会向第二个群集Tomcat发送请求,同样会出现同样的问题。

10 个答案:

答案 0 :(得分:9)

您遇到问题的根本原因是Java EE被设计为以不同的方式工作 - 尝试阻止/等待服务线程是重要的禁忌之一。我首先说明这个问题的原因,以及之后如何解决问题。

Java EE(Web和EJB层)旨在扩展到非常大的规模(群集中的数百台计算机)。但是,为了做到这一点,设计师必须做出以下假设,这些假设是对如何编码的具体限制:

  • 交易是:

    1. 短暂生活(例如,不要阻止或等待大于一秒钟的时间)
    2. 彼此独立(例如,线程之间没有通信)
    3. 对于EJB,由容器管理
  • 所有用户状态都保存在特定的数据存储容器中,包括:

    1. 通过例如JDBC访问的数据存储。您可以使用传统的SQL数据库或NoSQL后端
    2. 有状态会话bean,如果使用EJB。将它们视为将其字段持久保存到数据库的Java Bean。有状态会话bean由容器管理
    3. Web session这是一个键值存储(有点像NoSQL数据库,但没有扩展或搜索功能),可以在特定用户的会话中保留数据。它由Java EE容器管理,具有以下属性:

      1. 如果节点在群集中崩溃,它将自动重定位
      2. 用户可以拥有多个当前网络会话(即在两个不同的浏览器上)
      3. 当用户通过注销结束会话或者会话处于非活动状态的时间超过可配置超时时,Web会话结束。
      4. 存储的所有值必须可序列化,以便在群集中的节点之间保留或传输。

如果我们遵循这些规则,Java EE容器可以成功管理群集,包括关闭节点,启动新节点和迁移用户会话,而无需任何特定的开发人员代码。开发人员编写图形界面和业务逻辑 - 所有的“管道”和#39;由可配置的容器功能管理。

此外,在运行时,Java EE容器可以通过一些非常复杂的软件进行监视和管理,这些软件可以跟踪实时系统上的应用程序性能和行为问题。

< snark>嗯,这就是理论。实践表明,错过了非常重要的限制,导致AOSP和代码注入技术,但这是另一个故事< / snark>

[围绕这个网络进行了很多讨论。关注EJB的是:Why is spawning threads in Java EE container discouraged?对于像Tomcat这样的Web容器,情况完全相同。

对于这篇文章感到抱歉 - 但这对你的问题很重要。由于线程的限制,您不应阻止Web请求等待另一个稍后的请求。

当前设计的另一个问题是,如果用户断开网络连接,电量耗尽,或者只是决定放弃,会发生什么?想必你会超时,但过了多久?对某些客户来说,这可能会导致满意度问题。如果超时太长,您可能最终阻止Tomcat中的所有工作线程,服务器将冻结。这会使您的组织面临拒绝服务攻击。

编辑:在发布更详细的算法说明后,改进了建议。

尽管上面讨论了阻止Web工作者线程的错误做法以及可能的拒绝服务,但很明显,用户会看到一个小时间窗口,在该窗口中对通知作出反应Android手机,这可以保持相当小,以增强安全性。这个时间窗口也可以保持在Tomcat超时之下以便响应。因此可以使用线程阻塞方法。

有两种方法可以解决此问题:

  1. 将解决方案的重点更改为客户端 - 使用浏览器上的Javascript轮询服务器
  2. 群集中节点之间的通信,允许从Android应用程序接收授权响应的节点取消阻止阻止servlet响应的节点。
  3. 对于方法1,浏览器通过Javascript轮询服务器,并对Tomcat上的Web服务进行AJAX调用;如果Android应用程序经过身份验证,则AJAX调用将返回True。优点:客户端,服务器上的最小实现,服务器上没有线程阻塞。缺点:在等待期间,您必须经常拨打电话(可能是一秒钟 - 用户不会注意到这种延迟),这相当于很多的呼叫以及服务器上的一些额外负载。 / p>

    对于方法2,还有选择:

    1. 使用Object.wait()阻止线程,可选择在共享数据存储中存储节点ID,IP或其他标识符:如果是,则接收Android应用程序授权的节点需要:

      1. 找到当前阻止或广播到群集中所有节点的节点
      2. 对于上面1.中的每个节点,发送一条消息,标识要取消阻止的用户会话。该消息可以通过以下方式发送:

        1. 在每个节点上都有一个仅供内部使用的servlet - 由执行Android应用程序授权的servlet调用。内部servlet将在正确的线程上调用Object.notify
        2. 使用JMS pub-sub消息队列向群集的所有成员广播。每个节点都是订阅者,收到通知后将在正确的线程上调用Object.notify()
    2. 轮询数据存储,直到线程被授权继续:在这种情况下,所有Android应用程序需要做的是将状态保存在SQL DB中

答案 1 :(得分:1)

使用wait / notify可能很棘手。请记住,任何线程都可以随时暂停。因此,在等待之前可以调用notify,在这种情况下,wait将永远阻塞。

在您的情况下,我不希望这样,因为您涉及用户交互。但是对于您正在进行的同步类型,请尝试使用信号量。创建一个0(零)数量的信号量。等待线程调用acquire(),它将阻塞,直到另一个线程调用release()。

以这种方式使用Semaphore比您描述的任务等待/通知更加健壮。

答案 2 :(得分:1)

在分析了您的问题之后,我得出结论,确切问题是群集环境中的多个JVM。

答案 3 :(得分:1)

确切的问题是由于群集环境。这两个请求都不会转到同一个JVM。但我们知道,当前一个线程在等待时,普通/简单通知在同一个JVM上工作。

您应该尝试执行这两个请求(第一个请求,当用户从Android应用程序回复时的第二个请求)。

答案 4 :(得分:1)

群集部署意味着群集中的任何节点都可以接收任何响应。

使用线程进行Web应用程序的等待/通知可能会累积大量可能无法通知的线程,这些线程可能会泄漏内存或创建大量被阻塞的线程。这最终可能会影响服务器的可靠性。

更强大的解决方案是将请求发送到Android应用程序并存储用户请求的当前状态以供稍后处理并完成HTTP请求。要存储您可以考虑的状态:

  • 所有tomcat节点连接到
  • 一种java缓存解决方案,可以在
  • 等tomcat节点上运行

tomcat集群中的所有节点都可以看到此状态。

当Android应用程序的回复到达另一个节点时,恢复线程正在执行的操作的状态并继续在该节点上进行处理。

如果应用程序的UI正在等待来自服务器的响应,您可以考虑使用请求从服务器轮询响应状态。处理android app响应的节点不需要与处理UI请求的节点相同。

答案 5 :(得分:1)

考虑使用内存网格,以便群集中的实例可以共享状态。我们使用Hazelcast在实例之间共享数据,因此如果响应到达不同的实例,它仍然可以处理它。

E.g。您可以使用值为1的分布式倒计时锁存器来设置发送消息后等待的线程,当响应从客户端到达单独的实例时它可以减少,该实例可以将锁存器减少到0以便运行第一个线程

答案 6 :(得分:1)

在Web服务环境中使用Thread.wait是一个巨大的错误。相反,维护一个用户/令牌对的数据库并定期使它们到期。

如果需要群集,请使用可群集的数据库。我会推荐像memcached这样的东西,因为它在内存中(和快速)并且开销很低(键/值对很简单,所以你不需要RDBMS等)。 memcached已经为你处理了令牌的到期,所以它看起来非常合适。

我认为用户名 - >令牌 - >密码策略是不必要的,特别是因为您有两个不同的组件共享相同的双因素身份验证责任。我认为您可以进一步降低复杂性,减少用户的混淆,并节省一些短信费用。

与您的网络服务的互动非常简单:

  1. 用户使用用户名+密码登录您的网站
  2. 如果主要身份验证(用户名/密码)成功,请生成令牌并将userid = token插入memcached
  3. 将令牌发送到用户的手机
  4. 向用户显示“输入令牌”页面
  5. 用户通过电话接收令牌并将其输入表格
  6. 根据用户的id从memcached中获取令牌值。如果匹配,则使memcached中的令牌到期并考虑第二因素成功
  7. 在您要在memcached
  8. 中设置的任何时间后,令牌将自动过期

    上述解决方案没有线程问题,它可以扩展到支持您自己软件所需的JVM数量。

答案 7 :(得分:1)

我很害怕,但线程无法在经典Java EE群集上迁移。

您必须重新考虑您的体系结构以实现不同的等待/通知(无连接)。

或者,您可以尝试使用terracotta.org。看起来这允许在多台计算机上集群整个JVM进程。也许这是你唯一的解决方案。

阅读 Introduction to OpenTerracotta 中的简短介绍。

答案 8 :(得分:0)

我想问题是,你的第一个线程在JVM 1中向用户的Android应用程序发送通知,当用户回复时,控件转到JVM 2.这就是主要问题。

不知何故,两个线程都可以访问同一个JVM来应用等待和通知逻辑。

答案 9 :(得分:0)

解决方案:

为所有等待的线程创建单个联系点。因此,在集群环境中,所有线程都将在第三个JVM(单点联系)上等待,因此以这种方式所有请求(任何集群Tomcat)将联系同一个JVM以等待并通知逻辑,因此没有线程会等待无限的时间。如果有回复,那么如果同一个对象已经等待并且第二次被通知,则会通知线程。