2014-07-21 8 views
6

Pochodzę z tła Perla i piszę moją pierwszą aplikację internetową Java MVC przy użyciu Spring.Spring MVC background process

Moja aplikacja internetowa umożliwia użytkownikom przesyłanie zamówień, które aplikacja przetwarza synchronicznie, wywołując usługę SOAP innej firmy. Następną fazą projektu jest umożliwienie użytkownikom składania masowych zamówień (np. Pliku CSV zawierającego 500 wierszy) i przetwarzania ich asynchronicznie. Oto fragment mojego istniejącego kontrolera:

@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"; 
    } 
} 

Planuję utworzyć nowy @RequestMapping do czynienia z przychodzącym CSV i modyfikować OrderService aby móc przełamać CSV siebie i utrzymują poszczególne rozkazy do bazy danych.

Moje pytanie brzmi: jakie jest najlepsze podejście do tworzenia pracowników w tle w aplikacji MVC Spring? Idealnie byłoby mieć 5 wątków przetwarzających te zamówienia, i najprawdopodobniej z kolejki. Przeczytałem o @Async lub przesłaniu Runnable do fasoli SimpleAsyncTaskExecutor i nie jestem pewien, w którą stronę pójść. Niektóre przykłady naprawdę mi pomogą.

+0

Uwaga boczna: adnotacja '@ Service' jest tutaj nadmiarowa. – sp00m

+0

Co się stanie, jeśli proces w tle zakończy się niepowodzeniem? Aka, wysyłam plik i import z jakiegoś powodu nie powiedzie się. Co się wtedy stanie? Jeśli chcesz mieć możliwość ponownego uruchomienia procesu importu, Spring Batch może mieć sens. W przeciwnym razie istnieje wiele innych opcji. –

Odpowiedz

1

Myślę, że Spring Batch to przesada i nie jest to naprawdę to, czego szukasz. Jest to bardziej przydatne w przypadku przetwarzania wsadowego, np. Zapisywanie wszystkich zamówień do pliku, a następnie przetwarzanie wszystkich naraz, podczas gdy wydaje się, że jest to bardziej asynchroniczne przetwarzanie, w którym chcesz po prostu "stać w kolejce" pracy i przetwarzać ją w ten sposób.

Jeśli tak jest w rzeczywistości, zajrzę do używania modelu pub/sub przy użyciu JMS. Istnieje kilka dostawców JMS, na przykład Apache ActiveMQ lub Pivotal RabitMQ. Zasadniczo Twój OrderService podzieliłby CSV na jednostki pracy, wepchnął je do kolejki JMS, a ty miałbyś wielu Konsumentów skonfigurowanych do odczytu z Kolejki i wykonania zadania roboczego. Istnieje wiele sposobów na skonfigurowanie tego, ale chciałbym po prostu utworzyć klasę do przechowywania wątków roboczych i uczynić liczbę wątków konfigurowalną. Inne dodatkowe korzyści to:

  1. Możesz uzewnętrznić kod klienta, a nawet go uruchomić na zupełnie innym sprzęcie, jeśli chcesz.
  2. MQ jest dość dobrze znanym procesem, a istnieje wiele komercyjnych ofert. Oznacza to, że możesz łatwo napisać swój system przetwarzania zamówień w języku C# za pomocą MQ, aby przenieść wiadomości, lub nawet użyć Spring Batch, jeśli chcesz. Heck, istnieje nawet seria MQ dla hosta, więc możesz zlecić przetwarzanie zamówienia na komputerze mainframe w języku COBOL, jeśli pasuje ci to do gustu.
  3. To głupio po prostu dodać więcej konsumentów lub producentów. Po prostu zasubskrybuj kolejkę i odejdą!
  4. W zależności od używanego produktu kolejka zachowuje stan, aby wiadomości nie zostały "utracone". Jeśli wszyscy konsumenci przejdą do trybu offline, kolejka będzie po prostu tworzyć kopie zapasowe wiadomości i przechowywać je do momentu powrotu klientów.
  5. Kolejki są zazwyczaj bardziej niezawodne. Producent może zejść na dół, a konsumenci nawet nie wzdrygają się. Konsumenci mogą zejść na dół, a producent nawet nie musi tego wiedzieć.

Istnieje jednak kilka wad. Masz teraz dodatkowy punkt awarii.Prawdopodobnie będziesz chciał monitorować głębokości kolejki i będziesz musiał zapewnić wystarczającą ilość miejsca do przechowywania wiadomości podczas buforowania wiadomości. Ponadto, jeśli czas przetwarzania może być problemem, być może trzeba będzie monitorować, jak szybko przetwarzane są rzeczy w kolejce, aby upewnić się, że nie tworzy się zbyt duża kopia zapasowa lub nie łamie żadnych umów SLA, które mogą obowiązywać.

Edycja: Dodawanie przykład ... Gdybym miał gwintowaną klasę, na przykład tak:

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; 
    } 
} 

Wtedy zacznę wątki użyciu klasy tak:

@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; 
    } 
} 

Teraz masz miłą klasę usług, do której możesz dodać metody monitorowania, restartowania, zatrzymywania itp. Wszystkich wątków roboczych. Zrobiłem to jako @Service, ponieważ czułem się bardziej odpowiedni niż zwykły @Component, ale technicznie może to być wszystko tak długo, jak Spring wie, aby odebrać go podczas autowiring. Metoda init() w klasie usług jest tym, co uruchamia wątki, a dismantle() jest używany do ich zatrzymania i oczekiwania na zakończenie. Używają adnotacji @PostConstruct i @PreDestroy, dzięki czemu możesz nadawać im dowolne nazwy. Prawdopodobnie masz konstruktora na swoim MyWorkerThread, aby skonfigurować Kolejki i takie. Ponadto, jako zastrzeżenie, wszystko to zostało zapisane z pamięci, więc mogą występować niewielkie problemy z kompilacją lub nazwy metod.

Możliwe, że są już dostępne klasy do tego typu rzeczy, ale ja sam nigdy ich nie widziałem. Czy ktoś wie o lepszym sposobie korzystania z gotowych części, które chciałbym lepiej wykształcić?

+0

Dziękuję za odpowiedź. Przeglądałem JMS przy użyciu ActiveMQ i działało publikowanie (tj. Dodawanie zamówień do kolejki). Przechodzę teraz na konsumenta i używam SimpleThreadPool, ale biorąc pod uwagę, że moja aplikacja jest aplikacją webową (brak głównego punktu wejścia), w jaki sposób zapewniłbym, że ta pula wątków będzie działać podczas uruchamiania? –

+0

Puli wątków jest więcej dla wątków, które działają, a następnie zakończyć. Możesz mieć je w harmonogramie lub coś takiego. Myślę, że lepiej byłoby, gdyby twoja usługa uruchomiła twoje wątki lub napisała "opiekun" niestandardowego wątku. Zaktualizuję swoją odpowiedź na przykładzie. – CodeChimp

0

Jeśli rozmiar zamówienia może wzrosnąć w przyszłości i chcesz skalowalnego rozwiązania, proponuję, aby przejść z Spring-Batch framework. Uważam, że bardzo łatwo jest zintegrować się ze sprężyną-mvc, a przy minimalnej konfiguracji można łatwo uzyskać bardzo solidną architekturę przetwarzania równoległego. Use Spring-batch-partitioning .Jeśli to pomaga! Możesz zapytać, czy potrzebujesz pomocy dotyczącej integracji z Spring MVC.