Особенности данных в реальном времени

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

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

Эти диаграммы имеют следующие особенности:

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

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

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

В статье я хотел бы поговорить об этих проблемах, а также о некоторых решениях, соответственно.

Предметы в визуализации в реальном времени

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

  • прилавок
  • таймер
  • измерять

прилавок

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

  • Ответ с кодом 200
  • Запрос из определенного сеанса

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

Таймер

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

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

Измерять

Иногда это полезно, когда мы просто хотим записать некоторые (случайные) числа, для этого созданы датчики. В отличие от счетчика, у него нет стабильных темпов изменения, поэтому они вообще непредсказуемы. Обычно нас не волнует, как он меняется, вместо этого мы заботимся только о номере / статусе в определенный момент.

  • Узел жив или нет
  • Сколько там процессов
  • Как использование памяти в данный момент

Типичный рабочий процесс визуализации данных в реальном времени

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

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

  • Получить исходные данные
  • Токенизируйте и сделайте это структурным
  • Простая агрегация
  • Более глубокая агрегация

Токенизируйте и сделайте это структурным

В этой работе нам могут помочь различные инструменты. Посредством некоторой базовой конфигурации мы можем заставить его работать, используя logstash для передачи данных в statsd из производственного файла журнала (и данные в конечном итоге загружаются в графит).

input {
  stdin {}
}
filter {
  grok {
    match => {
      "message" => "%{DATA:time} %{DATA:status} %{NUMBER:request_time} %{DATA:campaign} %{DATA:mac} %{DATA:ap_mac} %{GREEDYDATA:session}"
    }
  }
}
output {
  stdout { codec => rubydebug }
  statsd {
    host => 'localhost'
    increment => "airport.%{session}"
  }
  statsd {
    host => 'localhost'
    increment => "airport.%{status}"
  }
}

logstash на самом деле является очень гибким, настраиваемым инструментом, который позволяет нам определять источник данных, правила сопоставления и вывод: если задано data source, когда запись в нем соответствует определенному rule, затем отформатировать ее и вывести в output. Похоже на то, что делает IFTTT (If This Then That), не так ли?

tail -f /var/logs/nginx/access.log | logstash -f log.conf

В приведенном выше примере мы используем stdin в качестве ввода, и когда он содержит time status request_time campaign mac ap_mac session, это совпадение. Для этих совпадающих строк мы загружаем их в statsd экземпляр, работающий локально. Инструкция increment может увеличивать соответствующий счетчик для каждого совпадения.

Скажем, у нас в лог-файле есть строчка:

1529242838 403 0.02 f3715a7f52d8cef53fef1f73134e487a 00:61:71:53:ff:b0 T2-CL*-49-D* 2293c8e9-8801-485b-9f1d-9e5a7f5a8965

И тогда результат матча должен быть:

{
        "campaign" => "f3715a7f52d8cef53fef1f73134e487a",
    "request_time" => "0.02",
          "status" => "403",
         "session" => "2293c8e9-8801-485b-9f1d-9e5a7f5a8965",
         "message" => "1529242838 403 0.02 f3715a7f52d8cef53fef1f73134e487a 00:61:71:53:f4:0b T2-CL13-49-D87 2293c8e9-8801-485b-9f1d-9e5a7f5a8965",
        "@version" => "1",
            "host" => "juntao-qiu.local",
          "ap_mac" => "T2-CL*-49-D*",
            "time" => "1529242838",
             "mac" => "00:61:71:53:ff:b0",
      "@timestamp" => 2018-06-17T13:40:39.023Z
}

Затем logstash увеличит счетчик airport.2293c8e9-8801-485b-9f1d-9e5a7f5a8965:

counter["airport.2293c8e9-8801-485b-9f1d-9e5a7f5a8965"] += 1

Статистика

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

По сути, StatsD - очень простая UDP служба. Использование UDP может помочь избежать огромного количества времени, которое TCP требуется для установления надежных соединений. StatsD поддерживает некоторый локальный счетчик и состояние и синхронизируется с graphite каждые 10 секунд по умолчанию.

Визуализировать

Чтобы визуализировать данные в реальном времени, вы должны рассмотреть множество вопросов, как вы обычно делаете это в других проектах визуализации. Настольный компьютер, Интернет или даже мобильные устройства? Если вам нужно отобразить это в Интернете, должна ли страница адаптироваться к разным размерам экрана? Визуализация - это динамически интерактивная диаграмма или просто статическое представление?

Кроме того, каковы требования к точности? Должны ли мы рассчитывать статистику каждую минуту или каждые 15 минут? Для разной точности требования совершенно разные по хранению. Часто используемая стратегия - снизить точность просроченных данных и просто визуализировать самые свежие данные с более высоким разрешением.

Выше показана типичная информационная панель в реальном времени на основе Интернета с использованием grafana.

Инструменты

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

База данных временных рядов

В этой конкретной суженной области на самом деле существует особый вид базы данных, СУБД временных рядов. По сути, это Key-Value база данных, в которой обычно хранятся метка времени, ключ и значение. Кроме того, некоторые реализации предоставляют Query Language, чтобы упростить и гибко запросить сохраненные значения.

Вот список некоторых известных TSDB:

Фидер / API

Хотя большая часть TSDB предоставляет собственный API для хранения и доступа к данным, большинство людей предпочитают использовать простые HTTP API. StatsD - очень простая Node.js служба, с помощью API (уже связанного со многими языками программирования) вы можете легко создавать counter и timer.

Визуализировать библиотеки

Grafana - мощный и широко используемый клиентский фреймворк. Вы можете легко интегрировать источник данных из различных бэкэндов. Возможно отобразить нагрузку на ЦП / память, данные из graphite, и статус онлайн-пользователя могут поступать из influxdb или promethous.

Если вам нужен больший контроль в некоторых конкретных случаях. Вы можете использовать комбинацию d3.js + cubism. Вы можете написать немного JavaScript для периодического извлечения данных из серверной службы. И, наконец, визуализируйте их на svg холсте.

Рендеринг времени

Логстальгия

Logstalgia - очень интересный инструмент, который может читать журнал определенного формата, а затем визуализировать его очень необычным способом, как в классической игре Brick Breaker.

Следующие поля обязательны для заполнения :

  • Отметка времени UNIX
  • Запросить имя хоста
  • Путь запроса
  • Код ответа
  • Размер ответа

И все поля должны быть расположены так:

1529206121|12.21.18.246|/dispatcher/campaigns/2de808e08dccec2c7e55e41ecbd5a421|200|20

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

const source = '[$time_local] "$remote_addr - $remote_user" "$request" $status $body_bytes_sent "$http_referer" $request_time "$http_user_agent"'; 
const NginxParser = require('nginxparser');
const parser = new NginxParser(source);
const moment = require('moment');
parser.read('-', (row) => {
    const ts = moment(row.time_local, "DD/MMM/YYYY:HH:mm:ss Z").unix();
    const parsed = row.request.split(/\s+/)
    console.log(`${ts}|${row.ip_str}|${parsed[1]}|${row.status}|${row.body_bytes_sent}`);
}, (err) => {
    throw err;
});

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

tail -f /var/log/nginx/access.log | node adaptor.js | logstalgia

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

Отображение времени на веб-странице

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

WebScoket + D3.js

const _ = require('lodash');
const { spawn } = require('child_process');
const generator = spawn('./generator.sh');
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
function parse(data) {
    //...
}
wss.on('connection', (ws) => {
  const output = (data) => {
    ws.send(JSON.stringify(parse(data)));
  }
  generator.stdout.on('data', output);
  ws.on('close', () => {
    generator.stdout.removeListener('data', output);
  });
});

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

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

Реализацией generator.sh может быть что угодно, что может непрерывно распечатывать какое-то сообщение журнала, простая реализация может быть такой:

tail -f /var/logs/nginx/access.log

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

ssh qa-env tail -f /var/logs/wifi-portal/wifi-portal-2018-06-13-access.log

На стороне клиента единственное, что вам нужно сделать, это установить соединение WebSocket и прослушивать поступающие данные. Вот скрипт, написанный на D3.js с использованием плагина от Real time Plugin.

var ws = new WebSocket("ws://localhost:8080");
ws.onopen = function() {
  console.log('connected');
};
ws.onmessage = function (evt) { 
  const event = JSON.parse(evt.data);
  categroies.push(_.truncate(event.campaign, { 'length': 8 }));
  const campaigns = _.uniq(categroies);
  chart.yDomain(campaigns);
  chart.yDomain().forEach(function(cat, i) {
    var now = new Date(event.date);
    var mills = event.mills * 200;
    const obj = {
      time: now,
      color: color(mills),
      opacity: 1,
      category: _.truncate(event.campaign, { 'length': 8}),
      type: "circle",
      size: mills,
    }
    chart.datum(obj);
  });
};

Легенда графиков:

  • Ось X представляет время
  • Ось Y представляет собой доступ к конкретному ресурсу.
  • Каждый запрос будет оформлен в виде пятна
  • Размер пятна представляет время отклика в мс.

Визуализируйте статистику

Использование графита

graphite имеет встроенную систему панелей управления, вы можете выбрать демонстрацию нескольких датчиков на одной диаграмме:

Кроме того, graphite предоставляет гораздо более мощный render API. Вы можете получить другой формат, например csv, json или двоичный формат изображения. Кроме того, вы можете получить более сложный результат расчета с помощью параметра target.

Например:

http://localhost/render/?format=json&target=stats.jc.airport.campaigns.1565ae2c79aee5e635e55d73354c7cd3
http://localhost/render?format=raw&target=alias(sumSeries(stats.jc.airport.campaigns.*)%2C%27%27)&from=1529245830&until=1529245929

Значение target здесь alias(sumSeries(stats.jc.airport.campaigns.*), ''), что означает вычисление сводки всех счетчиков, начинающихся с stats.jc.airport.campaigns. Вы можете указать from и until для получения данных за определенный период времени.

graphite предоставляет множество функций для агрегирования, таких как среднее значение, мин. / Макс. И т. Д. Вот полностью справочник.

Использование диаграммы горизонта

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

Вы можете указать другой источник данных для горизонтальной диаграммы, например graphite:

var graphite = context.graphite("http://localhost");
var api_metrics = [
  graphite.metric("sumSeries(stats.jc.airport.campaigns.*)").alias("Campaigns Freq")
];

cubims затем отправляйте такой запрос на graphite каждые 10 секунд:

http://localhost/render?format=raw&target=alias(sumSeries(stats.jc.airport.campaigns.*)%2C%27%27)&from=1529245830&until=1529245929

И обновляет график на основе полученных данных:

d3.select("body").selectAll(".horizon")
    .data(api_metrics)
  .enter().insert("div", ".bottom")
    .attr("class", "horizon").call(horizon.extent([0, 50]));

На самом деле horizon chart занимает всего несколько пространств по вертикали, поэтому вы можете легко объединить несколько диаграмм, как указано выше.

Резюме

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

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

использованная литература

Другие полезные ссылки