Статьи

Отказоустойчивость в Go

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

Netflix испытал досадные сбои в распределенной системе и разработал Hystrix как сложную реализацию шаблона прерывателя цепи . Вкратце, Hystrix это:

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

Github

Что такое Hystrix?

Действительно, Hystrix активно используется в Netflix и:

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

Github

Что такое Hystrix?

Хотя Hystrix реализован на Java , доступны альтернативные реализации. На самом деле, если вы создаете сервис на Go , вам наверняка захочется взглянуть на hystrix-go , что, как следует из его названия, является довольно простой в использовании реализацией Go для Hystrix.

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

Простая функция для получения открытых вопросов

01
02
03
04
05
06
07
08
09
10
11
12
13
14
func retrieveOpenIssues(userConfig UserDefinedConfig) JiraSearchResponse {
  url := fmt.Sprintf("%s/rest/api/2/search", userConfig.Host)
  query := fmt.Sprintf("project = %s AND resolution = Unresolved ORDER BY priority DESC", userConfig.Project)
  fields := []string{"summary", "key", "status", "assignee", "description", "issuetype", "created"}
  jsonStr, _ := json.Marshal(JiraSearchRequest{query, 0, fields})
  responseChannel := make(chan JiraSearchResponse)
  go func() {
      body := httpRequest(url, "POST", jsonStr, userConfig)
      var searchRes JiraSearchResponse
      json.Unmarshal(body, &searchRes)
      responseChannel <- searchRes
  }()
  return <-responseChannel
}

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

Иди в панике!

1
2
3
4
5
6
panic: Post https://jira.acme.com/rest/api/2/search: dial tcp 94.144.0.13:443: i/o timeout
 
goroutine 24 [running]:
runtime.panic(0x325620, 0xc208024bd0)
  /usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
...

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

Естественно, вам сначала нужно импортировать Hystrix-Go:

импорт Hystrix-Go

1
import "github.com/afex/hystrix-go/hystrix"

hystrix-go очень просто реализовать в вашем коде — вы просто реализуете свою логику внутри функции hystrix.Go (которая внутренне создает goroutine ). Более того, вы создаете резервную функцию, которая вызывается в состоянии ошибки ( например, время ожидания и т . Д. ).

Hystrix-Go очень прост в использовании

1
2
3
4
5
6
7
hystrix.Go("some command", func() error {
    // normal path code
    return nil
}, func(err error) error {
    // do this when errors occur
    return nil
})

Обратите внимание, что функция hystrix.Go принимает три аргумента, два последних — анонимные функции : первый — ваша обычная логика, а последний — желаемая запасная логика. Естественно, вы можете использовать Go Channel для получения данных от любой функции.

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

Hystrix-Go также прост в настройке

1
2
3
4
5
hystrix.ConfigureCommand("unique_command", hystrix.CommandConfig{
    Timeout:               1000,
    MaxConcurrentRequests: 100,
    ErrorPercentThreshold: 25,
})

В приведенном выше случае время ожидания указывается в миллисекундах; следовательно, команда unique_command вызовет условие unique_command через 1 секунду. Библиотека хорошо протестирована, и я рекомендую прочитать некоторые из ее тестов, чтобы увидеть срабатывание различных вариантов восстановления.

Обладая знаниями о том, как добавить запасной вариант и настроить любые связанные с ним условия, очень легко включить допустимую отказоустойчивость в любой код go. Например, я могу взять этот вызывающий код JIRA и обернуть его внутри функции hystrix.Go следующим образом:

Теперь с большим количеством Hystrix!

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
func retrieveOpenIssues(userConfig UserDefinedConfig) JiraSearchResponse {
  url := fmt.Sprintf("%s/rest/api/2/search", userConfig.Host)
  query := fmt.Sprintf("project = %s AND resolution = Unresolved ORDER BY priority DESC", userConfig.Project)
  fields := []string{"summary", "key", "status", "assignee", "description", "issuetype", "created"}
  jsonStr, _ := json.Marshal(JiraSearchRequest{query, 0, fields})
  responseChannel := make(chan JiraSearchResponse)
  hystrix.ConfigureCommand("Get all Issues", hystrix.CommandConfig{Timeout: 2000})
  hystrix.Go("Get all Issues", func() error {
      body := httpRequest(url, "POST", jsonStr, userConfig)
      var searchRes JiraSearchResponse
      json.Unmarshal(body, &searchRes)
      responseChannel <- searchRes
      return nil
  }, func(err error) error {
      var searchRes JiraSearchResponse
      responseChannel <- searchRes
      return nil
  })
  return <-responseChannel
}

В этом случае я также указал, что ключевая функция «Получить все проблемы» запустит тайм-аут через 2 секунды. Моя обычная логика, использующая HTTP GET , помещается в первую анонимную функцию, а моя запасная логика — во вторую. Эта резервная логика просто возвращает пустой ответ, чтобы не полностью влиять на нисходящие сервисы, полагаясь на какой-то ответ. То есть, если JIRA работает медленно, не работает или просто не отвечает, а не генерируется паника, возвращается пустой ответ. Это якобы запрещает каскадную цепочку ошибок.

Конечно, мой запасной код может использовать другую логику, если сочтет это необходимым; Более того, мой запасной код может определять тип сгенерированной ошибки. Например, мой резервный код может реагировать по-разному в случае тайм-аута, в отличие от проблемы параллелизма. Объект error переданный в резервную функцию, может быть запрошен достаточно легко: if err.Error() == "max concurrency" .

Netflix добился успеха с Hystrix в большой, высоко распределенной системе. Если вы хотите встроить допуск в любые сервисы Go, то я не могу рекомендовать Hystrix-Go достаточно: его очень легко настроить и использовать.

Ссылка: Отказоустойчивость в Go от нашего партнера JCG Эндрю Гловера в блоге The Disco Blog .