L'équipe du LMAX a présenté comment elle a réussi à faire 100 000 TPS avec moins de 1 ms de latence . Ils ont soutenu cette présentation avec un blog , un document technique (PDF) et le code source lui-même.
Récemment, Martin Fowler a publié un excellent article sur l'architecture LMAX et mentionne qu'ils sont maintenant capables de gérer six millions de commandes par seconde et met en évidence quelques-unes des étapes que l'équipe a prises pour augmenter un autre ordre de grandeur dans les performances.
Jusqu'à présent, j'ai expliqué que la clé de la vitesse du Business Logic Processor est de tout faire séquentiellement, en mémoire. Le fait de faire cela (et rien de vraiment stupide) permet aux développeurs d'écrire du code capable de traiter le TPS 10K.
Ils ont ensuite découvert que se concentrer sur les éléments simples d'un bon code pourrait faire remonter cela dans la gamme 100K TPS. Cela nécessite simplement un code bien factorisé et de petites méthodes - cela permet essentiellement à Hotspot de faire un meilleur travail d'optimisation et aux CPU d'être plus efficaces dans la mise en cache du code pendant son exécution.
Il a fallu un peu plus d'intelligence pour remonter d'un autre ordre de grandeur. Il y a plusieurs choses que l'équipe LMAX a trouvées utiles pour y arriver. L'une consistait à écrire des implémentations personnalisées des collections Java conçues pour être compatibles avec le cache et soigneuses avec les déchets.
Une autre technique pour atteindre ce niveau supérieur de performances consiste à mettre l'accent sur les tests de performances. J'ai longtemps remarqué que les gens parlent beaucoup de techniques pour améliorer les performances, mais la seule chose qui fait vraiment la différence est de les tester
Fowler a mentionné qu'il y avait plusieurs choses qui ont été trouvées, mais il n'en a mentionné que quelques-unes.
Y a-t-il d'autres architectures, bibliothèques, techniques ou "choses" qui sont utiles pour atteindre de tels niveaux de performances?
la source
Réponses:
Il existe toutes sortes de techniques pour le traitement des transactions hautes performances et celle de l'article de Fowler n'est qu'une des nombreuses à la pointe. Plutôt que d'énumérer un tas de techniques qui peuvent ou non s'appliquer à la situation de quiconque, je pense qu'il vaut mieux discuter des principes de base et de la façon dont LMAX aborde un grand nombre d'entre eux.
Pour un système de traitement des transactions à grande échelle, vous souhaitez effectuer toutes les opérations suivantes autant que possible:
Minimisez le temps passé dans les niveaux de stockage les plus lents. Du plus rapide au plus lent sur un serveur moderne, vous avez: CPU / L1 -> L2 -> L3 -> RAM -> Disque / LAN -> WAN. Le passage du disque magnétique moderne même le plus rapide à la RAM la plus lente est supérieur à 1000x pour un accès séquentiel ; l'accès aléatoire est encore pire.
Minimisez ou éliminez le temps passé à attendre . Cela signifie partager le moins d'état possible et, si l'état doit être partagé, éviter autant que possible les verrous explicites.
Répartissez la charge de travail. Les processeurs ne sont pas devenus beaucoup plus rapides au cours des dernières années, mais ils sont devenus plus petits et 8 cœurs sont assez courants sur un serveur. Au-delà de cela, vous pouvez même répartir le travail sur plusieurs machines, ce qui est l'approche de Google; la grande chose à ce sujet est qu'il met à l'échelle tout, y compris les E / S.
Selon Fowler, LMAX adopte l'approche suivante pour chacun d'eux:
Gardez tous les états en mémoire à tout moment. La plupart des moteurs de base de données le feront de toute façon, si la base de données entière peut tenir en mémoire, mais ils ne veulent rien laisser au hasard, ce qui est compréhensible sur une plateforme de trading en temps réel. Afin de réussir cela sans ajouter une tonne de risques, ils ont dû créer un tas d'infrastructures de sauvegarde et de basculement légères.
Utilisez une file d'attente sans verrou ("perturbateur") pour le flux d'événements d'entrée. Contrairement aux files d'attente de messages traditionnelles traditionnelles qui ne sont définitivement pas verrouillées, et impliquent en fait généralement des transactions distribuées extrêmement lentes .
Pas tant. LMAX jette celui-ci sous le bus sur la base que les charges de travail sont interdépendantes; le résultat de l'un modifie les paramètres des autres. Il s'agit d'une mise en garde critique , et que Fowler appelle explicitement. Ils font une utilisation afin de la concurrence pour fournir des capacités de basculement, mais toute la logique métier est traité sur un seul thread .
LMAX n'est pas la seule approche de l'OLTP à grande échelle. Et bien qu'il soit assez brillant en soi, vous n'avez pas besoin d'utiliser des techniques de pointe pour atteindre ce niveau de performance.
De tous les principes ci-dessus, le n ° 3 est probablement le plus important et le plus efficace, car, franchement, le matériel est bon marché. Si vous pouvez correctement partitionner la charge de travail sur une demi-douzaine de cœurs et plusieurs dizaines de machines, alors le ciel est la limite pour les techniques conventionnelles de calcul parallèle . Vous seriez surpris du débit que vous pouvez retirer avec rien d'autre qu'un tas de files d'attente de messages et un distributeur à tour de rôle. Ce n'est évidemment pas aussi efficace que LMAX - en fait même pas proche - mais le débit, la latence et la rentabilité sont des préoccupations distinctes, et ici nous parlons spécifiquement de débit.
Si vous avez le même type de besoins spéciaux que LMAX - en particulier, un état partagé qui correspond à une réalité commerciale par opposition à un choix de conception hâtif - alors je suggère d'essayer leur composant, car je n'ai pas vu beaucoup sinon cela est adapté à ces exigences. Mais si nous parlons simplement de haute évolutivité, je vous exhorte à faire plus de recherches sur les systèmes distribués, car ils sont l'approche canonique utilisée par la plupart des organisations aujourd'hui (Hadoop et projets connexes, ESB et architectures connexes, CQRS, que Fowler a également mentions, etc.).
Les SSD vont également changer la donne; sans doute, ils le sont déjà. Vous pouvez maintenant avoir un stockage permanent avec des temps d'accès similaires à la RAM, et bien que les SSD de qualité serveur soient toujours horriblement chers, ils finiront par baisser de prix une fois que les taux d'adoption augmenteront. Il a fait l'objet de recherches approfondies et les résultats sont assez époustouflants et ne feront que s'améliorer avec le temps, de sorte que l'ensemble du concept de "garder tout en mémoire" est beaucoup moins important qu'auparavant. Donc, une fois de plus, j'essaierais de me concentrer sur la concurrence dans la mesure du possible.
la source
Je pense que la plus grande leçon à tirer de cela est que vous devez commencer par les bases:
Pendant les tests de performances, vous profilez votre code, trouvez les goulots d'étranglement et corrigez-les un par un.
Trop de gens sautent directement à la partie «réparez-les un par un». Ils passent beaucoup de temps à écrire "des implémentations personnalisées des collections java", car ils savent juste que la raison pour laquelle leur système est lent est à cause des échecs de cache. Cela peut être un facteur contributif, mais si vous passez directement au réglage de code de bas niveau comme ça, vous risquez de manquer le plus gros problème de l'utilisation d'une ArrayList lorsque vous devriez utiliser une LinkedList, ou que la vraie raison pour laquelle votre système est lent parce que votre ORM charge paresseusement les enfants d'une entité et effectue ainsi 400 voyages distincts dans la base de données pour chaque demande.
la source
Je ne commenterai pas particulièrement le code LMAX car je pense qu'il est amplement décrit, mais voici quelques exemples de choses que j'ai faites qui ont entraîné des améliorations de performances mesurables significatives.
Comme toujours, ce sont des techniques qui doivent être appliquées une fois que vous savez que vous avez un problème et que vous devez améliorer les performances - sinon vous risquez juste de faire une optimisation prématurée.
Aidez le compilateur JIT avec les champs, méthodes et classes de finalisation final permet des optimisations spécifiques qui aident vraiment le compilateur JIT. Exemples spécifiques:
Remplacez les classes de collection par des tableaux - cela se traduit par du code moins lisible et est plus difficile à maintenir, mais est presque toujours plus rapide car il supprime une couche d'indirection et bénéficie de nombreuses optimisations intéressantes pour l'accès aux tableaux. Habituellement, une bonne idée dans les boucles internes / le code sensible aux performances après l'avoir identifié comme un goulot d'étranglement, mais évitez le contraire pour des raisons de lisibilité!
Utilisez des primitives dans la mesure du possible - les primitives sont fondamentalement plus rapides que leurs équivalents basés sur des objets. En particulier, la boxe ajoute une énorme quantité de frais généraux et peut provoquer des pauses GC désagréables. Ne laissez pas de primitives encadrées si vous vous souciez des performances / latence.
Minimisez le verrouillage de bas niveau - les verrous sont très chers à bas niveau. Trouvez des moyens d'éviter de verrouiller complètement ou de verrouiller à un niveau grossier afin que vous n'ayez besoin de verrouiller que rarement sur de gros blocs de données et que le code de bas niveau puisse continuer sans avoir à vous soucier des problèmes de verrouillage ou de concurrence.
la source
final
certains JIT, cela pourrait le comprendre, d'autres non. Cela dépend de l'implémentation (tout comme de nombreux conseils d'optimisation des performances). D'accord sur les allocations - vous devez comparer cela. Habituellement, j'ai trouvé qu'il était préférable d'éliminer les allocations, mais YMMV.Autre que déjà indiqué dans une excellente réponse d'Aaronaught, je voudrais noter qu'un code comme celui-ci pourrait être assez difficile à développer, à comprendre et à déboguer. "Bien que très efficace ... il est très facile de foirer ..." comme l'a mentionné l'un de leurs gars sur le blog LMAX .
Étant donné ci-dessus, je pense que ceux qui choisissent Disruptor et des approches similaires s'assurent mieux de disposer de ressources de développement suffisantes pour maintenir leur solution.
Dans l'ensemble, l'approche Disruptor me semble assez prometteuse. Même si votre entreprise ne peut pas se permettre de l'utiliser, par exemple pour les raisons mentionnées ci-dessus, pensez à convaincre votre direction "d'investir" un peu d'efforts pour l'étudier (et SEDA en général) - parce que si ce n'est pas le cas, il y a une chance qu'un jour leurs clients les laisseront en faveur d'une solution plus compétitive nécessitant 4x, 8x etc moins de serveurs.
la source