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

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

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

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

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

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

Для решения данной проблемы лучше всего подходит использование технологии распределенного трейсинга (Distrbuted tracing). Суть данной технологии заключается в представлении графов запросов (трейсов) в реальном времени. Анализ таких графов позволяет определить причины возникших проблем, например причины долгих запросов.

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

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

 

 

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

Трейсинг позволяет ответить на следующие основные вопросы при решении проблемы:

  • Какая именно функция является причиной проблемы

  • Длительность работы этой проблемной функции

  • Продолжительность выполнения функцией проблемной операции

  • Переданные функции параметры

  • Насколько глубоко переданные пользователем параметры задействуют компоненты функции

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

Кроме того, добавление команд трассировки усложняет код приложения (иногда довольно существенно) и может привести к увеличению нагрузки на систему и как следствие к замедлению в работе основного приложения.

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

Переходим к практике

В качестве средства для сбора распределенных трейсов рассмотрим систему Zipkin. Данная система была разработана Twitter еще в 2012 году. 

Существует несколько вариантов установки и использования данного решения. Так можно воспользоваться jar-файлом:

curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

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

git clone https://github.com/openzipkin/zipkin
cd zipkin
./mvnw -DskipTests --also-make -pl zipkin-server clean install
java -jar ./zipkin-server/target/zipkin-server-*exec.jar

Но мы в качестве тестового примера развернем Zipkin из контейнера Docker.

Для этого при запуске контейнера необходимо указать какой порт мы собираемся опубликовать:

docker run -d -p 9411:9411 openzipkin/zipkin

В результате загрузки контейнера нам должен стать доступен веб интерфейс приложения по порту 9411.

В качестве примера, на основе которого мы будем разбирать работу с трейсами давайте рассмотрим работу веб приложения и в частности, этап ответа на запрос GET /foo.

Как видно из представленной схемы, трейс у нас фиксируется на шаге получения запроса GET /foo и далее результаты выполнения запроса передаются в Zipkin. В результате в веб консоли будет отображено примерно следующее:

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

  • Span - одна завершившаяся операция в рамках запроса, содержит события и тэги

  • Trace - граф задержки всего запроса, состоит из span-ов

  • Tracer - библиотека в коде приложения, которые позволяют собирать и отправлять информацию о span-ах

И чтобы было понятно, о чем именно идет речь, проиллюстрируем это на примере:

В случае, если у нас нет возможности собирать трейсы онлайн, мы можем подгрузить их из JSON файлов. При этом файлы трейсов могут иметь примерно такой вид:

[
  {
    "traceId": "b7794163e2432bf0",
    "id": "d4976a1d6eb10c24",
    "name": "service_aaaa",
    "timestamp": 1559123832737078,
    "duration": 100,
    "localEndpoint": {
      "serviceName": "1111"
    },
    "remoteEndpoint": {
      "serviceName": "2222"
    },
    "debug": true
  },

  {
    "traceId": "b7794163e2432bf0",
    "parentId": "d4976a1d6eb10c24",
    "id": "ba7f99539c4e2b53",
    "name": "service_aaaa",
    "timestamp": 1559123832791081,
    "duration": 100,
    "localEndpoint": {
      "serviceName": "2222"
    },

    "remoteEndpoint": {
      "serviceName": "3333"
    },
    "debug": true
  }
]

Как видно, основным идентификатором является traceId, его значение используется для идентификации всех элементов трейса. Далее уже идут идентификаторы отдельных Span. В представленном примере первый спан является основным, в то время как второй следует за ним. Тем самым выстраивается связность между спанами внутри одного трейса.

Заключение

На этом можно закончить рассмотрение такой важной темы как распределенные трассировки. В этой статье мы рассмотрели основные моменты, связанные со сбором трейсов с помощью системы Zipkin.

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