2

我有一个想要从 J2EE Web 应用程序触发的 shell 脚本。

该脚本做了很多事情——处理、FTP 等——这是一个遗留问题。

运行需要很长时间。

我想知道最好的方法是什么。我希望用户能够单击链接、触发脚本并向用户显示一条消息,说明脚本已启动。我希望 HTTP 请求/响应周期是即时的,不管我的脚本需要很长时间才能运行。

我可以想到三个选项:

  • 在处理用户点击的过程中产生一个新线程。但是,我认为这不符合 J2EE 规范。
  • 在触发脚本之前将一些输出发送到 HTTP 响应流并提交。这给出了 HTTP 请求/响应周期已经完成的错觉,但实际上处理请求的线程仍然坐在那里等待 shell 脚本完成。所以我基本上为了自己的目的劫持了容器 HTTP 处理线程。
  • 创建一个包装脚本,在后台启动我的主脚本。这将使请求/响应循环在容器中正常完成。

以上所有内容都将使用 servlet 和 Runtime.getRuntime().exec()。

这是在 Java 1.4.2 上使用 Oracle 的 OC4J 应用服务器在 Solaris 上运行的。

请问有没有人对哪个是最简单的解决方案有任何意见,为什么?

或者有人有更好的方法吗?我们有 Quartz 可用,但我们不想将 shell 脚本重新实现为 Java 进程。

谢谢。

4

8 回答 8

3

You mentioned Quartz so let's go for an option #4 (which is IMO the best of course):

PS: The biggest problem may be to find documentation and this is the best source I've been able to find: How to use NativeJob?

于 2009-09-29T16:56:43.527 回答
2

我会选择选项 3,特别是如果您实际上不需要知道脚本何时完成(或者除了等待进程结束之外还有其他查找方式)。

选项 1 浪费了一个等待脚本完成的线程。选项 2 似乎是个坏主意。我不会劫持 servlet 容器线程。

于 2009-09-29T16:43:31.713 回答
2

您的应用程序是否有必要评估您正在启动的脚本的输出,或者这是一个简单的即发即弃的工作?如果不需要,您可以“滥用” Runtime.getRuntime().exec() 将立即返回并且进程继续在后台运行的事实。如果您真的想等待脚本/进程完成,则必须在 exec() 返回的 Process 对象上调用 waitFor()。

如果您正在启动的进程向 stdout 或 stderr 写入任何内容,请务必将它们重定向到日志文件或 /dev/null,否则该进程将在一段时间后阻塞,因为 stdout 和 stderr 可用作具有有限缓冲能力的 InputStreams 通过进程对象。

于 2009-09-29T18:08:05.083 回答
1

我对此的方法可能如下所示:

  • 在 servlet 中设置ExecutorService以执行实际执行。
  • 创建具有适当返回类型的Callable实现,它包装实际脚本执行(使用Runtime.exec())以将 Java 输入变量转换为 shell 脚本参数,并将脚本输出转换为适当的 Java 对象。
  • 当一个请求进来时,创建一个合适的Callable对象,将它提交给执行器服务并且把结果Future放在某个地方持久化(例如用户的会话,或者UID键控映射返回键给用户以供以后查找,这取决于需求)。然后立即向用户发送一个 HTTP 响应,暗示脚本已正常启动(如果需要,包括查找键)。
  • 添加一些机制让用户轮询其任务的进度,根据Future您刚刚查找的状态返回“仍在运行”响应、“失败”响应或“成功+结果”响应。

这有点手动,但根据您的 web 应用程序的结构,您可能可以将这些通用组件安装在某个地方。

于 2009-09-29T16:42:27.110 回答
1

如果您的 HTTP 响应/用户不需要查看脚本的输出,或者知道脚本何时完成,那么您最好的选择是在您提到的某种包装脚本中启动线程,以便它可以运行在整个 servlet 容器环境之外。这意味着您可以免除自己需要管理容器内的线程,或者像您提到的那样劫持线程等。

只有当用户需要被告知脚本何时完成和/或监视脚本的输出时,我才会考虑选项 1 或 2。

于 2009-09-29T16:43:48.047 回答
0

您的问题源于您试图违背 J2EE 中的“每个请求的单一响应”模型,并让最终用户的页面随着后端任务的执行而动态更新。

除非您想继续引入基于 Ajax 的解决方案,否则您将不得不强制用户浏览器上呈现的页面定期“轮询”服务器以获取信息,直到后端任务完成。

这可以通过以下方式实现:

  1. 当 J2EE 容器收到请求时,生成一个线程,该线程引用会话对象(将用于编写脚本的输出)

  2. 初始化响应 servlet 以编写一个 html 页面,该页面将包含一个 Javascript 函数,以定期(每 10 秒左右)从服务器重新加载页面。

  3. 在每个请求上,轮询会话对象以显示步骤 1 中生成的线程存储的输出

  4. [如果需要,可以添加清理逻辑以在线程完成后从会话中删除存储的内容,您也可以在会话中设置任何其他标志以标记脚本执行的状态转换]

这是实现您想要的一种方法 - 它不是所有方法中最优雅的,但它本质上是由于需要使用请求/响应模型从服务器异步更新您的页面内容。

还有其他方法可以实现这一点,但这实际上取决于您的约束有多不灵活。我听说过Direct Web Remoting(虽然我还没有玩过它),可能值得一看Developing Applications using Reverse-Ajax

于 2009-09-29T19:18:39.447 回答
0

异步后端进程最强大的解决方案是使用消息队列 IMO。最近我使用 Spring 嵌入的 ActiveMQ 代理实现了这一点,并装配了一个生产和消费 bean。当需要启动作业时,我的代码会调用生产者,生产者会将消息放入队列。消费者订阅了队列,并在单独的线程中被消息启动。这种方法巧妙地将 UI 与排队机制(通过生产者)和异步过程(由消费者处理)分开。

请注意,这是一个 Java 5、Spring 配置的环境,在开发人员机器上的 Tomcat 服务器上运行,并部署到测试/生产机器上的 Weblogic。

于 2009-09-29T18:37:06.770 回答
0

对于第二个选项,您可以使用 servlet,在响应 HTTP 请求后,您可以使用 java.lang.Runtime.exec() 来执行您的脚本。我还建议你看这里:http ://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

...对于使用它的一些问题和陷阱。

于 2009-09-29T16:32:16.073 回答