Ce post est à moitié une manière de faire , et une autre une façon de mieux le faire?

Donc, vous voulez construire un système qui effectue des tâches. Vous voulez que le travail puisse être organisé en parallèle pour la vitesse, mais aussi pour la redondance. Ce système doit être coordonnée de façon, par exemple, les mêmes tâches ne sont pas faites deux fois, le statut de chaque tâche est facile à voir, et de multiples serveurs peuvent effectuer les tâches simplement en interrogeant la source centrale.

Voici la traduction de Creating a Job queue in Innodb Comment peut-on construire cela avec innodb pour avoir MySQL comme système central de notre système?

[MYSQL]
CREATE TABLE IF NOT EXISTS job_queue(
   id int(10) not null auto_increment,

   updated timestamp not null,
   started timestamp not null,

   state ENUM('NEW', 'WORKING', 'DONE', 'ERROR' ) default 'NEW',

   PRIMARY KEY ( id ),
   KEY( STATE )
) ENGINE=Innodb;

Dans ce schéma, notre table de tâches a un identifiant unique, une heure de démarrage et de mise à jour et un statut.

Dans un système réel, il y aura sans doute un peu plus de méta-données ici sur la nature des tâches.

Lorsque de nouvelles tâches doivent être programmées, les lignes sont insérées dans la table (avec started à NOW (), et updated est mis à jour automatiquement)

[MYSQL]
insert into job_queue set started=NOW();

Maintenant, en perl, nous écrivons un contrôleur de tâches. Ce programme peut être exécuté sur chaque serveur qui pourrait accomplir les tâches.

[perl]
#!/usr/bin/perl

# Don't leave zombies around, shamelessly stolen from 'man perlipc'
use POSIX ":sys_wait_h";
sub REAPER {
    my $child;
    while (($child = waitpid(-1,WNOHANG)) > 0) {
        $Kid_Status{$child} = $?;
    }
    $SIG{CHLD} = \&REAPER;
}
$SIG{CHLD} = \&REAPER;

use DBD::mysql;

my $dbh = DBI->connect(
'DBI:mysql:database=test;host=127.0.0.1;port=3306',
'test', 'test',
{ RaiseError => 1, AutoCommit => 0 }
);


while( 1 ) {
 my $row;
 eval {
     my $sth = $dbh->prepare( "select id from job_queue where state='NEW' li
mit 1 for update" );
     $sth->execute();
     $row = $sth->fetchrow_hashref();
     $sth->finish();
 };

 if( $@ or !$row ) {
     # Couldn't lock or lock wait timeout
     $dbh->commit();
     sleep 10;
     next;
 }

 # Got one, change state, commit, and fork a worker

 $dbh->do( "update job_queue set state='WORKING' where id=" . $row->{id} );
 $dbh->commit();

 # Fork a worker
 if( fork )  {
     # Parent, let the child have the old connection and reconnect to
     # the db.
     $dbh->{InactiveDestroy} = 1;
     $dbh = DBI->connect(
        'DBI:mysql:database=test;host=127.0.0.1;port=3306',
        'test', 'test',
        { RaiseError => 1, AutoCommit => 0 }
     );
 } else{
     # fils
     print "Création du verrou\n";
     $dbh->do( "select * from job_queue where id=" . $row->{id}
         . " for update " );

     print "Travail en cours\n";
     #On simule un traitement
     srand( time );
     sleep rand 30;

     $dbh->do( "update job_queue set STATE='DONE' where
         id=" . $row->{id} );
     print "On Commit\n";
     $dbh->commit();

     $dbh->disconnect();

     exit;
 }
 sleep 1;
}

Maintenant, ce n'est pas mauvais. Mais il a au moins une chose que je n'aime pas. Si une des tâches n'aboutit pas, il n'existe aucun moyen pour réaffecter la tâche.

Ideally each worker would have a lock on his job row inside of a transaction, which it is doing now, but, instead of the job being in the 'WORKING' state first, I'd rather it was 'NEW' before the transaction started. If that were the case, if a worker died, it's unfinished transaction would be rolled back, and the job would be unlocked and NEW again.

Idéalement, chaque travailleur aurait un verrou sur son travail dans une transaction, ce qui est fait maintenant, mais, au lieu du travail en cours dans le «travail» d'abord, je préfère c'est "NOUVEAU" avant l'opération a commencé . Si tel était le cas, si un travailleur est mort, il est inachevé transaction serait annulée, et la tâche serait déverrouillé et NEW nouveau.

However, because of the way SELECT ... FOR UPDATE works, Innodb will deadlock waiting for 'NEW' jobs to unlock from the workers (or wait until they are done). This is sub-optimal, since my parent process could be forking new jobs in the meantime. What would fix this is a SELECT ... FOR UPDATE that skipped locked rows without blocking. Toutefois, en raison de la façon SELECT ... MISE À JOUR DE œuvres, InnoDB va impasse d'attente pour les «nouveaux» pour déverrouiller l'emploi des travailleurs (ou d'attendre jusqu'à ce qu'ils soient fait). Cela est sous-optimale, car mon père pourrait être forking de nouveaux emplois dans l'intervalle. Qu'est-ce que ce correctif est un SELECT ... MISE À JOUR DE verrouillé ignoré que des lignes sans blocage.

If anyone knows a good way to achieve this, please let me know!

However, assuming we have no better alternatives, we can create a reaper process like this:

[perl]
#!/usr/bin/perl

use DBD::mysql;

my $dbh = DBI->connect(
   'DBI:mysql:database=test;host=127.0.0.1;port=3306',
   'test', 'test',
   { RaiseError => 1, AutoCommit => 0 }
);


while( 1 ) {
    my $row;
    eval {
        my $sth = $dbh->prepare( "select id from job_queue where state='WORKING' limit 1 for update" );
        $sth->execute();
        $row = $sth->fetchrow_hashref();
        $sth->finish();
    };

    if( $@ or !$row ) {
        # Couldn't lock or lock wait timeout
        $dbh->commit();
        sleep 10;
        next;
    }

    # Got one, change state, commit, and fork a worker

    $dbh->do( "update job_queue set state='NEW' where id=" . $row->{id} );    $dbh->commit();

    sleep 1;
}

This will find the first WORKING row and try to lock it. Since normal jobs will be moved to DONE before they are committed, this should only ever find jobs that are in WORKING and unlocked, which means the worker died.

However this isn't perfect. Because SELECT ... FOR UPDATE will try to lock a row already locked, we have to wait until all jobs before our stalled job are complete, getting deadlocks along the way (be sure to set your innodb_lock_wait_timeout fairly low!. Even further, I've seen dead worker processes leave behind idle mysql connections that hold onto their row locks.

Is there any smoother way to do this? I'd love to hear other people's advice.