Maison > développement back-end > Problème PHP > Comment utiliser correctement les exceptions dans l'architecture des microservices

Comment utiliser correctement les exceptions dans l'architecture des microservices

醉折花枝作酒筹
Libérer: 2023-03-10 17:12:02
avant
1946 Les gens l'ont consulté

Cet article vous présentera l'utilisation correcte des exceptions dans l'architecture des microservices. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.

Comment utiliser correctement les exceptions dans l'architecture des microservices

L'utilisation correcte des exceptions se classe parmi les trois plus importantes dans l'architecture des microservices.

L'utilisation correcte des exceptions Il se classe parmi les trois premiers en importance dans l'architecture des microservices

Curdboys, ça fait longtemps que je vous souhaite à tous un joyeux Dragon Boat Festival. Je veux parler des exceptions récemment. Ma réflexion semble avoir formé une boucle fermée. J'espère que cette combinaison pourra être utile à votre code d'entreprise.

Ce qui suit ne discutera que des meilleures langues du monde et des langues les plus écologiquement complètes. Pas de commentaires.

Similarités et différences des exceptions

La conception des exceptions de PHP dans PHP7 est cohérente avec l'exception extends Throwable de Java, mais il existe encore quelques différences subtiles dans les raisons historiques et les concepts de conception. . Par exemple, les exceptions en PHP ont des attributs de code, il existe donc plusieurs exceptions regroupées dans la même exception, puis différents codes de logique métier sont écrits en fonction du code dans le bloc catch.

Les exceptions Java n'ont pas de code et ne peuvent pas être conçues comme ceci. Différentes exceptions ne peuvent être utilisées que pour différentes situations. Par conséquent, nous sommes habitués à encapsuler les services dans des classes d’empaquetage lorsqu’ils sont exposés au monde extérieur, au lieu de nous appuyer directement sur une transmission transparente des exceptions.

Gestion uniforme des exceptions

Dans le code Java, la chose la plus critiquée est les nombreuses captures d'essai. Je n'ai aucune objection. Prenez simplement un morceau de code

@Override
public DataResult<List<AdsDTO>> getAds(Integer liveId) {
    
    try {
        List<AdsDTO> adsDTO = new ArrayList<>();
        //...业务逻辑省略
        DataResult.success(adsDTO);
    } catch (Exception e) {
        log.error("getAds has Exception:{}", e.getMessage(), e);
        DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage()); // 将异常信息返回给服务端调用方
    }
    
    return dataResult;
}
Copier après la connexion

Souvent, j'écris simplement un try catch sans réfléchir, qu'il contienne ou non des exceptions non liées à l'exécution. Une meilleure façon consiste à utiliser aop pour intercepter tous les appels de méthodes de service, prendre en charge uniformément les exceptions et les gérer.

@Around("recordLog()")
public Object record(ProceedingJoinPoint joinPoint) throws Throwable {
  //... 请求调用来源记录
  
  Object result;

  try {
    result = joinPoint.proceed(joinPoint.getArgs());
  } catch (Exception e) {
    //... 记录异常日志
    
    DataResult<Object> res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage());
    result = res;
  }

    //... 返回值日志记录
  
  return result;
}
Copier après la connexion

Il y a un petit problème. Si les informations d'exception du service A sont renvoyées directement à l'appelant B, il peut y avoir des risques potentiels. Vous ne pouvez jamais faire confiance à l'appelant, même s'il s'agit d'un pauvre de troisième génération. paysan. Comme il n'est pas certain de la manière dont l'appelant gérera le message d'erreur, il peut être renvoyé directement au frontal en tant que json.

RuntimeException

Les exceptions en Java peuvent être divisées en exceptions d'exécution et exceptions non d'exécution n'ont pas besoin d'être interceptées, et elles ne sont pas non plus requises dans les méthodes. Mark throw Exception. Par exemple, si nous utilisons la classe d'outils Preconditions dans le package guava dans la méthode, l'IllegalArgumentException levée est également une exception d'exécution.

@Override
public DataResult<List<AdsDTO>> getAds(Integer liveId) {
  Preconditions.checkArgument(null != liveId, "liveIds not be null");
  
  List<AdsDTO> adsDTOS = new ArrayList<>();
  //...业务逻辑省略
  return DataResult.success(adsDTOS);
}
Copier après la connexion

Nous pouvons également utiliser cette fonctionnalité pour personnaliser notre propre classe d'exception métier afin d'hériter de RuntimeException

XXServiceRuntimeException extends RuntimeException
Copier après la connexion

Pour les situations qui ne sont pas conformes à la logique métier, lancez directement XXServiceRuntimeException

@Override
public DataResult<List<AdsDTO>> getAds(Integer liveId) {

  if (null == liveId) {
    throw new XXServiceRuntimeException("liveId can&#39;t be null");
  }
  
  List<AdsDTO> adsDTOS = new ArrayList<>();
  //...业务逻辑省略
  return DataResult.success(adsDTOS);
}
Copier après la connexion

puis aop effectue un traitement unifié et effectue les optimisations correspondantes. Pour l'approche approximative précédente, les exceptions autres que XXServiceRuntimeException et IllegalArgumentException doivent être enregistrées en interne et ne plus être exposées au monde extérieur. Cependant, vous devez vous rappeler d'enchaîner les liens distribués via requestId In. DataResult Retournez pour faciliter le dépannage.

@Around("recordLog()")
public Object record(ProceedingJoinPoint joinPoint) throws Throwable {
  //... 请求调用来源记录
  
  Object result;

  try {
    result = joinPoint.proceed(joinPoint.getArgs());
  } catch (Exception e) {
    //... 记录异常日志①
    log.error("{}#{}, exception:{}:", clazzSimpleName, methodName, e.getClass().getSimpleName(), e);
    
    DataResult<Object> res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR);
    if (e instanceof XXServiceRuntimeException || e instanceof IllegalArgumentException) {
       res.setMessage(e.getMessage());
    }
 
    result = res;
  }

  if (result instanceof DataResult) {
      ((DataResult) result).setRequestId(EagleEye.getTraceId()); // DMC 
  }

    //... 返回值日志记录
  
  return result;
}
Copier après la connexion

Surveillance des exceptions

En ce qui concerne la boucle fermée, après avoir utilisé la classe d'exception personnalisée, le seuil de surveillance et d'alarme du journal des exceptions peut être considérablement réduit, et l'alarme Pour être plus précis, prenons comme exemple la surveillance d'Alibaba Cloud SLS

* and ERROR not XXServiceRuntimeException not IllegalArgumentException|SELECT COUNT(*) AS count
Copier après la connexion

Ce qui est surveillé ici, c'est le journal des exceptions d'enregistrement du journal ①

Exceptions en PHP

Les problèmes mentionnés ci-dessus en Java existent également en PHP Si vous n'utilisez pas 3 méthodes pour simuler aop, vous ne pouvez pas penser que PHP est le meilleur langage au monde

//1. call_user_func_array
//2. 反射
//3. 直接 new
try {
  $class = new $className();
  $result = $class->$methodName();
} catch (\Throwable $e) {
    //...略
}
Copier après la connexion

. Semblable à la logique architecturale ci-dessus, plus de duplication de pseudo-code, fondamentalement cohérent. Il est également possible de personnaliser votre propre classe d'exception métier pour hériter de RuntimeException, puis d'effectuer un traitement de sortie externe.

Cependant, il existe un bagage historique dans PHP lors de sa conception initiale, de nombreuses exceptions d'exécution étaient générées sous forme d'erreurs de notification et d'avertissement, mais la sortie d'erreur manquait de pile d'appels, ce qui n'était pas propice au dépannage <🎜. >
function foo(){
  return boo("xxx");
}

function boo($a){
  return explode($a);
}

foo();
Copier après la connexion
Warning: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php on line 8
Copier après la connexion

Vous ne pouvez pas voir les paramètres spécifiques, ni la pile d'appels. Si vous utilisez set_error_handler + ErrorException, ce sera très clair.

set_error_handler(function ($severity, $message, $file, $line) {
    throw new ErrorException($message, 10001, $severity, $file, $line);
});

function foo(){
  return boo("xxx");
}

function boo($a){
  return explode($a);
}

try{
  foo();
}catch(Exception $e){
  echo $e->getTraceAsString();
}
Copier après la connexion

La dernière information imprimée est

Fatal error: Uncaught ErrorException: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php:12
Stack trace:
#0 [internal function]: {closure}(2, &#39;explode() expec...&#39;, &#39;/Users/mengkang...&#39;, 12, Array)
#1 /Users/mengkang/Downloads/ab.php(12): explode(&#39;xxx&#39;)
#2 /Users/mengkang/Downloads/ab.php(8): boo(&#39;xxx&#39;)
#3 /Users/mengkang/Downloads/ab.php(15): foo()
#4 {main}
  thrown in /Users/mengkang/Downloads/ab.php on line 12
Copier après la connexion

Si vous modifiez la fonction ci-dessus

function boo(array $a){
  return implode(",", $a);
}
Copier après la connexion

, elle ne peut pas être capturée, car l'erreur fatale PHP : Uncaught TypeError est levée, PHP7 Avec l'ajout de la

class Error implémente Throwable, il y aura Stack dans le journal des erreurs du système PHP, mais il ne peut pas être connecté en série avec l'ensemble du système métier. Ici, nous devons parler de la conception du journal à laquelle nous nous attendons. pour utiliser un traceId comme Java. Tous les journaux sont concaténés, des journaux Nginx aux journaux de niveau d'information normaux en PHP et ces Uncaught TypeErrors, de sorte que la sortie par défaut est reprise dans le journal des erreurs système et enregistrée à un endroit unifié dans le bloc de code catch. . Ensuite, modifiez-le simplement ici pour

set_error_handler(function ($severity, $message, $file, $line) {
    throw new ErrorException($message, 10001, $severity, $file, $line);
});

function foo(){
  return boo("xxx");
}

function boo(array $a){
  return implode(",", $a);
}

try{
  foo();
}catch(Throwable $e){
  echo $e->getTraceAsString();
}
Copier après la connexion

attraper Throwable pour accepter les erreurs et les exceptions.

Mais set_error_handler ne peut pas gérer certaines erreurs, telles que les erreurs E_PARSE. Vous pouvez utiliser register_shutdown_function pour les gérer.

值得注意的是register_shutdown_function的用意是在脚本正常退出或显示调用exit时,执行注册的函数。
是脚本运行(run-time not parse-time)出错退出时,才能使用。如果在调用register_shutdown_function的同一文件的里面有语法错误,是无法注册的,但是我们项目一般都是分多个文件的,这样就其他文件里有语法错误,也能捕获了
register_shutdown_function(function(){
    $e = error_get_last();
    if ($e){
        throw new \ErrorException($e["message"], 10002, E_ERROR, $e["file"], $e["line"]);
    }
});
Copier après la connexion

如果你想直接使用这些代码(PHP的)直接到项目可能会有很多坑,因为我们习惯了系统中有很多  notice 了,可以将 notice 的错误转成异常之后主动记录,但是不对外抛出异常即可。

推荐学习:php视频教程

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!

Étiquettes associées:
source:segmentfault.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal