web-dev-qa-db-fra.com

Solution unique sur plusieurs threads

Si je comprends bien, toutes les transactions sont liées à un thread (c'est-à-dire au contexte stocké dans ThreadLocal). Par exemple si: 

  1. Je commence une transaction dans une méthode parent transactionnelle
  2. Faire que la base de données insère # 1 dans un appel asynchrone
  3. Faire que la base de données insère # 2 dans un autre appel asynchrone

Cela donnera ensuite deux transactions différentes (une pour chaque insertion) même si elles partagent le même parent "transactionnel".

Par exemple, supposons que je réalise deux insertions (et en utilisant un exemple très simple, c’est-à-dire en n’utilisant pas d’exécuteur ni d’exécutable complet par souci de brièveté, etc.):

@Transactional
public void addInTransactionWithAnnotation() {
    addNewRow();
    addNewRow();
}

Effectuera les deux insertions, comme vous le souhaitez, dans le cadre de la même transaction.

Cependant, si je voulais paralléliser ces inserts pour des performances:

@Transactional
public void addInTransactionWithAnnotation() {
    new Thread(this::addNewRow).start();
    new Thread(this::addNewRow).start();
}

Ensuite, chacun de ces threads générés ne participera pas à la transaction car les transactions sont liées à des threads.

Question clé : Existe-t-il un moyen de propager la transaction en toute sécurité aux threads enfants?

Les seules solutions auxquelles j'ai pensé pour résoudre ce problème:

  1. Utilisez JTA ou un gestionnaire XA qui, par définition, devrait être capable de faire Ceci. Cependant, idéalement, je ne souhaite pas utiliser XA pour ma solution À cause des frais généraux 
  2. Transférez tout le travail transactionnel que vous voulez effectuer (dans l'exemple ci-dessus, la fonction addNewRow()) vers un seul thread et effectuez tous les travaux antérieurs de manière multithread.
  3. Trouver un moyen d'exploiter InheritableThreadLocal sur le statut de la transaction et de le propager aux threads enfants. Je ne sais pas comment faire ça.

Y a-t-il d'autres solutions possibles? Même si cela a un goût un peu comme une solution de contournement (comme mes solutions ci-dessus)?

8
Dovmo

Tout d’abord, une précision: si vous souhaitez accélérer plusieurs insertions du même type, comme votre exemple le suggère, vous obtiendrez probablement les meilleures performances en émettant les insertions dans le même fil et en utilisant un type de insertion de lot . En fonction de votre SGBD, plusieurs techniques sont disponibles, consultez:

En ce qui concerne votre question réelle, je voudrais personnellement essayer de diriger tout le travail vers un thread de travail. C'est l'option la plus simple, car vous n'avez pas besoin de manipuler ni ThreadLocals, ni l'inscription/délistement de transaction. En outre, une fois que vous avez vos unités de travail dans le même fil, si vous êtes intelligent, vous pourrez peut-être appliquer les techniques de traitement par lots décrites ci-dessus pour de meilleures performances. 

Enfin, le travail de tuyauterie vers les threads de travail ne signifie pas que vous devez avoir un seul thread de travail, vous pouvez avoir un pool de travailleurs et obtenir un certain parallélisme si cela est vraiment bénéfique pour votre application. Pensez en termes de producteurs/consommateurs.

2
gpeche

L'API JTA a plusieurs méthodes qui fonctionnent implicitement sur la transaction du thread en cours, mais cela n'empêche pas le déplacement ou la copie d'une transaction entre threads, ou l'exécution de certaines opérations sur une transaction qui n'est pas liée au thread actuel (ni à aucun autre). Cela ne cause pas de fin de maux de tête, mais ce n’est pas le pire….

Pour JDBC brut, vous n’avez pas du tout de transaction JTA. Vous avez une connexion JDBC, qui a ses propres idées sur le contexte de la transaction. Dans ce cas, la transaction est liée à la connexion et non au thread. Passez la connexion et le tx va avec. Mais les connexions ne garantissent pas nécessairement la sécurité des threads et constituent probablement un goulet d'étranglement en termes de performances. Par conséquent, le partage d'un thread entre plusieurs threads simultanés ne vous aide pas vraiment. Vous avez probablement besoin de plusieurs connexions qui pensent être dans la même transaction, ce qui signifie que vous avez besoin de XA, car c'est ainsi que la base de données identifie de tels cas. Vous êtes alors revenu à JTA, mais maintenant avec un JCA dans l'image pour gérer correctement la gestion des connexions. En bref, vous avez réinventé le serveur d’application JavaEE.

Pour les frameworks qui se superposent sur JDBC, par exemple Les ORM comme Hibernate, vous avez une complication supplémentaire: leurs abstractions ne sont pas nécessairement threadsafe. Vous ne pouvez donc pas avoir une session liée à plusieurs threads simultanément. Mais vous pouvez avoir plusieurs sessions simultanées, chacune participant à la même transaction XA.

Comme d'habitude, cela revient à la loi d'Amdahl. Si l'accélération de l'utilisation de plusieurs connexions par poste pour permettre à plusieurs threads simultanés de partager le travail d'E/S de la base de données est importante par rapport à ce que vous obtenez grâce au traitement par lots, la surcharge de XA en vaut la peine. Si l'accélération est en cours de calcul local et que l'entrée-sortie de la base de données est un problème mineur, un seul thread qui gère la connexion JDBC et transfère le travail de calcul non-IO à un pool de threads est la solution.

1
jbosstxnerd