Traitements asynchrones


1Présentation

Temma propose Asynk, un moyen simple d'effectuer des traitements asynchrones. Ce système est basé sur l'utilisation du composant d'injection de dépendances, en l'étendant pour que les appels aux objets gérés par le composant soient traités de manière asynchrone.

Exemple :

// appel synchrone via le loader
$this->_loader->MonObjet->maMethode($param1, $param2);

// appel identique en asynchrone
$this->_loader->asynk->MonObjet->maMethode($param1, $param2);

Comme pour le composant d'injection de dépendances, il est possible d'utiliser une notation de type tableau pour exécuter un objet placé dans un namespace :

// appel asynchrone
$this->_loader->asynk['\Mon\Name\Space\MonObjet']->maMethode($param1, $param2);

Les tâches peuvent être exécutées de plusieurs manières différentes :

  • par des workers Temma (des programmes qui tournent en tâche de fond)
  • par un script Temma exécuté par crontab toutes les minutes
  • par un script Temma exécuté par xinetd à chaque fois qu'une tâche est lancée

Pour les exécutions par worker ou par crontab, les tâches peuvent être récupérées depuis une file de messages (Beanstalkd ou AWS SQS) ou une base de données (MySQL ou Redis). Pour l'exécution par xinetd, seuls les stockages en base de données (MysQL ou Redis) sont possibles.

Attention aux limites concernant les données stockées pour chaque tâche :

  • 16 MO pour MySQL
  • 512 MO pour Redis
  • 64 KO pour Beanstalk
  • 512 KO pour SQS

Beanstalkd est la file de messages conseillée pour l'exécution de tâches avec Asynk.
Vous pouvez aussi utiliser une file Amazon SQS, mais son fonctionnement implique que les workers se connectent régulièrement pour voir s'il y a des tâches en attente, sachant qu'une facturation s'applique à chaque reqête effectuée.

La solution la plus simple à mettre en œuvre est de stocker les tâches en base de données, et de les exécuter par crontab. Pour une exécution rapide des tâches, au plus proche du temps réel, il est conseillé d'ajouter une exécution par serveur xinetd.


2Configuration

2.1Principe

La configuration repose sur une configuration étendue x-asynk dans le fichier etc/temma.php, contenant deux paramètres :

  • transport : le nom de la source de données qui va servir à transmettre les tâches.
    Le comportement sera différent en fonction du type de la source de données :
    • non définie : traitement par crontab ou worker avec un stockage MySQL ou Redis
    • socket : traitement par xinetd avec un stockage MySQL ou Redis
    • Beanstalk : traitement par la file de messages Beanstalkd
    • SQS : traitement par la file de messages AWS SQS
  • storage : le nom de la source de données qui va stocker les messages en attendant leur traitement, dans le cas où il n'y a pas de file de messages :
    • MySQL : stockage dans une base MySQL.
    • Redis : stockage dans une base Redis.

Suivant les valeurs, l'un ou l'autre paramètre peut être optionnel.

Voici les combinaisons possibles :

transport storage Traitement
MySQL Traitement par crontab ou par workers
Redis
socket MySQL Traitement par xinetd (+ crontab en option)
socket Redis
Beanstalk Traitement par workers
SQS

Asynk écrit des messages de log en utilisant la classe de log Temma/Asynk.

Voici un exemple de fichier de configuration :

<?php

return [
    'application' => [
        // sources de données
        'dataSources' => [
            // file de messages Amazon SQS
            'sqs' => 'sqs://AKXYZ:PWD@sqs.eu-west-3.amazonaws.com/123456789012/queue_name',
        ]
    ],
    // seuils des messages de log
    'loglevels' => [
        'Temma/Base'  => 'ERR',
        'Temma/Web'   => 'WARN',
        'Temma/Asynk' => 'NOTE',
    ],
    // configuration Asynk
    'x-asynk' => [
        // transport : file SQS
        'transport' => 'sqs',
    ],
];

2.2Stockage MySQL

Si vous choisissez un stockage des tâches dans une base de données MySQL, il faut créer la table qui contiendra les tâches.

Voici la requête de création de cette table :

CREATE TABLE Task (
    id             INT UNSIGNED NOT NULL AUTO_INCREMENT,
    dateCreation   DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    dateUpdate     DATETIME ON UPDATE CURRENT_TIMESTAMP,
    status         ENUM('waiting', 'reserved', 'processing', 'error') NOT NULL DEFAULT 'waiting',
    token          CHAR(16) CHARACTER SET ascii COLLATE ascii_general_ci,
    target         TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
    action         TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
    data           MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
    PRIMARY KEY (id),
    INDEX status (status),
    INDEX token (token)
);

La clé primaire (champ id) peut être du type BIGINT si vous avez besoin de gérer plus de 4 milliards de tâches.

Si la table est stockée dans une base différente de celle de la connexion, ou si elle est nommée autrement que Task, ou que les champs ont d'autres noms, il est possible de le spécifier dans la configuration étendue x-asynk du fichier etc/temma.php.

<?php

return [
    'x-asynk' => [
        'base'   => 'asynk_db',
        'table'  => 'asynk_tasks',
        'id'     => 'task_id',
        'status' => 'task_status',
        'token'  => 'task_token',
        'target' => 'task_target',
        'action' => 'task_token',
        'data'   => 'task_data',
    ]
];
  • Ligne 5 : Nom de la base de données qui contient la table.
  • Ligne 6 : Nom de la table.
  • Ligne 7 : Nom du champ contenant la clé primaire.
  • Ligne 8 : Nom du champ contenant le statut.
  • Ligne 9 : Nom du champ contenant le jeton de réservation.
  • Ligne 10 : Nom du champ contenant l'objet cible.
  • Ligne 11 : Nom du champ contenant la méthode cible.
  • Ligne 12 : Nom du champ contenant la sérialisation des paramètres.

Il est aussi possible d'utiliser une DAO personnalisée :

<?php

return [
    'x-asynk' => [
        'dao' => '\MyApp\AsynkDao'
    ]
];

3Traitement par crontab

3.1Présentation de la crontab

Le traitement par crontab est très simple à mettre en œuvre. Le démon crontab est installé ou installable sur tous les systèmes Unix, et permet d'être indépendant de tout autre logiciel comme une file de messages.

Le traitement par crontab est lancé toutes les minutes. Cela peut amener un léger délai dans le traitement des tâches. Si vous avez besoin d'un traitement sans latence, vous pouvez ajouter un traitement par xinetd (voir plus bas).


3.2Configuration de Temma pour la crontab

Le stockage utilisé pour enregistrer les tâches doit être déclaré dans la configuration étendue x-asynk.

Exemple de fichier etc/temma.php, avec un stockage Redis :

<?php

return [
    'application' => [
        'dataSources' => [
            'ndb' => 'redis://localhost'
        ]
    ],
    'x-asynk' => [
        'storage'   => 'ndb'
    ],
];

3.3Configuration de la crontab

Pour configurer la crontab, vous avez deux possibilités :

  • Modifier le contenu du fichier etc/asynk/crontab pour adapter le chemin vers la racine de votre projet.
    Ensuite, copier ce fichier dans /etc/cron.d/ (sous un nom adapté), par exemple avec la commande :
    sudo cp /chemin/vers/projet/etc/asynk/crontab /etc/cron.d/mon_projet
  • Ou ajouter la ligne suivante (en adaptant le chemin) dans la crontab de l'utilisateur désiré :
    * * * * *    cd /chemin/vers/projet/; bin/comma AsynkWorker crontab

4Traitement par xinetd

4.1Présentation de xinetd

Xinetd est un "super-démon" qui écoute sur plusieurs ports réseau. À chaque fois qu'il reçoit une connexion entrante, il lance le programme associé, et se charge des échanges réseau.
Pour la gestion d'Asynk, xinetd écoute par défaut sur le port 11137.

Lorsque Asynk utilise xinetd, les tâches asynchrones sont traitées immédiatement. Il y a toutefois deux choses à prendre en considération :

  • Si vous devez traiter un très grand nombre de tâches simultanées, xinetd montrera sa limite. Il est alors conseillé de passer par une file de message (Beanstalkd ou SQS).
  • Il peut arriver que xinetd ne puisse pas traiter certaines tâches (le démon ne tourne pas ou il est saturé). Il est donc conseillé de conjuguer le traitement par xinetd avec le traitement par crontab (voir plus haut), afin que les tâches qui n'ont pas été traitées par xinetd le soient par la crontab.

4.2Configuration de Temma pour xinetd

Dans la configuration, il faut prévoir une source de données de type socket, qui servira a Asynk pour se connecter à xinetd. Le serveur xinetd peut être sur le serveur local ou sur une machine distante. Par défaut, le port de connexion utilisé par Asynk est le 11137.

Exemple de fichier etc/temma.php, avec un stockage MySQL :

<?php

return [
    'application' => [
        'dataSources' => [
            'sock' => 'tcp://localhost:11137',
            'db'   => 'mysql://user:password@localhost'
        ]
    ],
    'x-asynk' => [
        'transport' => 'sock',
        'storage'   => 'db'
    ],
];

4.3Configuration de xinetd

Pour configurer xinetd, modifiez le fichier etc/asynk/xinetd pour adapter le chemin, puis copiez-le dans le répertoire /etc/xinetd.d/ (sous un nom adapté), par exemple avec la commande :

sudo cp /chemin/vers/projet/etc/asynk/xinetd /etc/xinetd.d/mon_projet


4.4(optionnel) Configuration de la crontab

Utiliser la crontab en plus de xinetd permet de s'assurer que toutes les tâches soient traitées, même si xinetd ne tourne pas ou est saturé.

Pour configurer la crontab, vous avez deux possibilités :

  • Modifier le contenu du fichier etc/asynk/crontab pour adapter le chemin vers la racine de votre projet.
    Ensuite, copier ce fichier dans /etc/cron.d/ (sous un nom adapté), par exemple avec la commande :
    sudo cp /chemin/vers/projet/etc/asynk/crontab /etc/cron.d/mon_projet
  • Ou ajouter la ligne suivante (en adaptant le chemin) dans la crontab de l'utilisateur désiré :
    * * * * *    cd /chemin/vers/projet/; bin/comma AsynkWorker crontab

5Traitement par workers

5.1Présentation des workers

Les workers sont des programmes qui tournent en tâche de fond. Vous pouvez faire tourner autant de workers que vous le souhaitez. Si un seul worker s'exécute, on peut considérer qu'il s'agit d'un démon de traitement.

Les workers se connectent à la source de données (file de message ou base de données) pour récupérer les tâches à effectuer. Ils récupèrent les tâches une à une, et les traitent séquentiellement. Si vous avez un grand nombre de tâches à traiter, il vaut donc mieux avoir plusieurs workers en parallèle, sinon les tâches pourraient s'empiler plus vite qu'elles ne sont traitées.

Il est important de s'assurer qu'un nombre minimal de workers tourne en permanence, pour ne pas risquer que des tâches ne soient pas traitées. Il est possible d'utiliser un superviseur comme Supervisord, qui redémarrera automatiquement les workers si le nombre minimal d'instances n'est pas assuré.


5.2Configuration de Temma pour les workers

La configuration Temma doit contenir des informations sur le transport et/ou le stockage des tâches.

Exemple de fichier etc/temma.php, avec un stockage MySQL :

<?php

return [
    'application' => [
        'dataSources' => [
            'db'        => 'mysql://user:password@localhost'
        ]
    ],
    'x-asynk' => [
        'storage'   => 'db'
    ],
];

Autre exemple de fichier etc/temma.php, avec un transport Beanstalkd :

<?php

return [
    'application' => [
        'dataSources' => [
            'beanstalk' => 'beanstalk://localhost/tube3'
        ]
    ],
    'x-asynk' => [
        'transport' => 'beanstalk'
    ],
];

Par défaut, les workers qui font une écoute active (“polling”) attendent 60 secondes entre deux connections pour récupérer des tâches en attente. Cela concerne les workers qui se connectent à une file Amazon SQS ou à une base MySQL ou Redis ; cela ne concerne pas les files Beanstalkd.

Il est possible de modifier ce délai avec le paramètre loopDelay de la configuration étendue x-asynk :

<?php

return [
    'application' => [
        'dataSources' => [
            'db'        => 'mysql://user:password@localhost'
        ]
    ],
    'x-asynk' => [
        'storage'   => 'db',
        // délai de 90 secondes entre deux vérifications
        'loopDelay' => 90
    ],
];

5.3Configuration de Supervisor

L'utilisation de Supervisor est optionnelle, mais elle peut être utile pour s'assurer que les workers tournent, et qu'ils sont relancés si un problème survenait.

Recopiez le fichier etc/asynk/supervisor.conf vers /etc/supervisor/conf.d/asynk.conf avec la commande suivante :

sudo cp /chemin/vers/projet/etc/asynk/supervisor.conf /etc/supervisor/conf.d/asynk.conf

Modifiez ensuite les paramètres suivants du fichier :

  • command : Indiquez le chemin correct vers la racine de votre projet.
    Exemple : command=/chemin/vers/projet/bin/comma AsynkWorker
  • numprocs : Indiquez le nombre de workers qui doivent s'exécuter en même temps.
    Exemple : numprocs=5

Forcez ensuite Supervisor à prendre en compte cette configuration :

sudo supervisorctl reread
sudo supervisorctl update