如何在Java EE中隔离用户会话?

8
我们正在考虑使用Java EE开发一款关键任务应用程序,其中一个令我印象深刻的事情是该平台缺乏会话隔离。让我解释一下这种情况。
我们有一个本地Windows应用程序(完整的ERP解决方案),每月接收来自稀疏贡献者的约2k行代码和50个漏洞修复。它还支持脚本编写,因此客户可以添加自己的逻辑,而我们对这些逻辑一无所知。每个服务器节点都有一个代理和进程池,而不是使用线程池。代理接收客户端请求,将其排队直到有可用的池实例,发送请求到该实例,将响应传递给客户端,并将实例释放回进程池。
由于存在如此多的稀疏贡献和定制脚本,这种架构非常强大,部署版本通常会出现一些严重的错误,例如无限循环、长时间等待悲观锁、内存损坏或内存泄漏。我们实现了内存限制、请求超时和简单的看门狗。每当某个进程未能正确及时地回答时,代理就会简单地杀死它,因此看门狗会检测并启动另一个实例。如果进程在开始回答请求之前崩溃,代理将向另一个池实例发送相同的请求,用户不会知道服务器端的任何故障(除了管理员日志)。这很好,因为一些实例正在处理请求时被虚假代码缓慢地破坏。由于大多数会话数据保存在客户端或(在极少数情况下)共享存储中,它似乎可以完美地运行。
现在考虑转移到Java EE,我在规范或流行的应用程序服务器(如Glassfish和JBoss)中找不到类似的东西。是的,我知道大多数集群实现都可以通过会话复制进行透明故障转移,但我们有一些小公司在简单的2节点集群上使用我们的系统(我们还有一些冒险家在单节点服务器上使用该系统)。使用线程池,我理解有一个有漏洞的线程可能会使整个节点崩溃,因为服务器无法检测并安全地杀死它。使整个节点崩溃比杀死单个进程要糟糕得多-我们有每个节点约100个池化进程实例的部署。
我知道IBM和SAP意识到了这个问题,基于http://www.trl.ibm.com/people/kawatiya/pub/Kawachiya07vee.pdfhttp://java.sys-con.com/node/47362。但基于最近的JSR、论坛和开源工具,社区中并没有太多活动。
现在来看问题!
  1. 如果您遇到类似的情况并使用Java EE,您是如何解决的?

  2. 您是否了解即将推出的开源产品或Java EE规范的变更,可以解决此问题?

  3. .NET是否有相同的问题?您能否解释或引用参考资料?

  4. 您是否了解一些现代化和开放式平台,可以解决此问题,并且值得为ERP业务逻辑而努力?

请注意,我必须要求您不要提及进行更多测试或任何类型的QA投资,因为我们不能强迫客户在其自己的脚本上进行测试。我们还有紧急修复bug的情况必须绕过QA,虽然我们强制客户接受这一点,但我们不能让他接受一个有缺陷的软件部分可能会影响一系列不相关的功能。这个问题涉及到健壮的架构,而不是开发过程。

感谢您的关注!


更新:看起来这个问题将在Java 8中得到解决:http://www.pcworld.com/businesscenter/article/237337/java_8_gears_up_for_the_cloud.html - fernacolo
3个回答

6
你所遇到的是Java和“敌对”应用程序使用方面的一个根本问题。
这不仅是在Java EE级别上的一个根本问题,而且是在核心JVM级别上的一个问题。可用的典型JVM在加载“不安全代码”方面存在各种问题,如内存泄漏、类加载器泄漏、资源枯竭和未清理的线程终止,典型的JVM在共享环境中处理糟糕行为的代码时并不足够强大。
一个简单的例子是Java堆内存耗尽。基本规则是,没有人(我特指核心Java库和几乎所有其他第三方库)捕获OutOfMemory异常。有少数人会这样做,但即使他们能做些什么,也很有限。典型的代码处理它们“预期”要处理的异常,但让其他异常通过。运行时异常(其中OOM是其中之一)会沿着调用堆栈向上冒泡,留下一堆未经检查的关键路径代码,留下各种未知状态的东西。
例如构造函数或静态初始化器“不能失败”,留下未初始化的类成员,这些成员“从不为空”。这些受损的类根本不知道它们已经受损了。没有人知道它们已经受损了,也没有办法清理它们。一个达到OOM的堆是一个不安全的镜像,几乎需要重新启动(除非你自己编写或审查了所有代码,当然你不会这样做-谁会这样做?)。
现在,可能会有特定于供应商的JVM表现得更好,并且给您更多的控制权。基于Sun/Oracle JVM的那些(即大多数)则不行。
因此,这不一定是Java EE问题,而是JVM问题。
在JVM中托管敌对代码是一个坏主意。唯一实用的方法是如果您托管脚本语言,并且该脚本语言实现了某种资源控制。可以做到这一点,并且可以将现有的语言进行调整(JavaScript、Groovy、JPython、JRuby)。这些语言直接访问Java库使得它们潜在危险,因此您可能还需要限制只能访问由脚本处理程序包装的方面。此时,问题“为什么要使用Java”就浮出水面了。
请注意,Google App Engine没有做任何这些事情。它为每个正在运行的应用程序启动单独的JVM,但即使如此,它也极大地限制了可以在这些JVM中执行的操作,尤其是通过现有的Java安全模型。这里的区别在于这些实例倾向于“长寿”,以避免启动和关闭的处理成本。我应该说,它们应该是长寿的,而那些不是则会承担这些成本。
您可以自己创建多个JVM实例,为其提供基础设施以处理逻辑请求,为其提供自定义类加载器逻辑以尝试保护免受类加载器泄漏,并最小化地让您关闭这些实例(它们只是一个进程)。这种方法可能有效,并且根据调用的粒度和逻辑的“启动”时间,可能工作得“还可以”。启动时间将至少是从一次运行到下一次运行逻辑类的加载时间,仅这一点就可能使这是一个不好的想法。而且这肯定不会是“Java EE”。Java EE没有设置来做这种事情。但是您也不清楚您正在寻找哪些Java EE功能。
实际上,这就是Apache和“mod_php”的做法。多个实例作为进程单独处理请求,必要时杀死表现不佳的实例。这就是为什么PHP在共享托管业务中很常见的原因。在这种结构中,它基本上是“安全”的。

JVM只是一种通用的运行环境,可运行包括必须具有强大和可扩展性的应用程序以及允许更简单、偶尔出现的应用程序。如果使用其他通用语言,例如C++和Python,则有关恶意代码的所有事情仍然适用。我认为这是Java EE的问题,它对强壮性、性能和支持大型应用程序和开发团队有美好的演讲,但实际上无法实现。当然,Java EE可以依赖JVM来执行某些任务,但它也可以像servlets一样自己实现。 - fernacolo
通常情况下,大多数C++和Python程序不是长时间运行的进程,允许加载任意代码。它们本质上不是“容器”,就像JVM和JEE容器那样。大多数其他语言都由操作系统托管,并直接通过进程依赖于它的服务,每个进程都是独立的执行环境,具有自己的资源限制。常见的JVM和JVM规范没有类似于进程概念的东西。 JEE无法防止WAR执行“new byte [1000000000]”直到堆耗尽,从而破坏JVM。 - Will Hartung
我同意你的观点,除了一点。让JVM成为长时间运行的进程的不是JVM本身,而是JEE。 JEE可以启动多个具有有限内存的子JVM来运行应用程序代码。这不会防止WAR杀死单个JVM,但会防止WAR杀死整个服务器(这才是真正重要的)。当然,还有一些问题,例如共享数据和JVM占用空间,必须解决才能使其正常工作,也许这需要对JVM和/或JEE规范进行更改。 - fernacolo
当然,但就其当前状态而言,JVM因其缓慢的启动和沉重的内存负担而广为人知。在共享资源方面,它实际上是Unix系统上相当糟糕的公民。它也在运行了一段时间后表现更好。因此,虽然您提出的建议肯定可以实现(这是我在最后提到的),但运行时间越长越好。并且围绕Java构建的环境并围绕这些运行时特性工作,以便大多数系统在其自己的长时间运行环境中工作(JEE服务器、OSGI服务器、Spring等)。 - Will Hartung

0

我认为你的情况非常不典型,因此几乎没有现成的框架/平台可以满足这种需求。Java EE 假设请求处理代码由与应用程序的其余部分编写的同一团队编写,因此它不需要被隔离、监视和重置得那么频繁,并且所有系统部分的错误修复方式都是相同的。这种假设大大简化了大多数项目的开发、部署、测试等流程,不会强制他们支付他们不需要的东西,当然,并不适用于所有人。如果你想要根本不同的东西,你可能需要自己实现相当数量的故障转移逻辑。不过,Java EE 确实提供了这方面的基本构建块。

我认为(虽然没有具体的经验来证明),.NET 或其他平台基本上也是基于类似的假设构建的。


0

我们曾经将一个非常庞大的Perl网站转移到Java上,虽然没有那么严重。当收到HTTP请求时,我们会实例化一个类并调用其processRequest方法,周围包裹着try-catch和时间测量。添加一个计时器和线程就足以使线程终止。实际上,这在现实生活中可能已经足够了。

像Glassfish这样的Java EE服务器是一个OSGi容器,您可以采用更多的隔离手段。

此外,您可以运行一系列(Web或本地)应用程序,并通过中央Web应用程序分派请求。这些应用程序然后被隔离。

序列化会话和操作系统进程启动新JVM更加隔离。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接