Унифицированная обработка исключений на основе Spring Cloud Gateway

задняя часть Spring Cloud

image.png

1. Предпосылки

В распределенной среде развертываний кластеров становится все больше, а связи между сервисами становятся все длиннее и длиннее.Каждый сервис имеет свой код ошибки.Если сервис ненормальный, сложно быстро и четко передать ошибку. проблема с позиционированием кода. И коды ошибок тоже разбросаны по каждому сервису, которым нельзя управлять централизованно, если каждый сервис обрабатывает сам, то связь слишком высокая. На основе этого фона мы сделали усиленный шлюзBetterGateway.

2. Обзор

Ввиду вышеуказанных недостатков в сочетании с текущим распределенным и популярным дизайном мы решили внести определенные изменения в шлюз.Каждый запрос должен проходить через шлюз, чтобы достичь бизнес-кластера.После обработки бизнес-кластером запрос снова возвращается вызывающему абоненту через шлюз. Чтобы реализовать централизованное управление файлами конфигурации, глядя на популярный центр конфигурации, мы используем Nacos в качестве центра конфигурации. Сохраните код ошибки в Nacos и используйте функцию динамического обновления Nacos, чтобы реализовать динамическое изменение кода ошибки без остановки. В то же время мы также можем поддерживать хранение файлов конфигурации в файловой системе и использовать nio для мониторинга изменений файлов для достижения динамического обновления, как показано на рис. 1.1.

image_6.png
图1.1

Чтобы точно и быстро найти исключение по коду ошибки, мы провели следующий анализ:
Чтобы быстро найти исключение по коду ошибки, необходимо указать специальное правило сборки кода ошибки, по этому коду ошибки можно сразу увидеть, какой интерфейс является ненормальным, под какой функцией какого поставщика услуг. С этой целью мы оговариваем формат кода ошибки, как показано на рисунке 1.2.

image_7.png

图1.2

Кроме того, необходимо обеспечить уникальность кода ошибки.Код ошибки здесь - это код ошибки после сборки (пожалуйста, ознакомьтесь с кодом ошибки каждого сервиса и разницей между кодом ошибки после сборки и кодом ошибки после сборки. Код ошибки используется для замены каждого кода ошибки ниже.Подавайте свой собственный код ошибки, если вам нужно использовать код ошибки после сборки, автор напишет его понятно). Коды ошибок каждого поставщика услуг могут перекрываться. Чтобы обеспечить уникальность, у нас есть разные правила для разных сетевых вызовов:

Сетевые вызовы между службами сложны и делятся на две большие категории.

  • 1. Служебный внутренний звонок.
    Название службы + код ошибки.
  • 2. Трехсторонний интерфейс (внешняя сеть) вызов.
    URL запроса + код ошибки

Для внутренних вызовов службы внутренняя служба определяет набор кодов ошибок. Например, 1000 и 1001, каждая внутренняя служба является поставщиком услуг.Например, служба а вызывает интерфейс отправки службы б.Если интерфейс неисправен, возвращается код ошибки 1000, который можно рассматривать как 1000 было возвращено поставщиком услуг b. Тогда этот сервис является сервис-провайдером, а имя сервиса + код ошибки — уникальным идентификатором. Для вызова трехстороннего интерфейса (внешней службы) мы используем путь запроса + код состояния, возвращаемый запросом, в качестве уникального флага. Например Служба b вызывает интерфейс запросов компании xxx. Если интерфейс успешно возвращает 200, в случае сбоя он возвращает 1000 (неверный параметр), 1001 (ошибка аутентификации), тогда компания xxx является поставщиком услуг, а интерфейс запроса + возвращаемый код состояния является уникальным идентификатором.

3. Функция

серийный номер Функция текущая поддержка
1 Преобразование кода ошибки
2 Горячая конфигурация (Nacos)
3 Горячая конфигурация (файловая система)
4 Динамическая аутентификация
5 Вперед ×
6 распределение ×
7 Повторить попытку ×
8 компенсировать ×
9 Динамическое возвращаемое значение ×

4. Дизайн

4.1 Общий дизайн

BetterGateway основан на дизайне Spring Cloud Alibaba и Spring Cloud. Он также поддерживает файлы конфигурации, которые будут храниться в локальной файловой системе. Эта статья основана на примере Spring Cloud Alibaba.

Общий дизайн BetterGateway

Запрос сначала поступает на шлюз, а шлюз перенаправляет его нижестоящей системе, как показано на рисунке выше. Шлюз вызывает систему A, а система A вызывает систему B. Если система B ненормальна, система B собирает код ошибки (в основном имя службы и код ошибки). система Б:

  • Генерирует исключение, обрабатывает это исключение через глобальный класс обработки исключений и возвращает его вышестоящей системе.
  • Решайте сами.После обработки верните этот код ошибки вышестоящей системе.

Система А получает результат, возвращенный интерфейсом, и оценивает, не произошел ли сбой.В случае сбоя у системы А в настоящее время есть два варианта:

  • Генерирует исключение, обрабатывает это исключение через глобальный класс обработки исключений и возвращает его вышестоящей системе.
  • Решайте сами.После обработки верните этот код ошибки вышестоящей системе.

Не изменяйте код ошибки, возвращаемый системой B, в системе A. Главное не менять уникальный идентификатор (название сервиса + код ошибки или путь трехстороннего интерфейса + код) После обработки системой А запрос приходит на шлюз, и шлюз определяет успешность возвращаемого значения. В случае сбоя код ошибки будет собран в соответствии с конфигурационным файлом, и собранный код ошибки будет возвращен вышестоящей системе.

4.2 Описание функциональных компонентов

4.2.1 Файлы конфигурации
  • Общая конфигурация поставщика услуг
    [
      {
        "code":"01",
        "name":"order-service",
        "dataId":"order-service.json",
        "domainName":"order-service",
        "authMethod":"token",
        "authConfig":{
          "ignorePath":[
            "/order/submit",
            "/order/cancel"
          ],
          "ignorePathPrefix": [
           "/common"
          ]
        },
        "type": "inner",
        "version": "5"
      }
    ]
    
    • codeКод поставщика услуг.
    • nameИмя поставщика услуг.
    • dataIdИдентификатор конфигурации поставщика услуг.
    • domainNameДомен поставщика услуг.
    • authMethodМетод аутентификации по умолчанию.
    • authConfigДополнительные методы аутентификации.
      • ignorePathНеаутентифицированный путь (полный путь).
      • ignorePathPrefixНеаутентифицированный путь (префикс).
    • typeКатегория поставщика услуг.
    • versionНомер версии конфигурации, конфигурация кода ошибки поставщика услуг.
[
  {
    "url": "order-service",
    "featCode": "001",
    "errorCodeList": [
      {
        "errorCode": "5106",
        "code": "001",
        "type": "P",
        "tips": "当前用户无此节点操作权限",
        "handleStrategy": "",
        "handleParam": ""
      }
    ]
  },
  {
    "url": "order-service",
    "featCode": "002",
    "errorCodeList": [
      {
        "errorCode": "5012",
        "code": "001",
        "type": "P",
        "tips": "改订单已作废",
        "handleStrategy": "forward",
        "handleParam": "/viewCancelOrder"
      },
      {
        "errorCode": "5013",
        "code": "001",
        "type": "P",
        "tips": "订单正在处理中 ",
        "handleStrategy": "forward",
        "handleParam": "/viewDoingOrder"
      }
    ]
  },
  {
    "url": "/system/institution/dealer/search",
    "featCode": "",
    "errorCodeList": [
      {
        "errorCode": "",
        "code": "",
        "type": "",
        "tips": "",
        "handleStrategy": "distribute",
        "handleParam": "http://10.98.14.80/system/institution/dealer/search"
      }
    ]
  }
]
  • urlАдрес интерфейса (внутренняя служба как имя службы).
  • featCodeФункциональное доменное кодирование.
  • errorCodeListСписок кодов ошибок.
    • errorCodeКод ошибки (внутренний).
    • codeКод ошибки (дисплей).
    • typeТип ошибки.
    • tipsСообщение об ошибке.
    • handleStrategyстратегия обработки.
    • handleParamПараметры политики обработки.

По умолчанию сообщение об ошибке перезаписывает атрибут msg в исходном ответе, который можно расширить с помощью заполнителей. В настоящее время поддерживаются следующие заполнители:

  • #КОД ПРИЛОЖЕНИЯ#. используется вmsgКод ошибки дисплея после преобразования.
  • #глутамат натрия#. используется вmsgОтобразите информацию об исходном ответе в формате .

стратегия распространенияdistributeДля входящих запросов, не связанных с сопоставлением кодов ошибок. Таким образом, при настройке политики распространения URL-адрес является путем входящего запроса, а другие атрибуты, связанные с кодом ошибки, задаются пустыми.

4.2.2 Конфигурация политики обработки

Поддерживаемые стратегии

  • распределять
    • handleStrategyраспространять.
    • handleParamАдрес назначения рассылки. Несколько адресов можно разделить запятыми.
  • переадресация (временно недоступно)
    • handleStrategy forward.
    • handleParamАдрес назначения для переадресации.
  • повторить попытку (временно недоступно)
    • handleStrategy retry.
    • handleParamколичество попыток, интервал между попытками.

4.2 Связанное преобразование

Благодаря приведенному выше введению код ошибки может быстро и точно определить, какая служба не работает. Если услуга ненормальна, код ошибки будет прозрачно передан на шлюз. Он отображается в цепочке вызовов, как показано на рисунке 1.3.

image.png 图1.3

Так как так называемый одиночный провод не образует линию, а отдельное дерево не образует лес, практическое применение BetterGateway требует преобразования системы и взаимодействия, поэтому мы рекомендуем использовать следующие структуры в качестве унифицированных возвращаемых значений между вызовами службы. , настраиваемые исключения (ApiException) и поддержка глобального обработчика исключений (APIExceptionHandler).

4.2.1 Структура

@Data
public class ApiResult<T> implements Serializable {
  private static final long serialVersionUID = 0xc93480e15321b2c5L;

  private static final Logger logger = LoggerFactory.getLogger(ApiResult.class);

  /**
   * 状态码
   */
  private Integer code;

  /**
   * 说明信息
   */
  private String msg;

  /**
   * 错误code
   */
  private String appCode;

  /**
   * 服务名/url
   */
  private String path;

  /**
   * 返回数据
   */
  private T data;

  public ApiResult() {
    this.path = PropertyUtils.getServiceName();
  }

  public ApiResult(ResultCode code) {
    this.code = code.code();
    this.msg = code.message();
    this.appCode = String.valueOf(code.code());
    this.path = PropertyUtils.getServiceName();
  }

  public ApiResult(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
    this.appCode = String.valueOf(code);
    this.path = PropertyUtils.getServiceName();
  }

  public ApiResult(ResultCode resultCode, T data) {
    this.code = resultCode.getCode();
    this.msg = resultCode.getMsg();
    this.appCode = String.valueOf(resultCode.getCode());
    this.path = PropertyUtils.getServiceName();
    this.data = data;
  }

  public static <T> ApiResult<T> failure(Integer code, String path) {
    ApiResult<T> result = failure();
    result.setCode(code);
    result.setAppCode(String.valueOf(code));
    result.setPath(path);
    return result;
  }

  public static <T> ApiResult<T> failure(Integer code, String path, T data) {
    ApiResult<T> result = failure();
    result.setCode(code);
    result.setAppCode(String.valueOf(code));
    result.setPath(path);
    result.setData(data);
    return result;
  }

  public static ApiResult<String> failure(ResultCode code, String data, boolean isCustomErrorMessage) {
    ApiResult<String> result = failure(code, data);
    if (isCustomErrorMessage && Toolkit.isValid(data)) {
      result.setMsg(data);
    }
    return result;
  }

  public static <T> ApiResult<T> failure(APIException e) {
    ApiResult<T> result = failure();
    result.setAppCode(e.getAppCode());
    result.setPath(e.getPath());
    result.setMsg(e.getMsg());
    result.setData((T) e.getData());
    return result;
  }

  public static <T> ApiResult<T> success() {
    return new ApiResult<T>(ResultCode.SUCCESS);
  }

  public static <T> ApiResult<T> success(T data) {
    ApiResult<T> result = new ApiResult<T>(ResultCode.SUCCESS);
    result.setData(data);
    return result;
  }

  public static <T> ApiResult<T> success(T data, String msg) {
    ApiResult<T> result = new ApiResult<T>(ResultCode.SUCCESS.code(), msg);
    result.setData(data);
    return result;
  }

  public static <T> ApiResult<T> failure(ResultCode rc) {
    StackTraceElement se = Thread.currentThread().getStackTrace()[2];
    logger.error("failure result code: {}, msg: {}", rc.getCode(), rc.getMsg());
    logger.error("failure info: {} {} {}", se.getClassName(), se.getMethodName(), se.getLineNumber());
    return new ApiResult<T>(rc.getCode(), rc.getMsg());
  }

  public static <T> ApiResult<T> failure() {
    return failure(ResultCode.FAIL);
  }

  public static <T> ApiResult<T> failure(ResultCode code, T data) {
    ApiResult<T> result = failure(code);
    result.setData(data);
    return result;
  }
}

Вы также можете использовать пользовательский ApiResult по мере необходимости, но он должен иметь следующие свойства:

  • code: Код состояния целочисленного типа, исходная форма, не влияет на исходную функцию.
  • appCode: код состояния типа String, который может хранить более богатую информацию, а также нецелые коды состояния сторонних интерфейсов. Переведенные коды состояния переопределяют это значение.
  • msg: информация подсказки, преобразованная информация подсказки перезапишет это значение.
  • path: Путь исключения. Принято считать, что ошибка стороннего интерфейса — это URL-адрес интерфейса, а вызов внутренней службы — это имя службы. Также могут использоваться другие имена, которые можно настроить в файле конфигурации.

    различатьcodeа такжеappCodeЧтобы не влиять на исходный интерфейс, если код ApiResult изначально является строкой, его можно использовать напрямую без appCode. когдаcodeСопоставление кода состояния выполняется только в случае неудачи.Статус успешного выполнения настраивается в классе перечисления ResultCode в общем пакете. По умолчанию 200.

4.2.2 Пользовательское исключение (ApiException)

@Data
public class APIException extends RuntimeException {

  private String appCode;
  private String path;
  private String msg;
  private Object data;

  public APIException() {
  }

  public APIException(String appCode, String path) {
    this.appCode = appCode;
    this.path = path;
  }

  public APIException(String appCode, String path, Object data) {
    this.appCode = appCode;
    this.path = path;
    this.data = data;
  }

  public APIException(String appCode, String path, String msg, Object data) {
    super(msg);
    this.appCode = appCode;
    this.path = path;
    this.msg = msg;
    this.data = data;
  }

  public APIException(ApiResult apiResult) {
    this.appCode = apiResult.getAppCode();
    this.path = apiResult.getPath();
    this.msg = apiResult.getMsg();
    this.data = apiResult.getData();
  }
}

4.2.3 Глобальная обработка исключений (APIExceptionHandler)

@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class APIExceptionHandler {

  private static final Logger logger = LoggerFactory.getLogger(APIExceptionHandler.class);

  @ExceptionHandler(value = APIException.class)
  public ApiResult<Object> handleBizException(APIException e) {
    logger.error("发生接口调用异常 原因是:", e);
    ApiResult<Object> result = new ApiResult<>(ResultCode.FAIL);
    result.setAppCode(e.getAppCode());
    result.setPath(e.getPath());
    result.setMsg(e.getMsg());
    result.setData(e.getData());
    return result;
  }
}

5. Важные занятия

5.1 Фильтры

  • 【CacheRequestBodyFilter】Кэшировать тело запроса.
  • 【RemoveCachedBodyFilter】 Освободить кешированное тело запроса.
  • 【GlobalExceptionHandler】 Обработка исключений.
    • Для исключений службы, не относящихся к шлюзу, для таких исключений шлюзу необходимо выполнить преобразование кода ошибки.
    • Шлюз ненормальный.Шлюз тоже является java процессом, и может возникнуть само исключение.
  • 【ErrorCodeFilter】 Преобразование кода ошибки.
  • [ErrorCodeSpringFilter] Преобразование кода ошибки, отличное от приведенного выше, этот класс использует метод, предоставляемый spring-cloud-gateway.
  • 【AuthFilter】 Аутентификация, режим стратегии реализует динамическую аутентификацию различных поставщиков услуг.

5.2 Изменения в файлах конфигурации

  • 【ConfigDataChangeContainer】 Храните последний файл конфигурации.
  • [CoreContainerService] Использование класса ConfigDataChangeContainer для сравнения данных и извлечения новых данных.
  • [NacosClient] Клиент nacos извлекает новый файл конфигурации из Nacos.
  • 【FileClient】 Клиент для чтения файлов. Извлеките файл конфигурации из файловой системы. Если вы хотите начать чтение локального файла, dataId — это полный путь.
  • [NacosConfChangeHandler] Класс обработки монитора Nacos, используемый для приема файлов, настроенных в центре конфигурации.
  • [FileConfigChangeHandler] Класс обработки мониторинга файлов, мониторинг изменений файлов и чтение файлов.

5.3 Несколько специальных интерфейсов

  • 【DataFormatConversion】 Этот интерфейс используется для уведомления об изменениях файла конфигурации и отправки последнего файла конфигурации классу, который реализует этот интерфейс.
  • 【CompensationService】 Этот интерфейс используется для обработки мер компенсации после сбоя запроса.
  • 【AuthService】 Интерфейс аутентификации.

6. Тест

В тестовом примере есть четыре роли: интерфейсная система, шлюз, sys-service (системная служба), API-служба (служба интерфейса внешней службы) и трехсторонний интерфейс данных. Рисунок 1.4.
img_7.png

Рисунок 1.4

В следующих примерах показаны исключения между различными службами и трехсторонним интерфейсом, а также коды ошибок после преобразования шлюза для различных ситуаций.

6.1: системная служба работает нормально

http://localhost:81/sys/sysNormal
Внешний интерфейс напрямую запрашивает sys-service, а sys-service не является ненормальным.

img_6.png

6.2: Служба API работает нормально

http://localhost:81/sys/apiNormal
Внешний интерфейс напрямую запрашивает sys-service, sys-service вызывает API-сервис, и API-сервис не является исключением.

img_1.png

6.3 исключение системной службы

http://localhost:81/sys/sysException
Внешний интерфейс напрямую запрашивает sys-service, sys-service неисправен, и шлюз преобразует код ошибки.

img_8.png

6.4 исключение службы API

http://localhost:81/sys/apiException
Фронтенд напрямую запрашивает sys-сервис, sys-сервис вызывает апи-сервис, в апи-сервисе возникает исключение, а шлюз конвертирует код ошибки.

img_3.png

6.5 Исключение внешнего интерфейса вызова API

http://localhost:81/sys/apiCallBaiduException
Внешний интерфейс напрямую запрашивает sys-сервис, sys-сервис вызывает API-сервис, а API-сервис вызывает трехсторонний внешний интерфейс, внешний интерфейс неисправен, и шлюз преобразует код ошибки.

img_4.png

6.6 API неправильно вызывает внешний интерфейс, и код ошибки не настроен.

http://localhost:81/sys/apiCallBaiduException
Фронтенд напрямую запрашивает sys-сервис, sys-сервис вызывает api-сервис, а API-сервис вызывает сторонний внешний интерфейс Внешний интерфейс ненормальный, и преобразование кода ошибки интерфейса не настроено, и отображается самый примитивный код ошибки.

img_5.png


Компания Nanjing Sanbaiyun Information Technology Co., Ltd. (Che 300) была создана 27 марта 2014 г. Это предприятие мобильного Интернета, базирующееся в Нанкине и в настоящее время расположенное в Нанкине и Пекине. После 7 лет накопления совокупное количество оценок достигло 5,2 миллиарда раз, и оно завоевало расположение многих высококачественных инвестиционных институтов в стране и за рубежом, таких как Sequoia Capital и SAIC Industrial Fund.
Sanbaiyun — отличный независимый сторонний поставщик услуг SaaS для автоматических транзакций и финансовых услуг, основанный на искусственном интеллекте, с автоматическим ценообразованием транзакций и стандартизацией автоматического контроля финансовых рисков в качестве основных продуктов.

Присоединяйтесь к Sanbaiyun, станьте свидетелем бурного развития автомобильной промышленности и с нетерпением ждем возможности идти с вами рука об руку!
Официальный сайт компании:www.sanbaiyun.com/
Отправьте свое резюме:hr@che300.com, пожалуйста, укажите от Наггетс 😁