Принцип и реализация сервисно-адаптивного объединения

Go

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

call_chain

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

Принцип предохранителя

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

В этом режиме вызывающая служба поддерживает конечный автомат для каждой вызывающей службы (путь вызова), и в этом конечном автомате есть три состояния:

  • Закрыто: в этом состоянии нам нужен счетчик для записи количества неудачных вызовов и общего количества запросов.Если уровень отказов достигает заданного порога в течение определенного временного окна, переключается на отключен В это время включается период ожидания , а по истечении времени ожидания он переходит в полузакрытое состояние.Период ожидания дает системе возможность исправить ошибку, вызвавшую сбой вызова, чтобы вернуться в нормальное рабочее состояние. В выключенном состоянии ошибка вызова зависит от времени и сбрасывается через определенные промежутки времени, что предотвращает переход предохранителя в открытое состояние из-за случайных ошибок.
  • Открыто (Open): В этом состоянии ошибка будет возвращена немедленно при инициировании запроса. Как правило, запускается таймер тайм-аута. По истечении времени таймера состояние переключается на полуоткрытое состояние. Вы также можете установите таймер для периодического обнаружения службы.
  • Half-Open: В этом состоянии приложению разрешено отправлять определенное количество запросов в вызываемую службу. Если эти вызовы являются нормальными, можно считать, что вызываемая служба вернулась в нормальное состояние. В это время автоматический выключатель переводится в закрытое состояние.Также необходимо сбросить счетчик. Если в этой части все еще происходит сбой вызова, считается, что вызываемый абонент не восстановился, автоматический выключатель переключится в выключенное состояние, а затем сбросит счетчик.Полуоткрытое состояние может эффективно предотвратить восстановление службы. снова перегружены внезапным большим количеством запросов.
breaker_state

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

Введение предохранителя

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

Мы знаем, что прерыватель цепи в основном используется для защиты вызывающей стороны.Вызывающему абоненту необходимо пройти через прерыватель цепи при инициировании запроса, а клиентский перехватчик как раз и выполняет эту функцию, поэтому во фреймворке zRPC прерыватель цепи реализован в client В терминальном перехватчике принцип работы перехватчика следующий:

interceptor

Соответствующий код:

func BreakerInterceptor(ctx context.Context, method string, req, reply interface{},
	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  // 基于请求方法进行熔断
	breakerName := path.Join(cc.Target(), method)
	return breaker.DoWithAcceptable(breakerName, func() error {
    // 真正发起调用
		return invoker(ctx, method, req, reply, cc, opts...)
    // codes.Acceptable判断哪种错误需要加入熔断错误计数
	}, codes.Acceptable)
}

Реализация предохранителя

Реализация автоматических выключателей в zRPC относится кАлгоритм защиты от перегрузок Google Sre, принцип алгоритма следующий:

  • Количество запросов (запросов): общее количество запросов, инициированных вызывающим абонентом
  • Количество принятых запросов (accepts): количество запросов, обычно обрабатываемых вызываемой стороной.

В нормальных условиях эти два значения равны.За исключением вызываемой службы, запрос начинает отклоняться, а значение количества принятых (акцептов) запросов начинает постепенно становиться меньше количества запросов ( запросы). В это время звонящий может продолжать посылать запросы. , пока запросы = K * не будут приняты, как только этот лимит будет превышен, автоматический выключатель снова включится, и новый запрос будет отброшен локально с определенной вероятностью и вернуть ошибку напрямую.Формула расчета вероятности выглядит следующим образом:

client_rejection2

Изменяя K (значение множителя) в алгоритме, можно регулировать чувствительность предохранителя.При уменьшении значения алгоритм адаптивного предохранителя будет более чувствительным.При увеличении значения алгоритм адаптивного предохранителя будет менее чувствительным. Например, если предположить, что лимит запросов вызывающего абонента изменяется с запросов = 2 * acceptst на запросы = 1,1 * accepts, то это означает, что один из каждых десяти запросов вызывающего абонента приведет к срабатыванию прерывателя цепи.

Путь к коду — нулевой/основной/прерыватель

type googleBreaker struct {
	k     float64  // 倍值 默认1.5
	stat  *collection.RollingWindow // 滑动时间窗口,用来对请求失败和成功计数
	proba *mathx.Proba // 动态概率
}

Реализация адаптивного алгоритма слияния

func (b *googleBreaker) accept() error {
	accepts, total := b.history()  // 请求接受数量和请求总量
	weightedAccepts := b.k * float64(accepts)
  // 计算丢弃请求概率
	dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
	if dropRatio <= 0 {
		return nil
	}
	// 动态判断是否触发熔断
	if b.proba.TrueOnProba(dropRatio) {
		return ErrServiceUnavailable
	}

	return nil
}

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

func Acceptable(err error) bool {
	switch status.Code(err) {
	case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss: // 异常请求错误
		return false
	default:
		return true
	}
}

Если запрос нормальный, количество запросов и количество принятых запросов увеличиваются на единицу через markSuccess, если запрос ненормальный, увеличивается только количество запросов на единицу.

func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
	// 判断是否触发熔断
  if err := b.accept(); err != nil {
		if fallback != nil {
			return fallback(err)
		} else {
			return err
		}
	}

	defer func() {
		if e := recover(); e != nil {
			b.markFailure()
			panic(e)
		}
	}()
	
  // 执行真正的调用
	err := req()
  // 正常请求计数
	if acceptable(err) {
		b.markSuccess()
	} else {
    // 异常请求计数
		b.markFailure()
	}

	return err
}

Суммировать

Вызывающий объект может защитить себя с помощью механизма прерывателя цепи, чтобы предотвратить аномальные вызовы нижестоящих служб или слишком много времени, чтобы повлиять на бизнес-логику вызывающего объекта.Многие полнофункциональные микросервисные платформы будут иметь встроенные прерыватели цепи. На самом деле требуется не только прерыватель цепи между вызовами микросервиса, но и механизм прерывателя цепи может быть введен при вызове зависимых ресурсов, таких как mysql, redis и т.д.

адрес проекта:

GitHub.com/them-specialty/go…

Если вы считаете, что статья хорошая, добро пожаловатьgithubнажмитеstar🤝