Meilleure façon d'implémenter une file d'attente basée sur une table simultanée

10

J'ai une table dans MySQL qui représente une file d'attente de liens à traiter. Les liens sont traités par une application externe, un par un, et supprimés à la fin. Il s'agit d'une file d'attente à volume élevé et j'ai plusieurs instances de l'application de traitement, réparties sur plusieurs serveurs.

Comment puis-je m'assurer que chaque enregistrement est sélectionné par une seule application? Existe-t-il un moyen de signaler / verrouiller l'enregistrement?

En ce moment, pour éviter que deux ou plus ne récupèrent le même lien, je n'autorise chaque instance qu'à récupérer un certain ensemble d'enregistrements (basé sur le MOD de leur ID), mais ce n'est pas un moyen transparent d'augmenter le traitement de la file d'attente accélérer simplement en ajoutant de nouvelles instances.

Miguel E
la source
Mon mantra: "Ne faites pas la queue, faites-le". Autrement dit, au lieu de lancer une tâche dans une file d'attente, lancez un processus pour effectuer la tâche.
Rick James

Réponses:

7

Premièrement: MySQL est l'un des pires logiciels possibles pour l'implémenter, surtout s'il est très dynamique. La raison en est que les moteurs comme MEMORY et MyISAM n'ont que des verrous de table complète tandis que les moteurs plus appropriés comme InnoDB ont une pénalité en écriture plus élevée (pour fournir des propriétés ACID) et sont optimisés pour accéder aux enregistrements qui sont spatialement et temporellement proches (ceux-ci sont définis en mémoire ). Il n'y a pas non plus de bon système de notification de changement pour MySQL - il doit être implémenté comme un sondage. Il existe des dizaines de logiciels plus optimisés pour cette tâche .

Cela dit, j'ai vu avec succès mettre en œuvre ce type d'accès si les exigences de performance / efficacité ne sont pas très élevées. De nombreuses personnes ne peuvent pas se permettre d'introduire et de maintenir une technologie complète et distincte uniquement pour une petite partie de la logique métier.

SELECT FOR UPDATEc'est ce que vous cherchez - lire la sérialisation. Bien qu'une MISE À JOUR / SUPPRIMER verrouille toujours la ligne pendant une transaction MYSQL en cours d'exécution, vous souhaiterez peut-être éviter une transaction volumineuse pendant le processus, donc:

START TRANSACTION;
SELECT * FROM your_table WHERE state != 'PROCESSING' 
  ORDER BY date_added ASC LIMIT 1 FOR UPDATE;
if (rows_selected = 0) { //finished processing the queue, abort}
else {
UPDATE your_table WHERE id = $row.id SET state = 'PROCESSING'
COMMIT;

// row is processed here, outside of the transaction, and it can take as much time as we want

// once we finish:
DELETE FROM your_table WHERE id = $row.id and state = 'PROCESSING' LIMIT 1;
}

MySQL se chargera de verrouiller toutes les sélections simultanées sauf une lors de la sélection des lignes. Comme cela peut entraîner de nombreuses connexions verrouillées en même temps, gardez la transaction initiale aussi petite que possible et essayez de traiter plus d'une ligne à la fois.

jynus
la source
Merci. Pensez-vous que les performances peuvent bénéficier d'un verrou plus grand (en changeant la LIMITE pour dire 10)?
Miguel E
@MiguelE En général, oui, plus vous passez de temps à traiter et moins vous risquez d'entrer en collision avec d'autres transactions, mieux c'est. Mais cela peut dépendre dans certains cas, cela pourrait également provoquer l'effet inverse (plus de transactions sont verrouillées). Testez-le toujours en premier. Il est également important d'indexer correctement la table, sinon vous pouvez vous retrouver avec un verrouillage complet de la table dans certains modes d'isolement.
jynus
1
Et ce serait probablement une bonne idée de garder une trace de la date à laquelle vous avez commencé à traiter la ligne juste au cas où le processus se bloquerait et que vous souhaitez implémenter un mécanisme de temporisation.
Julian
3

Comme je l'ai expliqué dans cet article , MySQL 8 a introduit la prise en charge de SKIP LOCKED et NO WAIT.

SKIP LOCKED est utile pour implémenter des files d'attente de travaux (alias files d'attente par lots) afin que vous puissiez ignorer les verrous déjà verrouillés par d'autres transactions simultanées.

NO WAIT est utile pour éviter d'attendre qu'une transaction simultanée libère les verrous que nous souhaitons également verrouiller. Sans NO WAIT, nous devons soit attendre que les verrous soient libérés (au moment de la validation ou de la libération par la transaction qui détient actuellement les verrous), soit que le délai d'acquisition des verrous expire. Par conséquent, NO WAIT agit comme un délai de verrouillage avec une valeur de 0.

Pour plus de détails sur SKIP LOCK et NO WAIT, consultez cet article .

Vlad Mihalcea
la source
0

J'ai fait quelque chose de similaire avec des vérifications DBCC hors ligne (deux serveurs effectuant des restaurations de sauvegarde, puis une vérification DBCC). Un serveur rassemble toutes les 31 sauvegardes du serveur hier et les place dans une file d'attente, puis ce serveur et un autre tirent de cette file d'attente. Bien qu'il n'y ait pas beaucoup de serveurs, la méthode doit rester la même: demandez au serveur d'applications d'exécuter une requête de mise à jour par rapport à la file d'attente mettant à jour un champ de date / heure et un champ "serveur d'applications" avec le nom de ce serveur d'applications ou un ID encore meilleur numérique. Cela entraînera un verrouillage ou s'il existe déjà un verrou d'un autre serveur obtenant la ligne suivante, il sera bloqué et attendra que l'autre application ait fini d'obtenir la ligne suivante. Vous souhaiterez ensuite que l'application retire l'enregistrement le plus récent de la file d'attente pour son champ d'application et obtienne les informations que vous souhaitez. Utiliser MySQL '

Chris Woods
la source