2017-01-19 7 views
3

Ceci est ma première application Catalyst et je ne sais pas comment résoudre le problème suivant.Comment gérer un processus de longue durée dans une application Catalyst?

L'utilisateur entre des données dans un formulaire et sélectionne un fichier (jusqu'à 100 Mo) à télécharger. Après la soumission du formulaire, le calcul réel prend jusqu'à 5 minutes et les résultats sont stockés dans un DB. Ce que je veux faire est d'exécuter ce processus (et peut-être aussi le téléchargement de fichier) dans le fond pour éviter un délai d'expiration du serveur. Il devrait y avoir une sorte de retour à l'utilisateur (comme un message "Le travail a été démarré" ou une barre de progression). Le formulaire doit être bloqué pendant que le travail est en cours d'exécution. Une page de résultat devrait être affichée une fois le travail terminé.

En heures de lecture, je suis tombé sur des concepts comme les demandes asynchrones, les files d'attente, les démons, Gearman, ou Catalyst::Plugin::RunAfterRequest.

Comment le feriez-vous? Merci d'aider un novice web dev! PS: Dans mon application locale actuelle, le travail est effectué en parallèle avec Parallel :: ForkManager. Pour la vraie application, serait-il conseillé d'utiliser un service de cloud computing comme Amazon EC2? Ou juste trouver un hébergeur qui offre des serveurs multi-core?

+3

Faire le téléchargement en tant que demande asynchrone aurait du sens. Renvoie un ID de travail et demande à l'action de définir un indicateur dans un modèle lorsqu'il est terminé. Ensuite, demandez à votre page d'interroger le backend de manière asynchrone régulièrement (comme tous les 10s) et si elle obtient un _done_, actualisez la page. Je vais taper une réponse dans un peu. – simbabque

+0

En ce qui concerne votre question Hoster, cela dépend vraiment du cas d'utilisation. Amazon ou d'autres services cloud ont l'avantage d'être facilement évolutifs si nécessaire, mais peuvent être plus coûteux que d'avoir votre propre serveur, en plus d'autres considérations. Cette question devrait probablement être affichée séparément et ailleurs. – bytepusher

+0

@simbabque Si vous avez le temps, des astuces pour des outils/plugins utiles ou des exemples de code seraient très utiles. Merci bytepusher, je vais considérer que lorsque l'application va en production –

Répondre

1

Placez le travail dans une file d'attente et procédez dans un processus différent, en dehors de l'application Web. Alors que votre processus Catalyst est occupé, même si vous utilisez Catalyst :: Plugin :: RunAfterRequest, il ne peut pas être utilisé pour traiter d'autres requêtes Web.

Il existe des systèmes de files d'attente très simples, tels que File::Queue. Fondamentalement, vous attribuez un ID de travail au document, mettez-le dans la file d'attente. Un autre processus vérifie la file d'attente et sélectionne de nouveaux travaux.

Vous pouvez enregistrer l'état du travail dans une base de données ou dans tout autre élément accessible aux applications Web. Sur le frontal, vous pouvez interroger le statut du travail toutes les X secondes ou minutes pour donner un retour à l'utilisateur.

Vous devez déterminer la quantité de mémoire et d'unité centrale dont vous avez besoin. Un processeur multi-cœur ou plusieurs processeurs peuvent ne pas être requis, même si plusieurs processus sont en cours d'exécution. Choisir entre un serveur dédié ou un cloud comme EC2 est plus une question de flexibilité (redimensionnement, instantané, etc.) que de prix.

+0

C'est à peu près ce que j'allais dire aussi. – simbabque

+0

Merci pour votre aide Julien. Je suis nouveau à l'aide de Perl pour les applications Web "complexes". Je ne pouvais pas comprendre comment utiliser File :: Queue. Pourriez-vous écrire un exemple de code pour la mise en file d'attente et l'interrogation de Catalyst? En dehors de cela, pensez-vous que je pourrais adapter cette approche en utilisant TheSchwartz: http://fayland.me/perl/2007/10/04/use-theschwartz-job-queue-to-handle/? –

+0

Le document pour File :: Queue contient des exemples: http://search.cpan.org/perldoc?File%3A%3AQueue – Julien

1

D'une manière ou d'une autre, je ne pouvais pas avoir l'idée de File :: Queue. Pour l'exécution parallèle non bloquante, j'ai fini par utiliser une combinaison de TheSchwartz et de Parallel :: Prefork comme si elle était implémentée dans le Foorum Catalyst App. Fondamentalement, il y a 5 éléments importants. Peut-être que ce résumé sera utile aux autres.

1) TheSchwartz DB

2) Un client (poignée DB) pour le TheSchwartz DB

package MyApp::TheSchwartz::Client; 

use TheSchwartz;  
sub theschwartz { 
    my $theschwartz = TheSchwartz->new(
     databases => [ { 
      dsn => 'dbi:mysql:theschwartz', 
      user => 'user', 
      pass => 'pass', 
     } ], 
     verbose => 1, 
    ); 
    return $theschwartz; 
} 

3) Un agent de travail (où le travail réel est effectué)

package MyApp::TheSchwartz::Worker::Test; 

use base qw(TheSchwartz::Moosified::Worker); 
use MyApp::Model::DB;  # Catalyst DB connect_info 
use MyApp::Schema;   # Catalyst DB schema 

sub work { 
    my $class = shift; 
    my $job = shift;  
    my ($args) = $job->arg; 
    my ($arg1, $arg2) = @$args; 

    # re-use Catalyst DB schema  
    my $connect_info = MyApp::Model::DB->config->{connect_info}; 
    my $schema = MyApp::Schema->connect($connect_info); 

    # do the heavy lifting 

    $job->completed(); 
} 

4) Un processus de travail TheSchwartzWorker.pl qui surveille la table travail non-stop

use MyApp::TheSchwartz::Client qw/theschwartz/; # db connection 
use MyApp::TheSchwartz::Worker::Test; 
use Parallel::Prefork; 

my $client = theschwartz(); 

my $pm = Parallel::Prefork->new({ 
    max_workers => 16, 
    trap_signals => { 
     TERM => 'TERM', 
     HUP => 'TERM', 
     USR1 => undef, 
    } 
}); 

while ($pm->signal_received ne 'TERM') { 
    $pm->start and next; 

    $client->can_do('MyApp::TheSchwartz::Worker::Test');  
    my $delay = 10; # When no job is available, the working process will sleep for $delay seconds 
    $client->work($delay); 

    $pm->finish; 
}  
$pm->wait_all_children(); 

5) Dans le contrôleur de catalyseur: insérer un nouvel emploi dans la table travail et passer quelques arguments

use MyApp::TheSchwartz::Client qw/theschwartz/; 
sub start : Chained('base') PathPart('start') Args(0) { 
    my ($self, $c) = @_; 

    $client = theschwartz(); 
    $client->insert(‘MyApp::TheSchwartz::Worker::Test’, [ $arg1, $arg2 ]); 

    $c->response->redirect(
     $c->uri_for(
      $self->action_for('archive'), 
      {mid => $c->set_status_msg("Run '$name' started")} 
     ) 
    ); 
} 

La nouvelle piste est grisé sur la page « archives » jusqu'à ce que tous les résultats sont disponibles dans la base de données.