Comment j'ai battu CORBA
Dans le domaine de l'intégration, il existe encore des cas d'usages où la technologie CORBA restait souveraine par manque de challengers suffisamment performants. Je pense à des services interopérables avec plusieurs milliers d’appels synchrones par seconde et une latence en dessous des 5 millisecondes.
Avec mon client, j’ai fait le pari que je pouvais atteindre le même niveau de performance avec des technologies JAVA en utilisant le standard JSON sur HTTP (REST) …
… mais il ne m’a pas cru. (Sic)
Disclaimer : CORBA est une technologie riche pouvant mettant en œuvre des objets distribués avec une gestion de callbacks mais aussi des transactions distribuées. Le cas présenté ici se « limite » à l’appel de méthodes stateless comme on le ferait à la manière d’un web service ou de systèmes dits « Remote Procedure Call » (RPC) et ne représente pas donc la totalité des possibilité offertes.
Tentative 1
Pour battre un monstre, il est nécessaire de sortir l’artillerie lourde.
Il me fallait un Runtime rapide : J’ai anticipé qu'un serveur d'application full-stack allait introduire des filtres HTTP dans toutes les requêtes et fournir un environnement managé qui allait nuire aux performances. Je suis donc parti sur un serveur léger embarqué Jetty dans lequel j'ai activé les NIO.
Il me fallait une sérialisation rapide : Faisant confiance à la communauté, ainsi qu'à quelques benchmarks , je choisis de prendre Jackson comme parseur JSON. (Par la suite des essais avec boon ou json-simple n’amélioreront rien).
J'ai écrit les interfaces en suivant la démarche HATEOAS afin de réduire la taille des requêtes entrantes et de me limiter le plus possible à des paramètres simples.
Les premiers résultats tombent et l’on mesure une dégradation des performances de l'ordre de 35%.
Dans le roundtrip je perdais du temps partout (en bleu sur le schéma) : à la sérialisation, à la désérialisation mais également sur le réseau.
Tentative 2
Je garde Jetty.
Je décide de sérialiser les objets « à la main », je surcharge donc la méthode
toString()
et écris moi-même les chaînes JSON au moyen d’unStringBuilder
. Jackson réalise une introspection des objets qui introduisait un surcoût.Je décide de désérialiser "à la main"... et c'est une catastrophe. La lecture de chaînes JSON ne s’improvise pas, les opérations de
split()
et dereplace()
sont lentes (strCopy ?) de plus c’est une gageure à coder. Marche arrière - je reviens sur Jackson mais lui demande uniquement de produire uneMap
. Je n'ai qu'à surcharger le constructeur de mes objets pour accepter cette structure.
La nouvelle tentative m’indique une amélioration significative des temps de réponse (en vert sur le schéma) cependant je suis toujours 10% plus lent que le service CORBA.
Tentative 3 (désespérée)
Cette fois on ne rigole plus, je n’ai plus beaucoup de munitions en réserve. Il me reste à améliorer le temps sur le réseau et à agir sur les conteneurs.
Pour le réseau j’active la compression GZIP en espérant que le temps de compression sera inférieur à la latence réseau et ça marche, on note une légère amélioration.
Je transforme légèrement la partie serveur pour utiliser le framework de microservices Dropwizard. Ce dernier s’appuie toujours sur les mêmes technologies Jetty/Jersey/Jackson mais simplifie la configuration du runtime comme le nombre de threads, le keep-alive ou encore la gestion des timeouts. Plusieurs essais me permettent de configurer au mieux le conteneur pour absorber le scénario de tir prédéfini. Pour des raisons legacy, je suis bloqué sur un JRockIt JDK6.
Je monte le client sur un JDK8.
Le tir de performance suivant est concluant car je suis désormais plus performant que CORBA de l'ordre de 10 à 15%, et même en activant HTTPS, nous conservons une légère avance !
Autre piste non implémentée : Il est possible de gagner encore du temps au niveau du client lors de la réception de la réponse. On peut envisager un constructeur prenant en paramètre la chaine JSON renvoyée par le serveur, cette dernière ne serait réellement interprétée que lorsque l’on invoque l’un des accesseurs. Ce mode « lazy » déporte la désérialisation plus loin dans le code client et permet d’augmenter le throughput.
Conclusion :
Vous pouvez défendre qu’il est aujourd’hui possible, moyennant quelques astuces, de substituer une couche d’accès CORBA (sans l’aspect transactionnel ni stateful) par des services REST/JSON.
Dans notre démarche nous recherchions une solution très interopérable et l’on peut constater que nous n’avons pas eu recours à des formats de sérialisations binaires comme protocol buffers, Avro ou encore Thrift qui entraînent immanquablement une limitation. Nous vous conseillons de tester avec la compression GZIP et de ne pas optimiser prématurément.