2014-07-21 25 views
6

我来自Perl背景,正在使用Spring编写我的第一个Java MVC Web应用程序。Spring MVC后台进程

我的webapp允许用户通过调用第三方SOAP服务来提交应用程序同步处理的订单。该项目的下一阶段是允许用户提交批量订单(例如包含500行的CSV)并异步处理它们。这里是我现有的控制器的一个片段:

@Controller 
@Service 
@RequestMapping(value = "/orders") 
public class OrderController { 

    @Autowired 
    OrderService orderService; 

    @RequestMapping(value="/new", method = RequestMethod.POST) 
    public String processNewOrder(@ModelAttribute("order") Order order, Map<String, Object> map) { 

     OrderStatus orderStatus = orderService.processNewOrder(order); 

     map.put("orderStatus", orderStatus); 

     return "new"; 
    } 
} 

我计划建立一个新的@RequestMapping处理传入的CSV和修改OrderService能够打散的CSV,坚持个别订单到数据库中。

我的问题是:在MVC Spring应用程序中创建后台工作人员的最佳方法是什么?理想情况下,我将有5个线程处理这些订单,最有可能来自队列。我已阅读@Async或将Runnable提交给SimpleAsyncTaskExecutor bean,我不知道该走哪条路。一些例子会真的帮助我。

+0

注意:这里'@ Service'注解是多余的。 – sp00m

+0

如果后台进程失败会发生什么?也就是说,我上传了一个文件,导入失败,出于某种原因。然后会发生什么?如果你需要能够重新启动导入过程,Spring Batch可能是有意义的。否则,还有其他一些选择。 –

回答

1

我认为Spring Batch是矫枉过正的,而不是真正的你在找什么。批处理更像是将所有订单写入文件,然后一次处理所有订单,而这看起来更像是异步处理,您只需要“排队”工作并以此方式处理。

如果确实如此,我会考虑使用使用JMS的pub/sub模型。有几个JMS提供者,例如Apache ActiveMQ或Pivotal RabitMQ。实质上,您的OrderService会将CSV分解为工作单元,将它们推送到JMS队列中,并且您将有多个使用者设置为从队列中读取并执行工作任务。有很多种方法来配置这个,但我只是简单地创建一个类来保存你的工作线程,并使线程的数量可以被配置。这里的其他附加好处是:

  1. 您可以将消费者代码外部化,甚至可以在完全不同的硬件上运行(如果您愿意)。
  2. MQ是一个非常有名的过程,并且有很多商业产品。这意味着您可以轻松地使用MQ在C#中编写订单处理系统来移动消息,或者甚至可以使用Spring Batch。哎,甚至还有主机的MQ系列,所以如果它适合你的想象,你可以在COBOL主机上进行订单处理。
  3. 这很愚蠢,只是增加更多的消费者或生产者。只需订阅队列,然后离开!
  4. 根据使用的产品,队列保持状态,因此消息不会“丢失”。如果所有消费者都脱机,队列将简单地备份和存储消息,直到消费者回来。
  5. 队列通常也更健壮。生产者可以下来,你甚至不会退缩的消费者。消费者可以下来,制片人甚至不需要知道。

虽然有一些缺点。你现在有一个额外的失败点。您可能需要监视队列深度,并且在缓存消息时需要提供足够的空间来存储消息。另外,如果处理时间可能成为问题,则可能需要监视队列中处理速度如何快速,以确保其不会太多备份或破坏可能存在的任何SLA。

编辑:添加例子... 如果我有一类螺纹,例如这样的:

public class MyWorkerThread implements Runnable { 
    private boolean run = true; 

    public void run() { 
    while (run) { 
     // Do work here... 
    } 

    // Do any thread cooldown procedures here, like stop listening to the Queue. 
    } 

    public void setRunning(boolean runState) { 
    run = runState; 
    } 
} 

然后,我会使用一个类像这样启动线程:

@Service("MyThreadManagerService") 
public class MyThreadManagerServiceImpl implements MyThreadManagerService { 
    private Thread[] workers; 
    private int workerPoolSize = 5; 

    /** 
    * This gets ran after any constructors and setters, but before anything else 
    */ 
    @PostConstruct 
    private void init() { 
    workers = new Thread[workerPoolSize]; 
    for (int i=0; i < workerPoolSize; i++) { 
     workers[i] = new Thread(new MyWorkerThread()); // however you build your worker threads 
     workers[i].start(); 
    } 
    } 

    /** 
    * This gets ran just before the class is destroyed. You could use this to 
    * shut down the threads 
    */ 
    @PreDestroy 
    public void dismantle() { 
    // Tell each worker to stop 
    for (Thread worker : workers) { 
     worker.setRunning(false); 
    } 

    // Now join with each thread to make sure we give them time to stop gracefully 
    for (Thread worker : workers) { 
     worker.join(); // May want to use the one that allows a millis for a timeout 
    } 

    } 

    /** 
    * Sets the size of the worker pool. 
    */ 
    public void setWorkerPoolSize(int newSize) { 
    workerPoolSize = newSize; 
    } 
} 

现在你有一个很好的服务类,你可以添加方法来监视,重启,停止等所有工作线程。我认为它是一个@Service,因为它感觉比简单的@Component更合适,但从技术上讲,只要Spring知道在自动装配时就知道该选择它就可以。服务类中的init()方法是启动线程的方法,dismantle()用于正常停止并等待它们完成。他们使用@PostConstruct@PreDestroy注释,因此您可以将其命名为任何您想要的名称。您可能会在您的MyWorkerThread上设置构造函数来设置队列等。另外,作为一个免责声明,这些都是从内存中写出来的,所以可能会有一些温和的编译问题,或者方法名称可能会稍微偏离。

可能有类已经可以做这种事情,但我从来没有见过自己。是否有人知道使用现成的零件更好的方式,我希望能够更好地接受教育。

+0

感谢您的回复。我一直在使用ActiveMQ查看JMS,并使发布工作(即将订单添加到队列中)。我现在正在使用消费者并使用SimpleThreadPool,但由于我的应用程序是一个web应用程序(没有Main入口点),我将如何确保此线程池在启动时运行? –

+0

那么,一个线程池更适用于运行然后终止的线程。你可以让他们在调度器或其他东西。我认为最好是让你的服务启动你的线程或者写一个自定义线程“维护者”。我会用一个例子更新我的答案。 – CodeChimp

0

如果您的订单大小可以在将来增长,并且您希望有一个可扩展的解决方案,我建议您使用Spring-Batch framework。我发现与spring-mvc集成非常简单,只需最少的配置,您就可以轻松实现非常强大的并行处理架构。使用Spring-batch-partitioning。希望这可以帮助您!随意询问您是否需要有关与Spring MVC集成的帮助。