您的问题的根本原因是 Java EE 被设计为以不同的方式工作 - 尝试阻塞/等待服务线程是重要的禁忌之一。我将首先给出原因,以及之后如何解决问题。
Java EE(Web 层和 EJB 层)旨在能够扩展到非常大的规模(集群中的数百台计算机)。但是,为了做到这一点,设计人员必须做出以下假设,这些假设是对如何编码的具体限制:
交易是:
- 短暂的(例如不要阻塞或等待超过一秒左右的时间)
- 彼此独立(例如线程之间没有通信)
- 对于 EJB,由容器管理
所有用户状态都保存在特定的数据存储容器中,包括:
- 通过例如 JDBC 访问的数据存储。您可以使用传统的 SQL 数据库或 NoSQL 后端
- 有状态会话 bean,如果您使用 EJB。将这些视为将其字段持久保存到数据库的 Java Bean。有状态会话 bean 由容器管理
Web 会话这是一个键值存储(有点像 NoSQL 数据库,但没有规模或搜索功能),它在会话中为特定用户保留数据。它由 Java EE 容器管理,并具有以下属性:
- 如果节点在集群中崩溃,它将自动重新定位
- 用户可以拥有多个当前 Web 会话(即在两个不同的浏览器上)
- 当用户通过注销结束他们的会话时,或者当会话处于非活动状态的时间超过可配置的超时时间时,Web 会话结束。
- 存储的所有值都必须是可序列化的,它们才能在集群中的节点之间持久化或传输。
如果我们遵循这些规则,Java EE 容器可以成功管理集群,包括关闭节点、启动新节点和迁移用户会话,而无需任何特定的开发人员代码。开发人员编写图形界面和业务逻辑——所有“管道”都由可配置的容器特性管理。
此外,在运行时,Java EE 容器可以由一些非常复杂的软件监控和管理,这些软件可以跟踪实时系统上的应用程序性能和行为问题。
< snark >嗯,这就是理论。实践表明,有一些非常重要的限制被遗漏了,这导致了 AOSP 和代码注入技术,但那是另一回事了</snark>
[关于这个,网上有很多讨论。一个专注于 EJB 的文章在这里:为什么不鼓励在 Java EE 容器中生成线程?对于 Tomcat 等 Web 容器也是如此]
很抱歉这篇文章 - 但这对你的问题很重要。由于线程的限制,您不应阻止 Web 请求等待另一个稍后的请求。
当前设计的另一个问题是,如果用户与网络断开连接、电量耗尽或只是决定放弃,会发生什么?大概你会超时,但过了多长时间?对一些客户来说,这可能还为时过早,这会导致满意度问题。如果超时时间过长,您最终可能会阻塞Tomcat 中的所有工作线程,并且服务器将冻结。这会使您的组织面临拒绝服务攻击。
编辑:在发布了更详细的算法描述后改进了建议。
尽管上面讨论了阻止 Web 工作线程的不良做法以及可能的拒绝服务,但很明显,用户会看到一个小的时间窗口来对 Android 手机上的通知做出反应,这可以保持合理的小以增强安全性。这个时间窗口也可以保持在 Tomcat 的响应超时以下。所以可以使用线程阻塞方法。
有两种方法可以解决此问题:
- 将解决方案的重点转移到客户端——在浏览器上使用Javascript轮询服务器
- 集群中节点之间的通信允许节点接收来自 Android 应用程序的授权响应,以解除阻塞 servlet 响应的节点。
对于方法 1,浏览器通过 Javascript 通过 AJAX 调用对 Tomcat 上的 Web 服务进行轮询;True
如果 Android 应用通过身份验证,则 AJAX 调用返回。优点:客户端,服务器上的最小实现,服务器上没有线程阻塞。缺点:在等待期间,您必须进行频繁的调用(可能每秒一个 - 用户不会注意到这种延迟),这相当于大量的调用和服务器上的一些额外负载。
对于方法 2,还有一个选择:
在共享数据存储中可选地存储节点 ID、IP 或其他标识符来阻止线程Object.wait()
:如果是这样,接收 Android 应用授权的节点需要:
- 要么找到当前阻塞的节点,要么广播到集群中的所有节点
对于上面 1. 中的每个节点,发送一条消息,标识要解除阻止的用户会话。该消息可以通过以下方式发送:
- 在每个节点上都有一个仅限内部的 servlet - 这由执行 Android 应用程序授权的 servlet 调用。内部 servlet 将调用
Object.notify
正确的线程
- 使用 JMS 发布-订阅消息队列向集群的所有成员广播。每个节点都是一个订阅者,收到通知后将调用
Object.notify()
正确的线程。
轮询数据存储,直到线程被授权继续:在这种情况下,Android 应用程序需要做的就是将状态保存在 SQL DB 中