代码之家  ›  专栏  ›  技术社区  ›  jaletechs

作业或任务调度

  •  2
  • jaletechs  · 技术社区  · 7 年前

    我有一个运行在Glassfish 4上的多租户系统(Java EE),该系统在一年中的特定时期内每次接收大约500个打印作业请求。在此之前,资源足以处理这些请求,但在此期间,请求变得太多,服务器无法处理,从而导致大量停机。我解决这个问题的想法是简单地对这些请求的处理方式进行排序。也就是说,一种先到先服务的方式,处理一个请求,然后处理下一个请求,直到没有更多的请求为止。我试图将其构建为一种服务,它不断检查是否有任何请求,然后按顺序为请求提供服务。

    根据我从StackOverflow和大量在线搜索中找到的解决方案,我将其缩小到了几个。但我有一些担忧:

    日程安排: 我看到的大多数作业调度实现都需要一些周期性的间隔来执行任务。这对我的系统不起作用,因为每个打印作业完成的时间取决于生成的报告页数。可能是5页,也可能是50页。换句话说,我不知道一个请求需要多长时间才能得到服务。

    Java消息服务(JMS): 我曾想过使用JMS队列,但我不明白如何将其与我当前的情况联系起来。我知道这是为了传递信息,也许可以解决我的一部分问题,但我还不知道如何解决。

    无止境循环: 这看起来很俗气,坦率地说,这是一种我甚至不愿在Java EE应用程序和缺乏资源的系统上尝试的黑客行为。

    总而言之,我希望您能建议我如何实施一个系统,该系统将无休止地接收请求,为它们提供服务,无论它们需要多长时间,并继续处理下一个请求。如果没有任何请求,它将等待。如果请求太多,它只会按照接收顺序为它们提供服务。


    第一次编辑: 所以,考虑到我当前系统的多租户和整体复杂性,在对其进行了一些思考之后,我决定创建另一个系统,该系统将接收客户端请求以生成这些结果。这个提议的系统本身不会生成报告,只会要求当前系统生成报告,然后通过电子邮件发送给客户。我认为,在提议的系统中可以实现请求的排队。现在我只需要弄清楚这个系统如何也是一个Java EE应用程序。也许这就是其中一些答案发挥作用的关键所在。您的想法将不胜感激。

    2 回复  |  直到 7 年前
        1
  •  0
  •   contrapost    7 年前

    您可以尝试使用一个发送方和多个接收方实现点对点的消息传递模型。在这种情况下,将保存来自发送方的所有消息的队列将以这样的方式控制消息的使用,即每个消息将仅由一个接收方接收。

    注意!这种情况下的队列不能保证接收的顺序,但据我所知,这不适合您。

    所以,归根结底,尝试实现基于Java消息传递API的基础架构。

    顺便说一句,如果不是很关键,您可以为消息设置一个过期期限,并提高应用程序的稳定性。

        2
  •  0
  •   jaletechs    7 年前

    经过一个月的认真工作,我找到了一个行之有效的解决方案,并且已经实施。

    根据我的第一次编辑,我确实创建了一个新系统。但不是以我最初认为的方式。新系统能够生成我需要的报告。它的唯一职责是接收来自现有系统的请求,将它们保存在数据库中,并在适当的时候为这些请求生成报告。

    避免JMS: 在提问时,我需要JMS提供两样东西,即:

    (1) 某种排序的排序请求队列,以及

    (2) 使客户端调用异步。

    但不用做太多,我就可以用servlet实现异步调用。显然,这里有一处房产( asyncSupported )在 @WebServlet 注释使servlet调用异步。

    @WebServlet(name = "ReportGenServlet", urlPatterns = {"/report-gen-servlet-path"}, asyncSupported = true)
    

    接下来,我为这500个请求提供了一个可靠的持久存储,这些请求可能会立即得到服务,也可能不会立即得到服务,因此我继续实现了一个队列处理器,以先到先服务的方式为请求提供服务。

    import javax.ejb.*;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    /**
     * Created by jaletechs on 4/23/18.
     */
    
    /*
        A Report Request Queue Processor
        Checks for new and failed report requests every X minutes,
         and processes the requests accordingly
     */
    @Singleton
    @LocalBean
    @Startup
    public class ReportRequestQueueProcessor {
    
        @EJB
        private SomeJobEjb ejb;
        @EJB
        private ReportRequestEntityMngrLocal requestEntityMngr;
    
        private AtomicBoolean busy = new AtomicBoolean(false);
    
        @Lock
        @Schedule(second = "*/30", minute = "*", hour = "*", persistent = false)
        public void atSchedule() throws InterruptedException{
            processRequests();
        }
    
        @Lock(LockType.READ)
        public void processRequests() throws InterruptedException{
    
            if (!busy.compareAndSet(false, true)){
                return;
            }
    
            try {
                List<ReportRequest> requestList = requestEntityMngr.getPending(50);
    
                for(ReportRequest request: requestList) {
                   ejb.generateAndEmailReport(request);
                }
            } finally {
                busy.set(false);
            }
        }    
    }
    

    我必须说我非常喜欢这种方法。这个 @Lock 注释,以及 AtomicBoolean “我的逻辑”中的测试允许当前正在运行的处理任务在不中断的情况下完成,即使容器试图刷新。这非常适合处理没有固定完成时间的任务。此外,通过一些JPA查询,我能够获取旧的、尚未处理的请求,以及由于某种原因而失败的请求。这种有序性大大减少了服务器宕机的次数。此外,我发现客户愿意等待一段时间,只要他们能在合理的时间内得到保证的结果。

    我还应该补充一点,我优化了报告生成代码。事实上,我必须承认,这本应是第一步。当我完成优化时,用92秒生成的报告已经在13秒内生成。这是很大的进步。

    这就是我解决问题的方法。如有疑问、意见和建议,将不胜感激。谢谢你的帮助。