Informations sèches | Ctrip 10 moyens efficaces pour réduire le délai d'attente des clients

A propos de l'auteur

Wen est ingénieur senior en développement back-end chez Ctrip, se concentrant sur les performances du système, la stabilité, les systèmes commerciaux et d'autres domaines.

1. Origines

À l'ère de l'information d'aujourd'hui, la technologie des microservices est devenue une solution importante. La technologie des microservices peut rendre l'échelle et le fonctionnement du système plus flexibles, permettant ainsi une évolutivité et une disponibilité plus élevées. Cependant, le problème de délai d’attente qui se produit lors des appels de microservices est également devenu un danger caché majeur pour la disponibilité du système. Les délais d'attente peuvent entraîner une dégradation des performances du client et même l'empêcher de fonctionner correctement. Cet article vise le problème du timeout et propose des méthodes d'optimisation pertinentes pour réduire le risque d'expiration des appels de microservices.

1.1 Malentendus

Lorsque nous rencontrons des problèmes de délai d'attente ou de lenteur d'exécution, nous pensons souvent qu'il y a un problème avec la partie utilisatrice.

Par exemple : l'accès aux interfaces Redis, DB, RPC devient lent ou expire. Trouvez la partie de confiance pour résoudre le problème dès que possible. Les commentaires de l'autre partie sont qu'il n'y a aucun problème de mon côté (serveur). S'il vous plaît vérifiez si votre côté (client) a un problème.

En fait, la dégradation des performances est un problème très complexe qui peut impliquer plusieurs aspects, notamment le serveur et le client. Par exemple, des problèmes tels que la qualité du code, les ressources matérielles et les conditions du réseau entraîneront une dégradation des performances, entraînant une réponse lente, un délai d'attente et d'autres problèmes. Par conséquent, nous devons analyser de manière approfondie le problème et découvrir les différents facteurs qui affectent les performances.

1.2 Objectif du partage

Cet article présentera en détail les problèmes tels que la lenteur d'exécution et les délais d'attente que nous rencontrons dans l'environnement de production, et proposera des méthodes d'optimisation pertinentes pour réduire le risque de ralentissement ou de délai d'attente et améliorer la stabilité du système en optimisant les performances à longue traîne.

2. Classification des temps morts

Il existe généralement deux types de délais d'attente courants :

a. Délai d'attente de connexion (ConnectTimeout) : signifie que le temps nécessaire pour établir une connexion réseau dépasse le temps d'attente défini.

B. Socket timeout (SocketTimeout) : signifie que pendant le processus de transmission des données, le temps pendant lequel le client attend que le serveur réponde dépasse le temps d'attente défini.

Comme le montre la figure ci-dessous, ① est le moment de préoccupation pour le délai d'expiration de la connexion, et ② est le moment de préoccupation pour le délai d'expiration du Socket. Le délai d'attente expliqué dans cet article est le délai d'attente du Socket.

42618c68d1eca20fa73d52e52c88114d.png

Figure 1 Processus de demande du client

3. Analyse et optimisation des problèmes de délai d'attente


3.1 Définir un délai d'attente raisonnable

Définissez un délai d'expiration raisonnable en fonction de la situation réelle pour éviter un délai d'attente de l'interface causé par des paramètres de délai d'attente déraisonnables.

1) Analyse

Vérifiez si le délai d'attente défini par le client est raisonnable. Par exemple, si le P99.9 du serveur est appelé à 100 ms et que le timeout défini par le client est de 100 ms, 0,1% des requêtes expireront.

2) Plan d'optimisation

Lors de la définition du délai d'attente, nous devons prendre en compte de manière globale le délai du réseau, le temps de réponse du service, le GC, etc.

Prenons l'exemple du moteur de requête d'événement de ticket :

  • Interface principale : valeur minimale (P99.9*3, temps d'attente acceptable par l'utilisateur), le noyau affectera l'ordre et tentera de produire des résultats dans la plage acceptable par l'utilisateur.

  • Interface non principale : valeur minimale (P99,9*1,5, temps d'attente acceptable par l'utilisateur), l'interface non principale n'affecte pas les commandes et peu importe si elle n'est pas affichée.

3.2 Limitation de courant

Lorsque le système rencontre un trafic soudain, il utilise la limitation de courant pour contrôler la vitesse d'accès du trafic afin d'éviter les pannes ou les délais d'attente du système.

1) Analyse

Vérifiez s'il y a une augmentation soudaine du nombre de requêtes au point d'expiration. Par exemple, il y a une activité soudaine. À ce stade, l'application n'est pas développée à l'avance. Faire face à l'augmentation soudaine du trafic entraînera une charge de l'application être relativement élevé, ce qui entraîne des problèmes de délai d'attente.

2) Plan d'optimisation

Évaluez le trafic maximum que l'application actuelle peut transporter et configurez la limitation du trafic. La dimension peut être une machine unique + un cluster.

Limitation de courant sur une seule machine : évitez les collisions sur une seule machine face à une augmentation soudaine du trafic.

Limitation actuelle du cluster : fournissez des capacités de service maximales avec des ressources limitées pour garantir la stabilité du système sans panne ni panne.

3.3 Améliorer le taux de réussite du cache

L'amélioration du taux de réussite du cache peut améliorer la vitesse de réponse de l'interface et réduire le temps de réponse de l'interface, réduisant ainsi l'apparition de délais d'attente.

1) Analyse

Analysez le lien appelant, trouvez les zones lentes et optimisez-les pour améliorer la vitesse de réponse du serveur.

Comme le montre la figure ci-dessous, il est évident que le temps d'exécution du serveur dépasse de 200 ms le délai d'attente configuré par le client, ce qui entraîne un délai d'attente.

d801015ecd274b5d9cdbdf77545e92e1.png

Figure 2 Le client appelle le lien de délai d'attente du serveur

Continuez à analyser le lien d'exécution du serveur et constatez qu'il est dû à des échecs de cache.

e5cf48c8686d91c99a978bd2b3216874.png

Figure 3 Lien manquant de cache

2) Plan d'optimisation

Pour les systèmes à haute concurrence, il est courant d’utiliser le cache pour améliorer les performances.

La figure ci-dessous montre l'architecture de cache précédente. Cette architecture de cache présente deux risques.

a. Le cache a expiré de manière fixe, ce qui entraînera la pénétration directe d'un grand nombre de pannes de clé à un certain moment dans la base de données.

b. Le mécanisme d'actualisation actif consiste à supprimer le cache et à écouter les messages du journal binaire de la base de données pour supprimer le cache. Si une grande quantité de données est actualisée, un grand nombre de clés deviendront invalides.

d39619b028cc72e48aab90659dca208b.png

Figure 4 Expiration fixe + mode de chargement paresseux

En réponse aux risques ci-dessus, nous avons optimisé l'architecture du cache, en remplaçant l'expiration fixe par un renouvellement actif du cache et en écoutant activement les messages pour actualiser le cache, comme le montre la figure ci-dessous.

91ea74de060104b521bdc5fc1a33b33a.png

Figure 5 Comparaison de l'architecture avant et après la mise en cache

3) Effet

Le taux de réussite du cache est augmenté à plus de 98 % et les performances de l'interface (RT) sont améliorées de plus de 50 %.

3efcdb516eff4d914f0f4553559db996.png

Figure 6 Performances de traitement augmentées de 50 %

Cette solution d'optimisation de mise en cache a été publiée dans un article rédigé auparavant par notre équipe, "80 000 tickets vendus en 1 minute !" Il y a une introduction détaillée dans " La réflexion technique derrière la saisie de billets" . Les détails spécifiques ne seront pas développés ici. Les étudiants intéressés peuvent la lire par eux-mêmes.

3.4 Optimiser le pool de threads

Réduisez les threads déraisonnables et réduisez les délais d’attente causés par le changement de thread.

1) Analyse

a. Nombre de threads HTTP

Vérifiez d'abord si le nombre de threads HTTP côté serveur a augmenté de manière significative et si le trafic n'a pas augmenté. Assurez-vous que l'augmentation du nombre de threads HTTP n'est pas causée par l'augmentation du trafic. Comme le montrent les deux figures ci-dessous, lorsque le trafic est normal, le nombre de threads HTTP augmente, indiquant que la réponse du serveur est lente, et il peut être confirmé que le délai d'attente est provoqué par le serveur.

c2a3a823bcb11dc9f33a8a54ebedfe48.png

Figure 7 Le trafic du service est stable

626cb971ce33bf2b5a6690c320ffc4a2.png

Figure 8 Le nombre de threads HTTP augmente soudainement

B. Nombre total de threads

Vérifiez à nouveau si le nombre total de threads a augmenté (à l'exclusion du nombre de threads HTTP). Si tel est le cas, cela signifie que le nombre de threads a augmenté en raison de l'utilisation du multi-threading. À ce stade, vous devez vider les threads pour voir quels threads sont le plus utilisés.

2) Solutions

a. Gestion unifiée du pool de threads : paramètres de configuration dynamiques + capacités de surveillance

Un pool de threads unifié est encapsulé via des classes d'outils, fournissant des paramètres de configuration dynamiques et des capacités de surveillance du pool de threads.

  • Effet

Le pool de threads a des capacités de surveillance. La figure suivante montre la surveillance de la valeur minimale (nombre de threads principaux), de la valeur maximale (nombre maximum de threads) et du nombre de threads dans le pool de threads actuel. Vous pouvez vous y référer pour ajuster les paramètres du pool de threads.

b1d5d7cc9a150727eaaaa6db600b307b.png

Figure 9 Surveillance du niveau d’eau de la piscine Thread

b. Asynchrone à synchrone : n'utilisez pas le multi-threading s'il est inférieur à 10 ms

Dans les scénarios à forte concurrence, il y a trop de threads et le temps de planification des threads ne peut pas être garanti. Une tâche nécessite plusieurs tranches de temps CPU et le temps de planification suivant ne peut pas être garanti.

La figure suivante montre le temps d'exécution fastidieux d'un pool de threads. Grâce aux points enfouis, on constate que A s'exécute plus rapidement dans le pool de threads. La ligne moyenne et P95 sont tous deux inférieurs à 10 ms. Il n'est pas nécessaire d'utiliser le thread pool et passage à l’exécution synchrone.

3b7949b4d074e3f7afa484c940bf742a.png

Figure 10 Temps d'exécution avant optimisation

  • Effet

Les performances de l'interface ont été considérablement améliorées, avec la ligne moyenne réduite de 2,7 ms à 1,6 ms et le P99.9 réduit de 23,7 ms à 1,7 ms.

Dans le passé, le multithreading était utilisé et lorsque le volume des requêtes fluctuait, le nombre de threads augmentait davantage, ce qui faisait que le temps de planification des threads n'était pas garanti et que le P99.9 était très élevé.

6dccaaef6fbef6f4acb3e1be58d3fb82.png

Figure 11 Comparaison de la consommation de temps avant et après optimisation

De plus, on voit clairement que le nombre total de threads a été réduit en conséquence.

4a8718c2876f4cf149442a01aa82efc5.png

Figure 12 Le nombre total de threads diminue après un changement asynchrone vers la synchronisation

3.5 Optimiser le GC

Optimisez le GC, réduisez le temps de pause du GC et améliorez les performances de l'interface.

1) Analyse

Vérifiez d'abord s'il y a un Full GC au point d'expiration, puis vérifiez s'il y a des problèmes évidents dans le Yong GC.Comme le montre la figure ci-dessous, vous pouvez voir trois problèmes évidents.

d4816811fb735c7c6c56d014ad58d8d2.png

Figure 13 Temps de Yong GC

Si le délai d'expiration (comme indiqué ci-dessous) peut correspondre au moment du problème GC, il peut être confirmé que le problème est causé par Yong GC.

403c4f438d0a8b2af67744be418d0b74.png

Figure 14 Nombre de délais d'attente lorsque le client appelle le serveur

2) Solutions

A. Réglage des paramètres JVM universels

Vérifiez si les deux valeurs de -Xmx -Xms sont définies de la même manière. Si elles sont différentes, la JVM ajustera dynamiquement la taille du tas en fonction de la situation réelle pendant l'exécution. Cet ajustement entraîne souvent une surcharge de performances, et si le Le tas d'initialisation est petit, le nombre de GC sera relativement faible.

  • Effet

-Xmx3296m -Xms1977m est remplacé par -Xmx3296m -Xms3296m. L'effet est comme indiqué dans la figure ci-dessous. Il y a une diminution significative de la fréquence et du temps.

241643555c1b3cfbd020b38a6ee43908.png

Figure 15 Effet après réglage des paramètres JVM universels

B. Réglage des paramètres du garbage collector G1

Contexte : Si les valeurs maximales et minimales de la nouvelle génération ne sont pas définies ou si une seule des valeurs maximales et minimales est définie, alors G1 occupera tout l'espace du tas selon les paramètres G1MaxNewSizePercent (la valeur par défaut est 60) et G1NewSizePercent (la valeur par défaut est 5). Le rapport est utilisé pour calculer les valeurs maximales et minimales, et l'équilibrage dynamique est utilisé pour allouer l'espace de nouvelle génération.

Lorsque la JVM vient de démarrer, l'allocation par défaut de l'espace de nouvelle génération est de 5 % du tas total. À mesure que le trafic augmente, la nouvelle génération peut facilement devenir pleine, ce qui entraîne Yong GC. La prochaine fois, plus d'espace de nouvelle génération sera réaffecté jusqu'à ce que les 5 % par défaut soient atteints. Expansion dynamique et valeurs initiales appropriées. Cette configuration est sujette à des Yong GC fréquents lors de la publication de trafic d'accès ou d'un afflux de trafic important.

Pour résoudre ce type de problème, la solution d'optimisation consiste à augmenter G1NewSizePercent et à augmenter la valeur initiale pour rendre le GC plus stable. Cette valeur doit être définie en fonction du scénario métier et se référer à la distribution de taille initiale d'Eden dans le journal GC. Si elle est trop grande, cela peut provoquer des problèmes de Full GC.

En prenant le moteur de requête comme exemple, selon l'analyse des journaux GC, la proportion de la taille de la nouvelle génération dans le tas est relativement stable après 35 %.Les paramètres définis sont :


-XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=35

  • Effet

L'effet après optimisation est comme indiqué ci-dessous. Vous pouvez voir qu'après optimisation, le nombre de GC est réduit de 27 fois/min à 11 fois/min et le temps de GC est réduit de 560 ms à 250 ms.

1024973309c23493b2dd570f62432c28.png

Figure 16 Effet après réglage des paramètres G1

3.6 Changer le thread asynchrone en programmation asynchrone NIO

NIO (IO non bloquant) peut réduire le nombre de threads, améliorer l'utilisation des threads, réduisant ainsi les délais d'attente causés par le changement de thread.

1) Analyse : indicateurs CPU

Analysez les indicateurs liés au processeur. Si l'utilisation du processeur est normale et que la charge du processeur est élevée, vous devez vous concentrer dessus (si l'utilisation du processeur est élevée, cela signifie que le processeur lui-même est très occupé, il est donc normal que le processeur La charge doit être élevée).

Avant l’analyse, introduisons quelques concepts :

a. Tranche de temps CPU

Le CPU divise le temps en plusieurs tranches de temps, et chaque tranche de temps est allouée à un thread. Lorsqu'une tranche de temps est épuisée, le CPU arrêtera l'exécution du thread en cours, effectuera un changement de contexte vers la tâche suivante, et ainsi de suite.

Cela permet d'exécuter plusieurs tâches simultanément en même temps, améliorant ainsi l'efficacité du système et la vitesse de réponse.

La figure ci-dessous simule le processus d'exécution d'un processeur monocœur. Il convient de noter que le changement de contexte nécessite une surcharge, mais le temps réel requis pour un changement de contexte est très court (généralement au niveau de la microseconde).

c35199699b6ed51298a2bfa490f92455.png

Figure 17 Processus du thread d'exécution du processeur

B. Utilisation du processeur

Pour le comprendre à partir de la dimension tranche de temps, on suppose que chaque tranche de temps est exactement utilisée.

247706866ff56f2eda550fed0effc623.png

c. Charge du processeur

D'après l'analyse conceptuelle ci-dessus, si l'utilisation du processeur est normale mais que la charge du processeur est élevée, cela signifie que le processeur a beaucoup de tranches de temps d'inactivité et beaucoup de threads en attente, et que très peu de tranches de temps sont utilisées. , pour réduire la charge CPU, vous devez réduire le nombre de threads en attente.

5bf183581266a31348e5ecda8aaf8f51.png

2 ) Analyse : cas concrets

Nous avons rencontré à plusieurs reprises en production auparavant que la charge du processeur et l'utilisation élevée du processeur étaient normales. Le code n'a pas changé avant et après l'apparition du problème, le changement le plus évident étant l'augmentation du trafic. Après avoir vérifié le code, nous avons constaté que le pool de threads est utilisé pour appeler l'interface simultanément.La méthode d'appel est la suivante.

89cd9b04c6e331192d6f38cc5d6fef97.png

Figure 18 Modèle d'exécution du pool de threads

Cette méthode ne pose aucun problème lorsque le trafic est faible : à mesure que le trafic augmente, le nombre de threads requis doublera.

Par exemple : une requête pour A nécessite d'appeler 3 interfaces de BCD, alors le nombre de threads requis pour 100 simultanéités est de 100 + 3*100=400 (les 100 premiers sont le thread principal correspondant à A, et les 3*100 suivants sont les 100 requis par le fil BCD).

3) Solutions

L'appel simultané du pool de threads est remplacé par un appel asynchrone NIO, comme indiqué dans la figure ci-dessous.

Par rapport à avant, 100 accès simultanés nécessitent 100 threads (les threads de NIO lui-même ne sont pas pris en compte ici, c'est global, et il s'agit d'un nombre de threads relativement fixe et petit).

17bf1a40bd3557f75de42fd9e51a72c9.png

Figure 19 Modèle d'exécution d'appel asynchrone NIO

4) Effet

Le problème de timeout ne s'est plus produit et la charge du processeur a diminué de 50 % en moyenne. Avant, la charge dépassait souvent 2 (le nombre de cœurs du processeur était de 2). Après la transformation, la charge est tombée à environ 0,5.

916fec5b0bfb469b7b4bbfece223823c.png

Figure 20 Effet après optimisation de la charge CPU

3.7 Préchauffage pendant la phase de démarrage

Le préchauffage pendant la phase de démarrage peut établir des liaisons à l'avance, réduisant ainsi l'établissement de liaisons lors de la réception du trafic, réduisant ainsi l'apparition de délais d'attente.

1) Analyse

Une fois l'application extraite, un grand nombre de délais d'attente se produisent et une utilisation élevée du processeur par charge CPU est normale, ce qui indique qu'il y a de nombreux threads en attente. Cela signifie qu'il y a un grand nombre de demandes en attente d'être traitées après l'exécution de l'application. arrivé.

Nous avons déjà rencontré en production que nous attendions que Redis établisse un lien. Le processus de création de liens est synchrone. L'afflux instantané de requêtes juste après l'arrivée de l'application entraînera un grand nombre de requêtes à attendre que Redis termine le processus. établissement de liens.

2) Solutions

Pendant la phase de démarrage, préparez et établissez des liens à l'avance, ou configurez le nombre minimum de connexions inactives pour Redis. D'autres préparations de ressources peuvent également être complétées par un préchauffage pendant la phase de démarrage, comme les liens DB, le chargement du cache local, etc.

3.8 Optimiser le JIT

La compilation JIT (Just-In-Time) peut améliorer l'efficacité d'exécution du programme. Le trafic d'accès en niveaux de gris compile le bytecode en code machine local pour éviter tout impact sur les performances de l'interface.

1) Introduction au JIT

JIT est l'abréviation de Just-In-Time, qui signifie compilation juste à temps. JIT est une technologie qui compile le bytecode en code machine local lorsque le programme est en cours d'exécution, ce qui peut améliorer l'efficacité d'exécution du programme.

En Java, un programme est d'abord compilé en bytecode puis interprété et exécuté par la JVM. Cependant, l'efficacité de l'exécution interprétée est faible car le bytecode doit être interprété une fois pour chaque exécution. Afin d'améliorer l'efficacité d'exécution du programme, la technologie JIT a été introduite dans Java. JIT compilera les blocs de code fréquemment exécutés en code machine local lorsque le programme est en cours d'exécution, puis exécutera le code machine, ce qui peut considérablement améliorer l'efficacité d'exécution du programme.

2) Analyse

La technologie JIT peut optimiser dynamiquement le code en fonction des conditions d'exécution réelles du programme, améliorant ainsi les performances du programme. Cependant, le processus de compilation JIT prend un certain temps, de sorte que certains goulots d'étranglement en termes de performances peuvent survenir lors du premier démarrage du programme.

Comme le montre la figure ci-dessous, le temps JIT est très long après l'extraction de l'application. Il peut être confirmé que JIT provoque le délai d'attente.

591b2cbfb8535f6c67b7cdaa8024a9ea.png

Figure 21 Temps d'exécution JIT

3) Solutions

Une meilleure solution pour optimiser JIT consiste à activer le préchauffage du service (la fonction de préchauffage est prise en charge par le framework Ctrip RPC).

Le principe est qu'une fois pullinée, l'application n'accède pas immédiatement à 100% du trafic, mais augmente progressivement le trafic au fil du temps, pour finalement recevoir 100% du trafic, ce qui permettra de compiler une petite partie du trafic. le code chaud à l'avance.

4) Effet

Après avoir activé le préchauffage du service, comme le montre la figure ci-dessous, le trafic des applications augmente progressivement et vous pouvez voir que le temps de réponse diminue de plus en plus avec le temps, ce qui permet d'obtenir l'effet de préchauffage.

363af38a9be71575468ba15975150c56.png

Figure 22 Volume de requêtes et temps de réponse une fois le service activé

3.9 Changer la machine hôte

Lorsque la charge de l'hôte est trop élevée, vous pouvez envisager de remplacer l'hôte pour éviter qu'une charge excessive de l'hôte n'affecte la charge du conteneur.

1) Analyse

A. Indicateur de limitation du processeur

Jetez un œil à l’indicateur de limitation du processeur de l’application. La limitation du processeur entraînera la mise en veille du processeur et provoquera des pauses dans le service. Si l'utilisation du processeur est normale mais qu'une limitation du processeur se produit, cela est principalement dû à des problèmes d'hôte.

923f32a05c2e16e11be7db8309762598.png

Figure 23 Situation de limitation du processeur

B. Indicateurs d'hôte

Vérifiez si l'utilisation du processeur de l'hôte, la charge du processeur, le disque, les E/S et d'autres indicateurs sont normaux. Comme le montre la figure ci-dessous, si la charge du processeur est supérieure à 1 après un certain temps, cela signifie que la charge de l'hôte est lourde.

5d6019b0ae636fac50ad63e154618562.png

Figure 24 Surveillance de la charge du processeur hôte

2) Solutions

Redémarrez la machine et changez d'hôte.

3.10 Optimiser le réseau

Dépannez les lignes réseau instables pour assurer la stabilité du réseau.

1) Analyse

L'objectif du réseau est d'examiner l'indicateur TCPLostRetransmit (paquets de retransmission perdus). Par exemple, dans l'image ci-dessous, si l'indicateur à un certain point est anormal, mais que d'autres indicateurs à ce stade sont normaux, on peut initialement soupçonner qu'il s'agit d'un problème de réseau. La confirmation finale doit être confirmée par le équipe liée au réseau.

d21e8b72e132f6e10a5e56b97313a052.png

Figure 25 Indicateur TCPLostRetransmit

2) Solutions

Trouvez l’équipe liée au réseau pour dépanner et optimiser.

4. Résumé

En examinant le texte intégral, il explique principalement comment analyser, localiser et optimiser les problèmes de délai d'attente, et résume 10 méthodes d'optimisation courantes, du plus simple au plus complexe. Ces méthodes ne résolvent pas nécessairement le problème de délai d'attente dans d'autres scénarios commerciaux différents. Elles doivent être vérifiées en fonction de votre scénario commercial réel.

Les méthodes résumées dans cet article sont toutes des situations réelles que nous avons rencontrées en production et résumées par une pratique continue. J'espère que ces contenus pourront apporter certains gains aux étudiants qui liront cet article.

4.1 Considérations d'optimisation

  • Les paramètres de délai d'attente et le réglage du GC doivent être optimisés en fonction de vos propres scénarios commerciaux.

  • Le coût et la complexité de la transformation de la programmation asynchrone de NIO sont élevés. Nous explorons également des moyens plus simples, tels que les threads virtuels introduits dans JDK19 (similaires aux coroutines Go), qui peuvent utiliser la programmation synchrone pour obtenir des effets asynchrones.

[Lecture recommandée]

9067a93688af998133c049db5e3ce9ec.jpeg

 Compte public « Ctrip Technology »

  Partager, communiquer, grandir

Je suppose que tu aimes

Origine blog.csdn.net/ctrip_tech/article/details/130998352
conseillé
Classement