Development

Журнал сообщений (коммуникационные логи)

Эта система обрабатывает и хранит все журналы полученных/отправленных пар запросов, созданных в ходе работы с SMS-сообщениями - Автор Milan Mimica

December 11 2015

Обзор

Платформа для работы с сообщениями Infobip обрабатывает около 250 млн. транзакций в день. Это 250 млн. полученных от клиента сообщений, которые были обработаны, поставлены в очередь, при необходимости адаптированы, направлены оператору связи, включены в отчет о доставке, который в свою очередь предоставляется клиенту. Это как минимум 6 пар запросов и ответов  с различными API, которые (иногда) работают с 4-мя совершенно разными протоколами – все для обработки одного сообщения. И это в случае, если все прошло успешно, и не требуются дополнительные запросы. И это только для внешней коммуникации –  т.е. мы не говорим о внутренних служебных сообщениях. Более того, многие сообщения взаимосвязаны, и работа с отдельно взятым сообщением не позволяет увидеть полную картину.

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

Распределенные логи

Итак, “распределенные” звучит хорошо. Это, своего рода, модный термин. Почему-то нам инстинктивно хочется, чтобы все было распределено. Но давайте посмотрим, что в действительности означает система распределенных логов.

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

Но проблема в том, что эти журналы сообщений не соотносятся друг с другом. Это значит, что 6 пар запросов/ответов абсолютно никак не связаны. Для того чтобы соотнести несколько логов по одному отдельно взятому сообщению  требуется очень тщательный анализ и ручная выгрузка идентификаторов соответствия из различных систем обработки данных. Не говоря о том, что иногда требуется найти логи для различных сообщений. Происходит это вследствие отсутствия связи между клиентом и оператором – у них просто нет единого идентификатора для нахождения соответствия.

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

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

Создание UUID

На самом деле, здесь нет ничего революционного. Мы просто поняли, что каждому сообщению нужен свой уникальный идентификатор – UUID (Универсальный уникальный идентификатор), который бы присваивался сообщению  в самом начале при первом же появлении сообщения в системе и передавался бы далее по цепочке другим сервисам вместе с сообщением. Тогда каждый сервис, создающий логи, мог бы прикреплять этот ID для сохранения метаданных. Теперь все стало просто! Теперь мы можем соотносить несколько записей журналов с одним сообщением. Но не все так просто…

Соотнесение отчетов о доставке

Сразу возникает новая проблема. Даже если мельком просмотреть логи, очевидно, что не хватает отчетов о доставке. Опять же, вопрос в разделении функциональностей, и опять же мы не хотим отказываться от этого подхода, поскольку в целом считаем его правильным. Сервисы, которые работают со специальным протоколом, могут передать сообщение, полученное с платформы, и создать отчет о доставке. Соотнесение отчета о доставке с самим сообщением более сложная задача, чем может показаться. Ясно, что это работа для платформы. Тем не менее, мы хотим, чтобы сервис, который получает отчет о доставке, создавал логи – и это задача сервиса. Однако на момент получения отчета о доставке информация о том, с каким сообщением его нужно соотнести (если вообще получится соотноести!), отсутствует, и сервис, таким образом, не знает, какой UUID прикрепить к логу.

Соотнесение связанных сообщений

Как мы говорили чуть выше, некоторые сообщения связаны. Обычно это длинные SMS сообщения, которые передаются отдельно, но обрабатываются и, в итоге, отображаются на телефоне как одно сообщение. Иногда необходимо их разъединять и соединять, поэтому у нас получаются связи один ко многим, многие к одному и многие ко многим. Было бы удобно иметь журналы сообщений для таких связанных сообщений, потому что часто их нужно учитывать как одно целое. Проблема в данном случае в том, что только внутренняя платформа знает о связи между этими частями сообщений и именно она производит соединение и разъединение. Сервисы, осуществляющие внешние связи, про  связи между частями сообщений не знают ничего. Они обрабатывают части сообщения отдельно и для каждой части имеют свой UUID.

Другой источник связей – это одновременная обработка множества сообщений. Клиент может просто отправить несколько сообщений в одном HTTP-запросе. Сервис обработки HTTP, проанализирует запрос и отправит на платформу несколько сообщений, но в журнале сообщений отразит  только один запрос  и один ответ. В данном случае будет создан один UUID, который будет привязан к HTTP запросу и новые UUID для каждого сообщения, созданного этим запросом.

Графовая база данных в помощь

Очевидно, что нам необходимо где-то хранить связи между различными UUID. У нас должна быть возможность извлекать все связанные логи, когда мы запрашиваем логи по какому-либо сообщению. Давайте представим себе более сложную, но не редкую связь. У нас есть одна отправка от клиента (UUID-100), содержащая два длинных сообщения (UUID-200 и UUID-210), которые в дальнейшем разбиваются на еще большее количество частей и передаются оператору (UUID-201, UUID-202, UUID-211, UUID-212). После этого происходит единая доставка  множества частей сообщений, содержащая отчеты о доставке для каждой из доставленных частей (UUID-300). 

Неудивительно, что это граф. Безусловно, можно приложить существенные усилия и хранить информацию в реляционной базе данных или трансформировать в формат JSON для документальной базы данных, но сегодня у нас есть графовые базы данных. Давайте использовать правильные инструменты для решения задач! В любой момент, когда появляются новые связи или происходит разделение или соединение, мы добавляем небольшой граф в графовую базу данных. Мы выбираем Neo4j, поскольку у них отличный маркетинг – они дружелюбны на конференциях, раздают бесплатные книги и футболки. Да, и программное обеспечение у них неплохое. Вам необходимо будет заплатить за кластеризацию и хеширование, но для логов подойдут и бесплатные версии. Только одна база Neo4j на машине с 16-ти битным процессором обрабатывает  100 млн узлов с 150 млн связей, занимая всего 15Гб памяти. Проблема с Neo4j заключается лишь в том, что удаление узлов не освобождает память или место на диске, поэтому вам придется время от времени чистить память.

Теперь, когда нам нужно осуществить выборку логов для сообщения, мы используем UUID сообщения, подтягиваем все связанные UUID через Neo4j и далее выгружаем все журналы сообщений из Graylog  в соответствии с их UUID. Важно, что извлекаются только связанные UUID, а не вся база. Например, если мы хотим получить коммуникационные логи для сообщения с UUID-200, нам нужно только извлечь записи логов для UUID-100, UUID-200, UUID-201, UUID-202 и UUID-300. А ветка UUID-21x уже не нужна. Идея в использовании разных соответствий при связывании множеств и соединении частей сообщений. Тогда простой Neo4j cypher query используется для извлечения соответствующих UUID.

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

(Автор: Milan Mimica, разработчик / Руководитель Рабочей Группы)