Maison  >  Article  >  Java  >  Introduction à l'exception et au résultat (exemple de code)

Introduction à l'exception et au résultat (exemple de code)

不言
不言avant
2019-03-12 15:52:004032parcourir

Le contenu de cet article est une introduction à Exception et Result (exemples de code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Dans le développement de systèmes distribués, nous devons souvent transmettre divers codes d'état et messages d'erreur à l'appelant le plus externe est généralement l'interface http/api, ainsi que des messages d'erreur tels qu'un échec de connexion, une erreur de paramètre, etc.

Les données exposées par l'interface la plus externe sont généralement dans un format json similaire à {code, msg, data}, et il n'y a aucune contestation à ce sujet.

Cependant, dans les appels RPC entre nœuds et les appels de méthode au sein des nœuds d'un système distribué, les informations d'erreur sont généralement transmises à l'aide de ServiceException ou Result. Quelle est la différence entre ces deux méthodes et laquelle est la meilleure ? Lequel est le pire ? Cet article se concentre sur l’efficacité du développement et les performances du système pour explorer ce problème.

RésultatIntroduction

Il s'agit d'un moyen relativement courant de transmettre des informations d'erreur. Certains grands fabricants les définissent même directement comme spécifications techniques, obligeant tout le monde à l'équipe. adopte cette approche. Les modèles de résultats courants sont les suivants :

@Data
public class Result<T> {
    private int code; // 也可以是String等
    private String msg;
    private T data;
}

L'application dans le développement de systèmes ressemble généralement à ceci :

Result<UserModel> userModelResult = userService.query(userId);
if (!userModelResult.isSuccess() || userModelResult.getData != null) {
    return Result.fail(userModelResult); // 透传错误
}
UserModel userModel = userModelResult.getData();
if (userModel.getStatus() != UserStatusEnum.NORMAL) {
    return Result.fail("user unavaliable"); // 用户不可用
}
// ... 正常使用UserModel

Dans un environnement de microservices distribués plus complexe, des codes similaires sont très courants. , chaque appel à un service dépendant est accompagné d'une logique de tolérance aux pannes similaire.

Ce mode est similaire au traitement des codes d'erreur dans le langage Golang. C'est aussi ce pour quoi Golang est critiqué, c'est-à-dire qu'un jugement d'erreur doit être fait à chaque étape.

La réalité la plus cruelle est que malgré l'encapsulation des résultats, il y aura toujours des exceptions du système back-end transmises de manière transparente. Dans les applications pratiques avec lesquelles j'ai été en contact, ce type de transmission transparente anormale qui traverse l'encapsulation du résultat n'est en aucun cas un cas isolé lorsque le système dont je suis responsable appelle le système commercial national le plus puissant en back-end, J'ai reçu un TC du centre commercial le plus interne. L'activité était anormale et plus de 5 équipes ont été suivies lors du dépannage.

Introduction à ServiceException

Comme son nom l'indique, cette méthode utilise des interruptions d'exception pour diviser la logique normale et la logique d'exception.

Dans le développement de systèmes, la plupart des erreurs nécessitent une interruption directe des services et un retour d'erreur direct à l'utilisateur. Pour cette raison, lorsque nous utilisons Result, nous devons souvent écrire quelque chose comme if(result.isFail(. )){return...} code comme celui-ci. En utilisant ServiceException, nous pouvons omettre la plupart du code similaire.

Habituellement, ServiceException peut être défini comme suit :

@Getter
public class ServiceException extends RuntimeException {
    private final int code;
    private final String msg;
    public ApiException() {
        this(-1, null);
    }
    public ApiException(Code code) {
        this(code, null);
    }
    public ApiException(Code code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }
}

Lorsque les composants internes du système rencontrent des situations anormales telles qu'une perte de données, un accès non autorisé, un échec de connexion, un verrouillage de compte, etc., ils lancez directement la logique d'interruption ServiceException. Ensuite, le filtre ou l'aspect le plus externe intercepte l'exception, extrait le code et le message et les renvoie à l'utilisateur.

La logique de code réelle utilisée est similaire à celle-ci :

UserModel userModel = userService.query(userId); // userID不存在、不可用等隐藏在异常中
// ... 使用userModel

Cette méthode est évidemment élégante et rationalisée, et est utile pour améliorer l'efficacité du développement et la maintenance ultérieure.

Cependant, de nombreuses rumeurs circulent sur le marché selon lesquelles l'utilisation d'interruptions anormales affectera les performances. Certaines personnes ont même conclu, grâce à de simples tests de performances, que l'exécution d'interruptions anormales est des centaines de fois plus rapide que le retour du résultat.

Test de performances

Pour résoudre les problèmes de performances, j'ai également effectué un test simple. Pour le code de test spécifique, voir :

https://. github.com/sisyphsu/b...

JMH est utilisé ici pour les tests de performances, j'envie vraiment la bibliothèque de tests fournie avec le langage golang.

La logique métier à l'intérieur du test est très simple, il suffit d'appeler System.currentTimeMillis() une fois et de renvoyer un horodatage long.

Dans le test de performances, la valeur de retour Result et l'exception de lancement sont utilisées respectivement. Pour le test de performances de lancement d'exceptions, des tests de pile d'appels de différentes profondeurs sont ajoutés. Exception, La pile du Thread actuel doit être analysée, et plus la pile d'appels est profonde, plus la perte de performances causée est importante. Les valeurs spécifiques de profondeur de pile sont 1, 10 et 100 :

Test.test                  avgt    5  0.027 ± 0.001  us/op
Test.testException         avgt    5  1.060 ± 0.045  us/op
Test.testDeep10Exception   avgt    5  1.826 ± 0.122  us/op
Test.testDeep100Exception  avgt    5  9.802 ± 0.411  us/op

À première vue, la perte de performances d'une profondeur de pile d'exceptions de 100 est en effet 360 fois supérieure à celle des appels de méthode ordinaires, et certains les gens sont en effet basés sur cela. Cette raison conduit à la conclusion que les interruptions d'exception Java entraînent de graves pertes de performances.

Analyser l'impact des performances

Mais il faut faire attention à l'unité de temps, qui n'est que des microsecondes, un millième de milliseconde et un millionième de seconde.

En supposant que le débit du processeur unique d'un microservice est de 1 000 QPS et que 10 % d'entre elles sont des requêtes illégales, la perte de performances due à une interruption anormale n'est que d'un dix millième et l'impact sur le temps de service n'est que de 0,001. millisecondes.

Dans le test de performances, le temps d'affaires sert uniquement à obtenir le temps système, ce qui prend environ 25 ns. Pour cette raison, la perte de performances causée par des interruptions anormales atteint des « centaines de fois » terrifiantes. Mais que se passe-t-il si le temps de travail passe de 25 ns à 25 us ou 25 ms ?

Parlons à nouveau des goulots d'étranglement des performances

Lorsque nous analysons les performances du système, nous devons comprendre son ordre de grandeur et les goulots d'étranglement des performances, et n'oubliez pas de tomber dans le dilemme de l'optimisation des performances.

Pour donner un exemple approximatif, dans les services réguliers, les opérations de base de données utilisant des index prennent entre 1 et 10 millisecondes, l'accès au cache distribué prend entre 3 et 30 millisecondes et la perte de performances réseau du microservice RPC est comprise entre 3 et 10 millisecondes, le réseau entre le client et le serveur prend entre 5 et 300 millisecondes, et ainsi de suite. Dans ce cas, optimiser pour un risque de performance de 0,001 milliseconde revient à ramasser des graines de sésame mais à perdre une pastèque.

J'ai écrit un jour un protocole réseau sous-jacent similaire à TCP. Dans ce scénario haute fréquence, l'optimisation des algorithmes apporte une optimisation des performances de 0,1 microseconde, ce qui signifie que le débit par seconde est amélioré de plusieurs fois, voire de plusieurs fois. mais dans le scénario basse fréquence des appels distribués, cet avantage en termes de performances n'est d'aucune utilité.

Un autre exemple, lorsque mes collègues et moi discutions de la conception des tables de données de base de données il y a quelques années, nous avons eu une dispute acharnée sur la longueur de int à utiliser pour le statut de la commande. il, 1 optimisé pour l'état des commandes Octets, il n'économise que moins de 1 Mo d'espace disque au fil des années. A quoi ça sert ?

Interruptions anormales dans RPC

Pour les frameworks d'appels à distance tels que Dubbo et HSF, lors de l'utilisation d'interruptions anormales pour transférer des informations d'erreur, une chose à noter est que le type d'exception doit être conçu pour être universal , c’est-à-dire le type de base référencé par chaque microservice.

Il est mentionné dans les spécifications techniques d'une certaine usine :

1) En utilisant la méthode de retour d'exception, si l'appelant ne l'attrape pas, une erreur d'exécution se produira.

2) Si vous n'ajoutez pas d'informations sur la pile, juste une nouvelle exception personnalisée et ajoutez votre propre message d'erreur, cela ne sera pas d'une grande aide à l'appelant pour résoudre le problème. Si des informations sur la pile sont ajoutées, la perte de performances de sérialisation et de transmission des données constitue également un problème en cas d'erreurs d'appel fréquentes.

Je suis assez insatisfait de cette spécification technique.

Tout d'abord, les exceptions métier doivent être transmises de manière transparente à la couche la plus externe par l'appelant. Les exceptions telles que l'inexistence des données, l'échec de connexion et le verrouillage de l'utilisateur sont souvent inutiles si elles sont détectées par l'appelant. le milieu.

La deuxième chose est la perte de performances. Quel type de perte de performances cette sérialisation de données basse fréquence et cette transmission intranet entraîneront-elles ? La transmission des informations de pile à l'appelant est également utile pour le dépannage. J'ai reçu une fois les informations de pile d'exception de TC. Selon le package dans la pile, j'ai directement contourné les troisième et quatrième couches pour trouver l'erreur de niveau inférieur. gagné beaucoup de temps.

Conclusion

Dans les microservices distribués, l'utilisation d'interruptions d'exception peut grandement simplifier le code métier et avoir un impact minimal sur les performances.

Avec l'aide de @NotNull, @Nullable et d'autres annotations, le développement distribué peut être aussi rapide et pratique que le vent. Dans les réseaux de services complexes, les exceptions métier peuvent également aider les développeurs à localiser avec précision les erreurs et à éviter la situation embarrassante de traçage des points de défaillance couche par couche tout au long de la chaîne d'appels.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer