[JAVA] Problèmes de performances lors des opérations de sérialisation liés à l'utilisation de LocalDateTime

Cet article décrit les problèmes de performances liés à l'utilisation des formats d'heure LocalDateTime et Instant que les ingénieurs d'Alibaba ont rencontrés au cours du processus de sérialisation.

De Lv Renqi

Les problèmes de performance

Lors de l'exécution d'un test de pression de performance avec une nouvelle version de Apache Dubbo, l'attribut de la classe Transfer Object (TO) J'ai trouvé un problème connexe. Le changement de «Date» en «LocalDateTime» a réduit le débit de 50 000 à 20 000 et augmenté le temps de réponse de 9 ms à 90 ms.

Parmi ces changements, celui qui nous préoccupait le plus était le changement du temps de réponse. Le temps de réponse est à bien des égards la pierre angulaire de bons chiffres de performance, car les indicateurs de performance ne sont significatifs qu'une fois qu'un certain niveau de temps de réponse est garanti. Pour les tests de résistance, les nombres de Gigabit par seconde (GPS) et de transaction par seconde (TPS) ne sont autorisés que si les nombres de temps de réponse cible sont atteints. Les nombres théoriques purs n'ont pas de sens. Dans le cloud computing, chaque bit de temps de réponse est important. Une augmentation de 0,1 ms du temps de réponse du service sous-jacent signifie une augmentation de 10% du coût global.

La latence est comme le tendon d'Achille d'un système avec des utilisateurs distants. Le délai des paquets de données augmente de 1 milliseconde tous les 100 km. Le temps d'attente entre Hangzhou et Shanghai est d'environ 5 millisecondes, et le temps d'attente entre Shanghai et Shenzhen est naturellement encore plus élevé en raison de la distance considérablement plus grande. Le résultat direct de la latence est une augmentation du temps de réponse, ce qui aggrave l'expérience utilisateur globale et augmente les coûts.

Si la demande modifie les enregistrements de la même ligne dans des unités différentes, le coût est très élevé, même s'il peut être cohérent et cohérent. Remote Fast Service Framework, Un cadre de service RPC distribué largement utilisé dans Alibaba. Si un service en appelle un autre sur une demande qui nécessite l'accès au service 0.18e27d1f7aNxOS) (HSF) ou à une autre base de données distante plus de 10 fois, la latence sera ajoutée immédiatement, avec un effet boule de neige.

Importance de l'universalité en Java

La gestion du temps est omniprésente dans le monde de l'informatique. Sans la notion rigoureuse du temps, 99,99% des candidatures seraient dénuées de sens et irréalisables. Cela est particulièrement vrai du traitement personnalisé axé sur le temps que l'on trouve dans la plupart des systèmes de surveillance sur le cloud de nos jours.

Kit de développement Java 8 (JDK 8) Auparavant, java.util.Date était utilisé pour décrire la date et l'heure, et java.util.Calendar était utilisé pour le calcul temporel. JDK 8 introduit des classes de temps plus pratiques telles que ʻInstant, LocalDateTime, ʻOffsetDateTime et ZonedDateTime. En général, ces classes ont rendu le traitement du temps plus pratique.

ʻInstantstocke les horodatages au format UTC (Accord World Time) et fournit un affichage de l'heure face à la machine ou interne. Il convient aux scénarios de stockage de base de données, de logique métier, d'échange de données et de sérialisation.LocalDateTime, ʻOffsetDateTime et ZonedDateTime contiennent des informations de fuseau horaire ou saisonnières et fournissent également un affichage de l'heure aux utilisateurs pour entrer et sortir des données. Si la même heure est envoyée à différents utilisateurs, les valeurs seront différentes. Par exemple, le délai de livraison d'une commande est affiché aux acheteurs et aux vendeurs à différentes heures locales. Vous pouvez considérer ces trois classes comme des outils orientés vers l'extérieur plutôt que vers les parties fonctionnelles internes de l'application.

En bref, ʻInstant est bon pour les services back-end et les bases de données, tandis que LocalDateTime` et sa cohorte sont bons pour les services front-end et les affichages. Les deux sont théoriquement compatibles, mais ils remplissent en fait des fonctions différentes. L'équipe commerciale internationale possède une riche expérience et des idées à cet égard.

«Date» et «Instant» sont souvent utilisés pour intégrer Dubbo au cadre interne de services à grande vitesse (HSF) d'Alibaba.

Reproduction des problèmes de performances

Vous pouvez essayer de le reproduire afin d'obtenir une image précise de ce qui se cache derrière les problèmes de performances que vous avez vus précédemment. Mais avant cela, considérons les avantages de performance de ʻInstant` à travers une brève démo. Pour ce faire, considérez le scénario général de définir une date au format «Date» puis d'utiliser le format «Instant».

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public String date_format() {
        Date date = new Date();
        return new SimpleDateFormat("yyyyMMddhhmmss").format(date);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public String instant_format() {
        return Instant.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern(
                "yyyyMMddhhmmss"));
    }

Après cela, exécutez le test de résistance pendant 30 secondes sur quatre threads simultanés locaux. Le résultat est le suivant.

Benchmark                            Mode  Cnt        Score   Error  Units
DateBenchmark.date_format           thrpt       4101298.589          ops/s
DateBenchmark.instant_format        thrpt       6816922.578          ops/s

A partir de ces résultats, nous pouvons conclure que «Instant» est avantageux en termes de performances de format. En fait, Instant présente également un avantage en termes de performances pour d'autres opérations. Par exemple, Instant s'est avéré présenter des performances prometteuses dans le calcul de l'addition et de la soustraction de date et d'heure.

Pièges instantanés lors des opérations de sérialisation

Ensuite, comme une réplication du problème que nous avons vu ci-dessus, Java et Hessian (optimisé pour Taobao) Nous avons également effectué des tests de résistance pour voir les changements de performances lors des opérations de sérialisation et de désérialisation, respectivement.

Hessian est HSF 2.2 et le schéma de sérialisation par défaut de Dubbo:

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public Date date_Hessian() throws Exception {
        Date date = new Date();
        byte[] bytes = dateSerializer.serialize(date);
        return dateSerializer.deserialize(bytes);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public Instant instant_Hessian() throws Exception {
        Instant instant = Instant.now();
        byte[] bytes = instantSerializer.serialize(instant);
        return instantSerializer.deserialize(bytes);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public LocalDateTime localDate_Hessian() throws Exception {
        LocalDateTime date = LocalDateTime.now();
        byte[] bytes = localDateTimeSerializer.serialize(date);
        return localDateTimeSerializer.deserialize(bytes);
    }

Le résultat était le suivant. En utilisant le protocole Hessian, le débit a fortement chuté lors de l'utilisation des formats ʻInstant` et LocalDateTime. En réalité, le débit est 100 fois inférieur à celui du format Date. Après une enquête plus approfondie, nous avons constaté que le flux d'octets sérialisés Date est de 6 octets, tandis que le flux LocalDateTime est de 256 octets. Cela augmente également le coût de la bande passante du réseau pour la transmission. La solution de sérialisation intégrée de Java montre une légère baisse, mais cela ne fait pas de différence substantielle.

Benchmark                         Mode  Cnt        Score   Error  Units
DateBenchmark.date_Hessian       thrpt       2084363.861          ops/s
DateBenchmark.localDate_Hessian  thrpt         17827.662          ops/s
DateBenchmark.instant_Hessian    thrpt         22492.539          ops/s
DateBenchmark.instant_Java       thrpt       1484884.452          ops/s
DateBenchmark.date_Java          thrpt       1500580.192          ops/s
DateBenchmark.localDate_Java     thrpt       1389041.578          ops/s

Analyse des problèmes

Notre analyse est la suivante. La date est l'un des huit types primitifs de sérialisation d'objets de Hesse.

image.png

Deuxièmement, Instant a dû passer par Class.forName pour la sérialisation et la désérialisation, ce qui a entraîné une forte baisse du débit et du temps de réponse. Par conséquent, Date a un avantage.

image.png

Dernière impression

J'ai trouvé que vous pouvez mettre à niveau et optimiser Hessian en implémentant com.alibaba.com.caucho.hessian.io.Serializer dans une classe telle que Instant via une extension et en l'enregistrant dans SerializerFactory. Vous pouvez résoudre le problème que vous avez résolu. Cependant, il y aura des problèmes de compatibilité avec les versions antérieures et futures. Ceci est un sérieux problème. Les dépendances assez complexes d'Alibaba rendent cela impossible. Compte tenu de ce problème, la seule recommandation que nous pouvons faire est d'utiliser Date comme attribut d'heure préféré pour la classe TO.

Techniquement, le protocole RPC de HSF est un protocole de couche session, et la reconnaissance de version est également effectuée ici. Cependant, la couche de présentation des données de service est implémentée dans un cadre de sérialisation auto-descriptif comme Hessian et manque de reconnaissance de version. Par conséquent, il est très difficile de mettre à niveau.

Recommended Posts

Problèmes de performances lors des opérations de sérialisation liés à l'utilisation de LocalDateTime
Définir l'heure de LocalDateTime à une heure spécifique
Sortie de la façon d'utiliser la méthode slice
Java: utilisez Stream pour trier le contenu d'une collection
Comment utiliser la méthode link_to
Comment utiliser la méthode include?
Comment utiliser la méthode form_with
[Édition Java] Histoire de la sérialisation
Comment utiliser la classe wrapper
Comment utiliser setDefaultCloseOperation () de JFrame
J'ai essayé d'utiliser pleinement le cœur du processeur avec Ruby
Je veux que vous utilisiez Enum # name () pour la clé de SharedPreference