Схема реализации прерывателя цепи общей реализации микросервисов Golang

Микросервисы

фон выключателя

Сценарии каскадного сбоя микросервиса

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

автоматический выключатель и повторные попытки

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

Анализ конструкции автоматического выключателя

Автоматический выключатель на основе прокси

Прерыватель цепи эквивалентен прокси-серверу для выполнения операции запроса, на котором выполняется выполнение операции запроса.

Принцип реализации:

  1. Перехватывать запрос, выполняемый сервисом, и решать, возвращаться ли напрямую через текущее состояние, если нет, то выполнять последующие операции
  2. Попытка выполнить операцию и получить возвращаемый результат
  3. Определите текущее состояние автоматического выключателя и измените его в соответствии с возвращенным результатом и текущей статистической информацией.
  4. вернуть результат выполнения

автомат состояния автоматического выключателя

В реализации конечного автомата автоматического выключателя существует три состояния: Closed (автоматический выключатель замкнут), Open (разомкнут), HalfOpen (полуоткрыт).

условие иллюстрировать Примечание
Closed закрытие Автоматический выключатель замкнут для выполнения нормальной работы
Open Открыть Автоматический выключатель разомкнут, все запросы возвращают ошибки напрямую, и ни один запрос не выполняется.
HalfOpen полуоткрытый Разрешить выполнение ограниченного количества запросов, в случае успеха вернуться к закрытию, если все еще не удается, вернуться к открытию, а затем перезапустить таймер тайм-аута.

# реализация автоматического выключателя

Принципиальная схема реализации

Реализация автоматического выключателя в основном разделена на три части: статистика состояния, передача состояния и выполнение запроса.

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

В Golang уже есть реализации с открытым исходным кодом,GitHub.com/Sony/go не горячо..., а потом сохранить рынок и проанализировать его реализацию

Статусная статистика - счетчик счетчиков

Counts — это счетчик, который записывает количество успешных и неудачных запросов текущего запроса.

type Counts struct {
	Requests             uint32    // 请求数
	TotalSuccesses       uint32    // 成功
	TotalFailures        uint32    // 失败
	ConsecutiveSuccesses uint32    // 连续成功
	ConsecutiveFailures  uint32 // 连续失败
}

Счетчик завершает количество раз, когда соответствующее состояние запроса завершено, и предоставляет данные для последующей передачи состояния.Counts предоставляет несколько вспомогательных интерфейсов, onRequest, onSuccess, onFailure и clear для реализации операции, соответствующей состоянию запроса.Если вам интересно , вы можете прочитать его ниже.

Конечный автомат — CircuitBreaker

type CircuitBreaker struct {
	name          string
        // maxRequests限制half-open状态下最大的请求数,避免海量请求将在恢复过程中的服务再次失败
	maxRequests   uint32
     // interval用于在closed状态下,断路器多久清除一次Counts信息,如果设置为0则在closed状态下不会清除Counts
	interval      time.Duration
        // timeout进入open状态下,多长时间切换到half-open状态,默认60s
	timeout       time.Duration
        // readyToTrip熔断条件,当执行失败后,会根据readyToTrip决定是否进入Open状态
	readyToTrip   func(counts Counts) bool
        // onStateChange断路器状态变更回调函数
	onStateChange func(name string, from State, to State)

	mutex      sync.Mutex
        //. state 断路器状态
	state      State
        // generation 是一个递增值,相当于当前断路器状态切换的次数, 为了避免状态切换后,未完成请求对新状态的统计的影响,如果发现一个请求的generation同当前的generation不同,则不会进行统计计数
	generation uint64
        //  Counts 统计
	counts     Counts
        // expiry 超时过期用于open状态到half-open状态的切换,当超时后,会从open状态切换到half-open状态
	expiry     time.Time
}

основной процесс

CircuitBreaker.Execute

Выполнение запроса, интерфейс выполнения запроса открыт для внешнего мира

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
        // 执行请求钩子,会根据当前状态,来返回当前的generation和err(如果位于open和half-open则不为nil), 通过err来进行判断是否直接返回
	generation, err := cb.beforeRequest()
	if err != nil {
		return nil, err
	}

        // 捕获panic,避免应用函数错误造成断路器panic
	defer func() {
		e := recover()
		if e != nil {
			cb.afterRequest(generation, false)
			panic(e)
		}
	}()

    // 执行请求
	result, err := req()
        // 根据结果来进行对应状态的统计, 同时传递generation
	cb.afterRequest(generation, err == nil)
	return result, err
}

CircuitBreaker.beforeRequest

func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
	cb.mutex.Lock()
	defer cb.mutex.Unlock()

        // 获取当前的状态
	now := time.Now()
	state, generation := cb.currentState(now)

    // open和half-open状态则直接返回
	if state == StateOpen {
		return generation, ErrOpenState
	} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
            // 避免海量请求对处于恢复服务的影响,这里有一个限流的操作,避免请求数超过最大请求数
		return generation, ErrTooManyRequests
	}
        // 统计状态
	cb.counts.onRequest()
	return generation, nil
}

CircuitBreaker.afterRequest

func (cb *CircuitBreaker) afterRequest(before uint64, success bool) {
	cb.mutex.Lock()
	defer cb.mutex.Unlock()

    // 重新获取状态
	now := time.Now()
	state, generation := cb.currentState(now)
        // 如果前后状态不一致,则不计数
	if generation != before {
		return
	}

        // 根据状态计数
	if success {
		cb.onSuccess(state, now)
	} else {
		cb.onFailure(state, now)
	}
}

CircuitBreaker.currentState

func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {
	switch cb.state {
	case StateClosed:
                // 如果当前当前是closed状态,并且有设置expiry,则递增Generation到新一轮统计计数
		if !cb.expiry.IsZero() && cb.expiry.Before(now) {
			cb.toNewGeneration(now)
		}
	case StateOpen:
                // 如果是Open状态,并且超时,则尝试到半打开状态
		if cb.expiry.Before(now) {
			cb.setState(StateHalfOpen, now)
		}
	}
	return cb.state, cb.generation
}

CircuitBreaker.toNewgeneration


func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
        // 递增generation, 清除状态
	cb.generation++
	cb.counts.clear()

        // 设置超时时间
	var zero time.Time
	switch cb.state {
	case StateClosed:
		if cb.interval == 0 {
			cb.expiry = zero
		} else {
			cb.expiry = now.Add(cb.interval)
		}
	case StateOpen:
		cb.expiry = now.Add(cb.timeout)
	default: // StateHalfOpen
		cb.expiry = zero
	}
}

Суммировать

автоматический выключатель золотая ссылка

  • beforeRequest : может ли запрос быть выполнен после завершения текущего запроса, статус переключается с течением времени, и в то же время возвращается текущее поколение
  • req: выполнить запрос
  • afterRequest: завершите статистику статуса запроса и решите переключить статус

Преимущества и недостатки автоматических выключателей

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

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

Другие статьи могут быть доступныwww.sreguide.com/