Некоторые мысли об обработке ошибок микросервиса

Go

предисловие

Обработка ошибок внутри и между системами проходит через весь жизненный цикл разработки, эксплуатации и прекращения существования системы и является моментом, требующим особого внимания в процессе написания кода. Об ошибке сообщается в одном месте, следует ли вернуться напрямую или распечатать строку журнала и вернуться? Как найти первопричину ошибки вложенной функции? Должен ли код ошибки в интерфейсе http или rpc определяться в каждой структуре ответа или возвращаться единообразно через код http и ошибку rpc? В этой статье будут объяснены определение, методы обработки и связанные с ними причины ошибок с двух сторон: внутрисистемной и межсистемной. Поскольку я обычно использую go для разработки, обработка ошибок в системе больше с точки зрения go.

Обработка ошибок в системе

                                         "Go Proverbs”

В "Go Bible" есть два аспекта обработки ошибок: 1. ошибки - это просто переменные 2. Не просто проверяйте, а плавно обрабатывайте ошибки. Эти две статьи на самом деле не только обобщают несколько способов, которыми мы обычно справляемся с ошибками, но и даем лучший способ борьбы с ошибками (если это можно сделать...).

определить переменную ошибки

Когда я впервые столкнулся с ошибками golang, я действительно почувствовал, что этот метод обработки ошибок довольно хорош, есть переменная, которая позволяет мне уточнить, какую ошибку я сделал, насколько она понятна и пряма, и есть много подобных примеров. в стандартной библиотеке и сторонней библиотеке io.EOF, sql.ErrNoRows и т.д. Однако у такого использования есть недостатки.

Недостаток 1: нельзя обернуть сообщения об ошибках

Первоначально функция возвращает io.EOF, но в бизнес-системе часто передается fmt.Errorf("xx file: %v", err), что напрямую приводит к сбою оценки на самом внешнем уровне.

Недостаток 2: переменная ошибок становится общедоступным API

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

Поэтому постарайтесь использовать как можно меньше переменных без ошибок.

Текстовое сообщение с ошибками

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

Утвердить с ошибками

Утверждение ошибок определяет только структуру, которая реализует интерфейс ошибок, например:

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

Распространение ошибок «черным ящиком», обработка ошибок с поведением утверждений (и обработка ошибок только один раз)

Один из любимых методов Дейва называется: Непрозрачные ошибки. Идея непрозрачных ошибок заключается в том, что как вызывающий функцию, метод и т. д. вы можете только знать, в порядке ли результат этой операции, но непредсказуем для возможных ошибок, поэтому вы должны вернуться напрямую (но должен быть введен процесс возврата дополнительного контекста), чтобы ошибки с контекстом могли передаваться черным ящиком между вызывающими объектами. Для ошибки, возвращаемой каждым уровнем, нас не волнует содержание ошибки, но нас интересует поведение, реализуемое соответствующей ошибкой, которое можно резюмировать следующим образом:

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

Errors are just values

Описывая выше несколько методов обработки ошибок, мы на самом деле видим, что ошибки — это действительно особая переменная. Для этих методов я чувствую, что после понимания соответствующих преимуществ и недостатков вы можете использовать их целенаправленно.Обработка ошибок непрозрачности очень яркая, и ее стоит попробовать.Это ориентированное на поведение, а не на ошибки программирование . На некоторые вопросы в предисловии действительно есть ответы: нам не нужно печатать журнал при каждой ошибке, нам нужно только передать контекст ошибки, а ошибку нужно обрабатывать только в одном месте. Так как же ошибка передает контекст? Это приводит к следующемуpkg/errorsБиблиотека (Дэйв не только указал направление, но и понял, и он был вне себя от радости и радости, хахаха..)

Ошибка передачи контекста

Наиболее часто используемые методы в нашей разработке:if err!=nil{return err}Этот метод обеспечивает быструю отдачу и равномерно обрабатывается внешним уровнем, но ему не хватает более полной информации, такой какxx module failed/ xx file open failed, если прошлоfmt.Errorfупаковки, что может привести к сбою верхнего уровня при ошибках Sentinel, поэтому нам нужен способ убедиться, что источник ошибки найден (error cause) и передать контекст каждого слоя, которыйpkg/errorsЧто эта библиотека делает для нас.

Wrap оборачивает ошибку и передает контекст

Код на самом деле относительно прост.Ниже показано использование Wrap через некоторые примеры материалов Дейва:

  • Чтение файла (первая оболочка)
  • Вызовите функцию чтения файла (вторая оболочка)
  • возможные результаты ошибок
  • Получить стек вызовов через ошибки. Печать

Причина распаковывает ошибку, чтобы получить источник ошибки

Источник ошибки можно узнать через Cause. Если нам нужно выполнить различную обработку в зависимости от источника ошибки, нам нужно использовать Cause, пример выглядит следующим образом:

Краткое описание обработки ошибок в системе

Вышеизложенное в основном резюмирует несколько способов борьбы с ошибками в го, а также компромиссы между плюсами и минусами в нескольких статьях Дэйви и некоторых моих собственных выводах.

  • Сведите к минимуму использование переменных ошибок, утверждений об ошибках и содержимого ошибок (можно использовать, но обратите внимание на компромиссы).
  • Рассматривайте ошибку как специальную переменную, не видимую в процедуре, и пытайтесь утверждать ее поведение, а не ее тип.
  • Ошибку следует обрабатывать только один раз, и процесс обработки ошибки должен основываться на содержании ошибки, чтобы определять различные варианты поведения.
  • Оберните контекст ошибки с помощью Wrap и получите источник ошибки с помощью Cause.
  • Библия — это просто история, а не норма, и она зависит от вашей собственной ситуации.

Межсистемная обработка ошибок

В первой половине в основном говорится о том, как следует определять, передавать и обрабатывать ошибки в системе go, а во второй половине в основном анализируется определение и передача ошибок между системами. Также у нас возникают вопросы при обработке http и rpc запросов: должен ли http код всегда передавать 200, а потом передавать код ошибки через кастомную структуру? Как должны передаваться ошибки между RPC?Должны ли сетевые ошибки и бизнес-ошибки передаваться через одну и ту же структуру? Должен ли быть унифицирован код ошибки для всей компании? Нужно ли централизованно настраивать копию ошибки APP в системе?

Ошибка определяется извне

В благотворительных службах мы часто определяем такие ответы:

 struct DeleteProductRes {
    1: optional DeleteProductData data
    1000: optional ThriftUtil.ErrInfo errinfo
 }

errinfo содержит код ошибки и информацию об ошибке, и каждая структура является аналогичным представлением. Проблема, вызванная этим, заключается в том, что SLI бизнес-системы трудно подсчитать на уровне фреймворка, а SLI включает в себя доступность и качество системы. Например, A звонит B, B звонит C, и срабатывает предохранитель между B и C, потому что нагрузка на C слишком высока. , SLA А на самом деле затрагивается, но у нас нет возможности своевременно и визуально его увидеть соответствующему ответственному лицу, поэтому информация об ошибках здесь должна относиться к самому внешнему уровню. Напротив, ниже приведено определение структуры и обработка ошибок grpc:

message QueryChangeResponse {
    message Item{
         string service_name = 1;
    }
    message Data{
        repeated Item items= 1;
    }
    Data data = 1;
}
rpc QueryChange(QueryChangeRequest) returns (QueryChangeResponse);

IDL grpc не содержит определения информации об ошибке, но клиент и сервер grpc имеют свой собственный статус и могут быть преобразованы в стандартную ошибку и обратно.

  • определение protobuf (автономный grpc, нет необходимости в специальной реализации):
package google.rpc;

message Status {
  // A simple error code that can be easily handled by the client. The
  // actual error code is defined by `google.rpc.Code`.
  // 一个可以被客户端处理的编码值
  int32 code = 1;

  // A developer-facing human-readable error message in English. It should
  // both explain the error and offer an actionable resolution to it.
  // debug使用,报错的具体原因
  string message = 2;

  // Additional error information that the client code can use to handle
  // the error, such as retry delay or a help link.
  // 附加错误信息,比如是否重试、重试策略、报错帮助链接等
  repeated google.protobuf.Any details = 3;
}
  • Назначение ошибки на стороне сервера:
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	// 有报错
	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
	// 无报错,请求成功
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
  • Логика обработки на стороне клиента:
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
	s,ok := status.FromError(err)
	if ok{// 可转为Status
		log.Println(s.Code())
		log.Println(s.Message())
		log.Println(s.Details())
	}else{// 普通error

	}
}else{
    // 无报错,请求成功
    log.Printf("Greeting: %s", r.GetMessage())
}
  • Перехватчик:
// server rpc cost, record to log and prometheus
func monitorServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	resp, err = handler(ctx, req)
	框架层面的各种通用处理...
	return resp, err
}

Уровень структуры может использовать перехватчик, чтобы легко получить информацию об ошибке (состоянии) для унифицированной обработки.Эта информация может использоваться для мониторинга, оповещения, оценки SLA системы и т. д. http также может добиться аналогичного эффекта, присваивая разные значения http-коду (и может единообразно передавать через заголовок информацию, аналогичную grpc Status). Для ошибок системного уровня, не связанных с бизнесом, библиотека состояний также унифицированным образом преобразует системные ошибки в состояния и сохраняет информацию о причине.Мы можем легко обрабатывать код ошибки или сообщение о состоянии. Поэтому было бы более уместно определить ошибку извне.

Дизайн кода ошибки

Четко определенный код ошибки может легко найти систему, которая сообщает об ошибке по коду ошибки.

  • определение Лучшим способом является присвоение кода сегмента определенного сегмента номера в соответствии с каждой бизнес-линейкой и системой. Например, код представляет собой целое число, и первые четыре цифры 8-битной длины (1000, 0400) представляют бизнес-направление, а последние четыре цифры могут быть кодом ошибки, настроенным бизнес-направлением. в полный код ошибки.
  • использовать Коды ошибок каждой бизнес-системы единообразно определены в базовой библиотеке для облегчения обмена информацией об ошибках. Мы также можем определить некоторые распространенные коды ошибок, похожие на такие ошибки, как 400 и 500. Конкретная информация о таких ошибках может отображаться в поле «Сообщение». вне.
  • продвигать Фактически, код ошибки сложно добиться единого корпоративного уровня, если есть разные языки, хотели объединить определение еще большего количества проблем, поэтому определение кодов ошибок - это больше конвенции, но не обязательно. По спецификации кода ошибки уменьшить стоимость коммуникаций во время ненормального расследования, соответствующие структуры уровня системы могут также наслаждаться преимуществами, представленными, идея состоит в том, чтобы привлечь больше людей с помощью этих преимуществ постепенно стандартизированного кода ошибки, не используйте В конце концов, это не имеет значения, не влияет на нормальный поток бизнеса.

Ошибка копирования универсальной конфигурации

Копия ошибки находится ближе к пользователю, и мы определенно не хотим, чтобы наши пользователи видели ее в приложении.127.0.0.1:8000 i/o timeoutошибка. В то же время пользователь запрашивает интерфейс, и этот интерфейс должен быть местом, где ошибка окончательно обрабатывается и определяется поведение, поэтому код ошибки должен быть преобразован в информацию, которую пользователь может принять. Содержание и шаблоны неправильного копирайтинга также будут часто меняться, поэтому по-прежнему необходима единая система настройки копирайтинга.Основой для получения копирайтинга может быть стандартный код ошибки, определенный вышеуказанным бизнесом, или сопоставление ключевого контента собственной системы копирайтинга. Условия.Дизайн будет относительно простым, поэтому я не буду его здесь слишком расширять.

конец

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

Ссылаться на

Dave.Cheney.net/2019/01/27/…

Dave.Cheney.net/2014/11/04/…

Dave.Cheney.net/Passat/go con…

GitHub.com/Персональный ПК/Персональный ПК.i…

cloud.Google.com/APIs/design…