воскресенье, 2 мая 2021 г.

Клиент Go для Elasticsearch: пакет estransport

До сих пор мы были озабочены первой обязанностью клиента: раскрытием API-интерфейсов Elasticsearch как типов на языке программирования Go. Давайте переключимся на компонент, отвечающий за отправку и получение данных по сети: пакет estransport.

Основным типом, предоставляемым пакетом, является estransport.Client, который реализует estransport.Interface. Он определяет единственный метод Perform(), который принимает *http.Request и возвращает *http.Response:

$ go doc -short github.com/elastic/go-elasticsearch/v7/estransport.Interface
type Interface interface {
  Perform(*http.Request) (*http.Response, error)
}

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

Прежде чем клиент сможет отправить запрос в кластер, он сначала должен знать, куда его отправить. Для локальной разработки это довольно просто: вы просто сохраняете значение по умолчанию (http://localhost:9200) или настраиваете клиент с одним адресом. В этом случае используется "единичный" пул соединений, который возвращает только одно соединение. Это также относится к использованию Elasticsearch Service в Elastic Cloud, которое предоставляет только одну конечную точку для кластера, поскольку имеет собственную логику балансировки нагрузки. И то же самое, естественно, применимо к любому кластеру за балансировщиком нагрузки или прокси.

Однако локальные производственные кластеры работают с несколькими узлами, и было бы неоптимально отправлять запросы только на один узел, что делает его узким местом. Вот почему пакет экспортирует интерфейс estransport.ConnectionPool, который позволяет выбрать соединение из списка:

$ go doc -short github.com/elastic/go-elasticsearch/v7/estransport.ConnectionPool
type ConnectionPool interface {
  Next() (*Connection, error)  // Next возвращает следующее доступное соединение.
  OnSuccess(*Connection) error // OnSuccess сообщает, что соединение было успешным.
  OnFailure(*Connection) error // OnFailure сообщает, что соединение не удалось.
  URLs() []*url.URL            // URLs возвращает список URL-адресов доступных подключений.
}
...

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

Метод Next() пула соединений "статуса" делегирует еще один интерфейс: estransport.Selector, с реализацией по умолчанию циклического селектора, который обычно является наиболее эффективным способом распределения нагрузки между узлами кластера. Опять же, пользовательская реализация селектора может быть передана в конфигурации, когда в сложных топологиях сети требуется более сложный механизм выбора соединения.

Например, упрощенная реализация селектора "hostname" может выглядеть так:

func (s *HostnameSelector) Select(conns []*estransport.Connection) (*estransport.Connection, error) {
  // Блокировка доступа снята

  var filteredConns []*estransport.Connection

  for _, c := range conns {
    if strings.Contains(c.URL.String(), "es1") {
      filteredConns = append(filteredConns, c)
    }
  }

  if len(filteredConns) > 0 {
    s.current = (s.current + 1) % len(filteredConns)
    return filteredConns[s.current], nil
  }

  return nil, errors.New("No connection with hostname [es1] available")
}

Однако настраиваемый селектор более полезен с другой функцией клиента: способностью обнаруживать узлы в кластере, в просторечии известной как "сниффинг". Он использует Nodes Info API для получения информации об узлах в кластере и динамически обновляет конфигурацию клиента в зависимости от состояния кластера. Это позволяет вам, например, указывать клиенту только на координирующие узлы кластера и позволяет ему автоматически обнаруживать данные или принимать узлы.

Клиент Go предоставляет метод DiscoverNodes() для выполнения операции вручную, а также параметры конфигурации DiscoverNodesInterval и DiscoverNodesOnStart для выполнения операции при инициализации клиента и через определенные промежутки времени. Помимо получения списка узлов и создания динамической конфигурации клиента, он также хранит метаданные, прикрепленные к узлам, такие как роли или атрибуты.

Еще одна полезная функция транспортного компонента - это возможность повторить неудачный запрос, что особенно важно в распределенной системе, такой как Elasticsearch. По умолчанию он повторяет запрос до трех раз при получении сетевой ошибки или ответа HTTP с кодом состояния 502, 503 или 504. Количество попыток повторения и коды ответа HTTP настраиваются вместе с дополнительной задержкой отсрочки. Эту функцию можно вообще отключить.


Читайте также:


Комментариев нет:

Отправить комментарий