On dégrossit la programmation réactive

Publié par Mickael Hafizou le

Temps de lecture : 4 minutes

Dans le cadre de travaux de R&D, nous nous sommes intéressés aux concepts de la programmation réactive.

Un SI classique s’appuie sur des composants backends qui communiquent entre eux. Certains de ces composants peuvent être soumis à des traitements longs impliquant fortement l’I/O (entrés/sorties). L’utilisation croissante de notre plateforme fait que ces composants sont de plus en plus sollicités.

Nous nous sommes donc penchés sur la programmation réactive qui nous paraissait être un élément puissant à prendre en compte dans notre démarche.

Dans cette première partie, nous allons aborder la problématique et les concepts afin de comprendre la base de cette technologie.

Problématique

La montée en puissance des échanges de données entre systèmes d’informations implique de la charge importante.

Ce flux de données à débit croissant tend vers une saturation des unités de traitement. Par exemple, avec la fibre, la vitesse de téléchargement devient supérieure à la vitesse d’écriture des disques durs.

Cela s’applique également aux applications. Cette forte charge sollicite les threads des applications qui peuvent être soumis à des problèmes de temps de réponse, voir de TimeOut, qui aboutissent à ralentir et rendre la plateforme indisponible. En programmation classique, tous les traitements se font de manière synchrone et séquentielle. Une ligne de code ne s’exécute que si la ligne précédente est terminée. Si un appel externe à l’application est effectué, l’exécution globale du traitement se retrouve dépendant du temps d’exécution de cet appel externe. On peut ajouter le fait que les serveurs d’application classiques sont souvent limités à un nombre fixe de thread. Toutes ces contraintes cumulées font perdre de l’élasticité aux applications.

La question est donc : comment faire en sorte que les composants applicatifs puissent continuer à rendre efficacement leur service avec cette charge qui augmente ?

Les concepts de base

Au niveau du code

Dans une exécution linéaire ou séquentielle, un bloc de code attend que le suivant fasse son traitement pour lui proposer de nouveaux éléments à traiter, afin qu’à son tour il puisse traiter de nouveaux éléments : cela s’appelle la BackPressure, ou contre-pression.

Le concept principal de la programmation réactive est d’éviter que notre application attende que des traitements externes se terminent. L’idée étant de ne plus exécuter le code de manière séquentiel et globale, afin d’utiliser les ressources CPU de manière optimisée pour réduire les temps de pause (idle), avoir des temps de traitements non pénalisés par le volume de données, et permettre de libérer plus rapidement les threads.

La programmation réactive est donc un cumul de différentes techniques permettent d’atteindre ce résultat :

  • Le pattern Observable : pour la gestion événementielle de couple émetteur / récepteur
  • L’asynchrone : pour isoler chaque unité de traitement
  • Le Stream : pour réduire la monté en mémoire et itérer sur les éléments
  • Le Multi-thread : pour paralléliser le traitement de chaque unité

Ce fonctionnement va orienter le code des traitements pour s’inscrire dans une file d’exécution spécifique, dont chaque bloc s’exécutera et se terminera dès que possible.

Au lieu d’attendre le suivant, chaque bloc se déroule au plus vite, puis libère son Thread pour redistribuer le temps de calcul du CPU aux autres blocs.

Cela signifie qu’un traitement n’ira pas plus vite à l’exécution, mais il sera moins voire pas pénalisé par la volumétrie, ni même par le temps d’exécution d’autres traitements.

Au niveau du serveur d’application

Dans l’objectif d’une stack réactive, le serveur d’application doit également fournir un fonctionnement non-bloquant.

Pour un serveur d’application classique, le paramétrage du nombre de thread maximum détermine le nombre de requêtes actives possible en simultané. Si un nombre de connexions atteint cette limite, l’application n’accepte plus de requêtes en entrée : les utilisateurs sont bloqués. Si l’attente est trop longue, la requête est interrompue en TimeOut. De plus, le serveur va continuer son traitement sans pour autant que sa réponse ne soit consommée. 

Le souci de ce fonctionnement bloquant peut donc engendrer un impact négatif vis à vis des utilisateurs, et pénaliser l’ensemble de ses services.

Un serveur d’application non bloquant comme Netty fonctionne avec peu de thread en entrée. Ces threads sont tout le temps en vie et bouclent systématiquement sur les événements qui lui sont envoyés. Les événements correspondent aux requêtes en entrée. 

Conclusion

En conclusion de cette première partie, on peut dire que la programmation réactive va changer notre façon de concevoir et développer une application. On rentre réellement dans un nouveau paradigme fortement basé sur la programmation fonctionnelle.

On peut également dire qu’il ne faut pas aborder la programmation réactive dans le but de résoudre des problèmes de performances d’une application. Il faut plutôt partir du postulat que les I/O longs seront toujours aussi longs, mais que ceux-ci n’auront plus d’impact sur le fonctionnement global de l’application.

Dans un contexte de forte digitalisation et d’échange de données massive, la technologie semble parfaitement adaptée pour répondre à des soucis de robustesse. Elle reste cependant encore jeune et il faudra prévoir une courbe d’apprentissage non négligeable pour la maîtriser.

Dans la deuxième partie, nous allons entrer plus précisément dans le concret pour comprendre les différents concepts de la programmation réactive. Nous présenterons notre démarche (tests, code, benchmark …) et les éléments et constats que nous en avons ressortis.

Catégories : Développement

Mickael Hafizou

Practice Leader