【问题标题】:Best method of triggering a shell script from Java从 Java 触发 shell 脚本的最佳方法
【发布时间】:2009-09-29 16:25:34
【问题描述】:

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

脚本做了很多事情 - 处理、FTP 等 - 这是一个遗留的事情。

运行需要很长时间。

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

我能想到三个选项:

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

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

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

请问有人对哪种解决方案最不可靠以及为什么有任何意见吗?

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

谢谢。

【问题讨论】:

    标签: java unix jakarta-ee shell quartz-scheduler


    【解决方案1】:

    您提到了 Quartz,所以让我们选择第 4 个选项(当然这是 IMO 最好的):

    PS:最大的问题可能是查找文档,这是我能找到的最佳来源:How to use NativeJob?

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 另一个想法是使用单独的处理线程等待队列(或使用 Object.notify() + Object.wait() 进行通知),并让 HTTP 请求处理程序推送到队列(或调用通知)。
      【解决方案3】:

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

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

      【讨论】:

      • 我担心 JavaDocs 中关于进程可能由于缓冲区而阻塞的语句,所以我真的不想这样做 - 以防万一出现 stdout/stderr .所以如果你能保证所有的stdout/stderr都被重定向,那么这个方法能保证工作吗?但我想这将涉及修改 shell 脚本,理想情况下我不想这样做。
      【解决方案4】:

      我的处理方法可能如下:

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

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

      【讨论】:

      • 抱歉 - 忘了说,它在 1.4.2 JRE 中,所以默认情况下并发包不可用。
      • 有一个可用于 Java 1.4 的 JSR 166 (java.util.concurrent) 的反向移植。见backport-jsr166.sourceforge.net
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

        • 谢谢,但我假设您会意识到我会在我提出的所有上述解决方案中使用 java.lang.Runtime.exec()。此外,我还假设所有这些都需要使用 servlet(或类似的)。
        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

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

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

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

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

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

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-03-13
            • 1970-01-01
            • 2011-03-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-12
            • 1970-01-01
            相关资源
            最近更新 更多