我正在开发一个我们拥有身份验证机制的项目。我们在认证机制中遵循以下步骤。
wait()
方法使当前线程等待。notify()
方法调用到先前等待的线程并发送成功作为响应,并且用户进入我们的系统。 notify()
方法调用到先前等待的线程并发送 failed 作为响应,并向用户显示无效的凭据消息一切正常,但最近我们搬到了集群环境。我们发现即使在用户回复并且无限制的等待时间之后,某些线程也不会得到通知。
对于服务器,我们使用的是Tomcat 5.5,我们正在关注 The Apache Tomcat 5.5 Servlet/JSP Container 来创建tomcat集群环境。
答案::可能的问题和解决方案
可能的问题是群集环境中的多个JVM。现在,我们还将聚集的Tomcat URL与生成的字符串一起发送到用户Android应用程序。
当用户点击回复按钮时,我们将发送生成的字符串以及聚集的Tomcat URL,因此在这种情况下,两个请求都将转到同一个JVM,并且它可以正常工作。
但我想知道是否有针对上述问题的单一解决方案。
此解决方案存在问题。 如果群集Tomcat崩溃会发生什么?负载均衡器会向第二个群集Tomcat发送请求,同样会出现同样的问题。
答案 0 :(得分:9)
您遇到问题的根本原因是Java EE被设计为以不同的方式工作 - 尝试阻止/等待服务线程是重要的禁忌之一。我首先说明这个问题的原因,以及之后如何解决问题。
Java EE(Web和EJB层)旨在扩展到非常大的规模(群集中的数百台计算机)。但是,为了做到这一点,设计师必须做出以下假设,这些假设是对如何编码的具体限制:
交易是:
所有用户状态都保存在特定的数据存储容器中,包括:
Web session这是一个键值存储(有点像NoSQL数据库,但没有扩展或搜索功能),可以在特定用户的会话中保留数据。它由Java EE容器管理,具有以下属性:
如果我们遵循这些规则,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轮询服务器,并对Tomcat上的Web服务进行AJAX调用;如果Android应用程序经过身份验证,则AJAX调用将返回True
。优点:客户端,服务器上的最小实现,服务器上没有线程阻塞。缺点:在等待期间,您必须经常拨打电话(可能是一秒钟 - 用户不会注意到这种延迟),这相当于很多的呼叫以及服务器上的一些额外负载。 / p>
对于方法2,还有选择:
使用Object.wait()
阻止线程,可选择在共享数据存储中存储节点ID,IP或其他标识符:如果是,则接收Android应用程序授权的节点需要:
对于上面1.中的每个节点,发送一条消息,标识要取消阻止的用户会话。该消息可以通过以下方式发送:
Object.notify
Object.notify()
。轮询数据存储,直到线程被授权继续:在这种情况下,所有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集群中的所有节点都可以看到此状态。
当Android应用程序的回复到达另一个节点时,恢复线程正在执行的操作的状态并继续在该节点上进行处理。
如果应用程序的UI正在等待来自服务器的响应,您可以考虑使用ajax请求从服务器轮询响应状态。处理android app响应的节点不需要与处理UI请求的节点相同。
答案 5 :(得分:1)
考虑使用内存网格,以便群集中的实例可以共享状态。我们使用Hazelcast在实例之间共享数据,因此如果响应到达不同的实例,它仍然可以处理它。
E.g。您可以使用值为1的分布式倒计时锁存器来设置发送消息后等待的线程,当响应从客户端到达单独的实例时它可以减少,该实例可以将锁存器减少到0以便运行第一个线程
答案 6 :(得分:1)
在Web服务环境中使用Thread.wait
是一个巨大的错误。相反,维护一个用户/令牌对的数据库并定期使它们到期。
如果需要群集,请使用可群集的数据库。我会推荐像memcached这样的东西,因为它在内存中(和快速)并且开销很低(键/值对很简单,所以你不需要RDBMS等)。 memcached已经为你处理了令牌的到期,所以它看起来非常合适。
我认为用户名 - >令牌 - >密码策略是不必要的,特别是因为您有两个不同的组件共享相同的双因素身份验证责任。我认为您可以进一步降低复杂性,减少用户的混淆,并节省一些短信费用。
与您的网络服务的互动非常简单:
userid
= token
插入memcached 上述解决方案没有线程问题,它可以扩展到支持您自己软件所需的JVM数量。
答案 7 :(得分:1)
我很害怕,但线程无法在经典Java EE群集上迁移。
您必须重新考虑您的体系结构以实现不同的等待/通知(无连接)。
或者,您可以尝试使用terracotta.org。看起来这允许在多台计算机上集群整个JVM进程。也许这是你唯一的解决方案。
阅读 Introduction to OpenTerracotta 中的简短介绍。
答案 8 :(得分:0)
我想问题是,你的第一个线程在JVM 1中向用户的Android应用程序发送通知,当用户回复时,控件转到JVM 2.这就是主要问题。
不知何故,两个线程都可以访问同一个JVM来应用等待和通知逻辑。
答案 9 :(得分:0)
解决方案:
为所有等待的线程创建单个联系点。因此,在集群环境中,所有线程都将在第三个JVM(单点联系)上等待,因此以这种方式所有请求(任何集群Tomcat)将联系同一个JVM以等待并通知逻辑,因此没有线程会等待无限的时间。如果有回复,那么如果同一个对象已经等待并且第二次被通知,则会通知线程。