线程在servlet环境中处理批处理作业

时间:2011-09-17 05:58:40

标签: java servlets spring-mvc threadpool batch-processing

我有一个Spring-MVC,Hibernate,(Postgres 9 db)Web应用程序。管理员用户可以发送请求以处理近200,000条记录(通过连接从各种表中收集的每条记录)。每周或每月请求此类操作(或者每当数据达到约200,000 / 100,000记录的限制时)。在数据库端,我正在实现批处理。

  • 问题:如此长时间运行的请求会占用服务器线程,导致普通用户受损。

  • 要求:此请求的高响应时间不是问题。由于这个耗时的过程,所需要的不是让其他用户受苦。

  • 我的解决方案:

    使用Spring taskExecutor抽象实现线程池。所以我可以用5或6个线程初始化我的线程池,并将200,000条记录分成更小的块,每块大小为1000。我可以在这些块中排队。为了进一步允许普通用户拥有更快的数据库访问权限,我可以让每个可运行的线程休眠2或3秒。 我看到的这种方法的优点是:我们不是一次性执行巨大的数据库交互请求,而是跨越更长的时间进行异步设计。因此表现得像多个普通用户请求。

有些有经验的人可以就此发表意见吗? 我还读到了使用面向消息的中间件(如JMS / AMQP或Quartz Scheduling)实现相同的beahviour。但坦率地说,我认为在内部他们也会做同样的事情,即创建一个线程池并在工作中排队。那么为什么不选择Spring taskexecutors而不是在我的网络应用程序中为这个功能添加一个全新的基础设施呢?

请分享您对此的看法,如果还有其他更好的方法可以告诉我吗? 再一次:完全处理所有记录而非关注的时间,所需要的是在此期间访问Web应用程序的普通用户不应受到任何影响。

2 个答案:

答案 0 :(得分:0)

您可以并行化任务并等待所有任务完成,然后再返回呼叫。为此,您希望使用自5.0以来Java标准中提供的ExecutorCompletionService

简而言之,您使用容器的服务定位器来创建ExecutorCompletionService的实例

ExecutorCompletionService<List<MyResult>> queue = new ExecutorCompletionService<List<MyResult>>(executor);

// do this in a loop
queue.submit(aCallable);

//after looping 
queue.take().get(); //take will block till all threads finish

如果您不想等待,您可以在后台处理作业而不会阻止当前线程,但是您需要一些机制来在作业完成时通知客户端。这可以通过JMS,或者如果你有一个ajax客户端,它可以轮询更新。

Quartz也有一个作业调度机制,但Java提供了一种标准的方式。

编辑: 我可能误解了这个问题。如果您不想要更快的响应,而是想要限制CPU,请使用此方法

您可以创建一个类似于PollingThread的内部类,其中每个作业包含java.util.UUID的批次和外部类中定义的PollingThreads数量。这将永远持续下去并且可以调整以保持CPU自由处理其他请求

 class PollingThread implements Runnable {
            @SuppressWarnings("unchecked")
            public void run(){
                Thread.currentThread().setName("MyPollingThread");
                while (!Thread.interrupted()) {
                    try {
                        synchronized (incomingList) {
                            if (incomingList.size() == 0) {
                                // incoming is empty, wait for some time
                            } else {
                                //clear the original
                                list = (LinkedHashSet<UUID>) 
                                        incomingList.clone();
                                incomingList.clear();
                            }
                        }

                        if (list != null && list.size() > 0) {
                            processJobs(list);
                        }
                        // Sleep for some time
                        try {
                            Thread.sleep(seconds * 1000);
                        } catch (InterruptedException e) {
                            //ignore
                        }
                    } catch (Throwable e) {
                        //ignore                    
                    }
                }
           }
    }

答案 1 :(得分:0)

Huge-db-operations通常在凌晨时触发,用户流量非常少。 (比如上午1点到凌晨2点。)一旦你发现了,你可以简单地安排一个工作在那个时候运行。 Quartz可以派上用场,基于时间的触发器。 (注意:也可以手动触发作业。)

现在可以将处理结果存储在不同的表中。 (我将其称为结果表)稍后,当用户想要此结果时,db操作将针对这些结果表,这些结果表具有最少的记录且几乎没有任何连接会涉及到。

  

而不是仅为此功能在我的网络应用中添加全新的基础架构?

Quartz.jar约为350 kb,添加此依赖项应该不是问题。另请注意,没有必要将其作为网络应用程序。这些ETL的类可以放在一个独立的模块中。来自web-app的请求只需要从结果表中获取

除了所有这些之外,如果你已经拥有了一个主从数据库模型(用你的dba进行讨论),那么你可以使用slave-db而不是master来执行large-db操作,普通用户会被指向至。