Файл: Операции, производимые с данными (Обзор существующих решений операций, производимые с данными).pdf
Добавлен: 29.02.2024
Просмотров: 36
Скачиваний: 0
СОДЕРЖАНИЕ
Глава 1. Обзор существующих решений операций, производимые с данными
Глава 2. Описание работы с данными
2.1 Функциональность приложения с точки зрения пользователя
2.2 Используемые технологии и детали реализации web-приложения
2.3 Общие элементы кастомизации интерфейса
2.4 Документация по использованию интерфейса с произвольными данными
Под стеками/очередями внутренних состояний располагается линии времени, каждая из которых отображает отдельную ноду. Между линиями происходит взаимодействия через сообщения. Сообщения каждой ноды имеют свой цвет. Отдельные типы сообщений могут выделяться и отображаться специфической линией: пунктирной (разной длины), точка-тире. Типы специфических сообщений также зависят от алгоритма.
Если сообщение доставлено (передача прошла успешно), то оно завершается на node-получателе точкой цвета линии сообщения. Если сообщение по каким-то причинам оборвалось, то оно завершается «крестиком» между node-отправителем и node-получателем.
При наведении на линию сообщения можно получить дополнительную информацию об этом сообщении. Она появляется над линией времени.
На линии времени есть засечки — это штампы, соответствующие изменениям состояния внутреннего стека/очереди узла. При наведении на засечку, запись в стеке/очереди будет подсвечена синим цветом, исключая случай, когда запись уже удалена.
При работающем тесте можно нажать кнопку “PAUSE” (на месте кнопки “PLAY”), и тест будет приостановлен.
При проигрывании и после остановки можно воспользоваться ScrollBar и прокрутить тест до нужного момента времени.
Если пользователю не требуется информация о сообщениях определенного процесса, и она мешает просмотру других деталей, он может «выключить» сообщения от этого процесса, воспользовавшись checkbox-ами под ScrollBar.
Справа от стеков, внутри красной окружности отображается «общее состояние». Оно также специфично для каждого алгоритма.
Над окружностью продемонстрирована информация о текущем времени, времени начала теста на сервере и времени, которое сейчас проигрывается в тесте алгоритма.
2.2 Используемые технологии и детали реализации web-приложения
Приложение написано на языке программирования JavaScript и работает на базе программной платформы Node.js [10]. В качестве основного Фреймворка, при разработке используется React [11].
В процессе выбора технологий стоял вопрос об использовании React, Angular.js [12], Angular 2 [13]. Вариант с Angular.js не подходил из-за постепенного устаревания технологии, медленной скорости работы и вытеснения его Angular 2, хотя существует достаточное количество активно поддерживаемых web-приложений, написанных на первом.
Angular 2 современен и активно разрабатывается. Сейчас идет активный переход на TypeScript в качестве основного языка разработки, и это повышает качество и читаемость кода. Однако, это создает некоторые трудности: при желании наследовать некоторую JavaScript-библиотеку, разработчик должен собственноручно прописать типизацию, иногда это занимает много времени и порождает сложности.
React — это Фреймворк, «живущий» за счет JSX, что очень удобно, в условиях разработки приложения с множеством динамических графических элементов. React имеет структуру кода, напоминающую объектно-ориентированное программирование (ООП), что повышает читаемость, при правильном оформлении. Существует множество готовых решений, которые можно использовать «из коробки», упрощающих жизнь разработчику.
Приложение структурировано по компонентам. Каждой компоненте соответствует одноименный файл с кодом класса. Зависимость между классами — иерархическая система. В корне существует одна главная компонента — Main, на уровень ниже две другие — ConfigurationSetup и App. На третьем уровне находятся «сыновья» App - NodeState, SharedState, Actions, Logs. Существует также отдельный Utils файл со вспомогательными функциями и компонента для реализации логики checkbox-а.
Рисунок 4. Иерархия компонент системы.
Поскольку приложение является SPA, Main — компонента, регулирующая, какую информацию в данный момент нужно скрыть, а какую отобразить. Логика внутри компоненты такова: если конфигурация алгоритма, который нужно воспроизвести, установлена, то отображается App компонента, иначе — ConfigurationSetup. Основные методы — SetConfig и RemoveConfig. Названия полностью соответствуют логике, которую реализовывают функции. Важное замечание: функции работы с конфигурацией никогда не вызываются внутри самой компоненты, они передаются параметрами в дочерние классы и вызываются оттуда исходя из логики, которая будет описана ниже.
Компонента ConfigurationSetup это конструктор конфигурации для запуска теста. Пользователь заполняет необходимые поля формы в соответствии с выбранным алгоритмом. Затем происходит обработка поступивших данных и формирование самого config в формате JSON, по структуре, указанной ниже.
{
"nodes": { // конфигурация нод
"0": { // id ноды, которая конфигурируется
"tunnels": [{ // каналы ноды 0
"id": 1, // нода, с которой конфигурируется канал
"host": "localhost",
"in": 11000, // порт для данных от 1 к 0 (на самом деле до proxy)
"out": 11001 // порт для данных от 0 к 1 (на самом деле от proxy)
}..]
}...
},
"PAC": { // конфигурация для backend-proxy
"nodes": { // id ноды, которая конфигурируется
"0": {
"1": {
"in": 11001, // порт для данных от 1 к 0
"out": 10010 // порт для данных от 0 к 1
}
}...
},
"ctrl_port": 9020, // порт для “тестировщика”
"ui_port": 9040, // порт для общения frontend и backend частей
"alg_support_port": 9060, // порт для сбора данных в json-отчет, который приходит от сервера при запросе данных из ui_port
"host": "localhost" // адрес тестирующего алгоритма
},
"algorithm": {
"type": "Lamport Mutex", // тип алгоритма
"params": {
"command_host": "localhost", // хост для отправки команд при «ручном режиме»
"command_port": 10002, // порт для отправки команд при «ручном режиме»
"manual":false // идентификатор, является ли запуск полностью «ручным» (если нет, начнется проигрывание тестового сценария)
}
}
}
После обработки конфигурации, вызывается метод SetConfig и config отправляется по WebSocket на легковесный сервер на python, чья работа заключается в запуске теста с заданной конфигурацией.
При установке конфигурации срабатывает конструктор компоненты App. Это компонента, регулирующая общение с тестирующим сервером и объединяющая все дочерние компоненты для визуализации.
Общение с тестирующим сервером реализовано передачей данных через Websocket. Данные запрашиваются UI порционно: UI указывает время, начиная с которого он ожидает получить данные, и сервер возвращает все данные, которые он имеет, начиная с этого момента времени. Каждый пакет данных помечается идентификатором. Дочерние компоненты по данному идентификатору понимает, пришли ли новые данные, и нужно ли их обрабатывать или нет.
Рисунок 5. Схема получения данных от сервера.
Описание некоторых методов класса:
СonnectSocket
Метод для соединения с тестирующим сервером. В Websocket передаются хост и порт из конфигурации, и происходит соединение, по которому будет происходить общение frontend и backend частей в дальнейшем. Важно заметить, что сокет не пробрасывается в случае, если алгоритм Custom и переданы mock-данные (с которыми будет происходить запуск, вместо запроса их от сервера).
RequestData
Метод для запроса данных от сервера. При запросе через Websocket передается timestamp, начиная с которого нужно возвращать действия, изменения состояния и т.д.
HandleData
Метод, выполняющий обработку при получении данных от сервера. Из приходящего JSON в соответствии с API[4] выделяются ключевая информация и сохраняется в локальный state для дальнейшей передачи дочерним компонентам.
ProcessStates
Метод для обработки приходящих в данных внутренних состояний узлов. Данные приходят в формате «время — изменение состояния», а не «время — слепок состояния». Из-за этого требуется обработка данных по получению, так как пользователю понятнее видеть слепок состояния для currentTime.
При обработке, для каждого изменения состояния, пришедшего в порции данных, проверяется к какой группе оно относится и в соответствии с этим меняет внутреннюю очередь или общее состояние.
Также результат работы используется компонентой Actions для отражения на линии времени засечек события «внутреннее состояние поменялось».
Изначально обработка состояний процессов была написана отдельно для NodeState и SharedState компонент и реализовывала разную логику обработки одних и тех же данных. После видимых «залипаний» интерфейса в этом месте при проигрывании алгоритма Raft (в этом алгоритме приходит большое количество записей в node_state), обработка state-а была перенесена в App. Сравнительных замеров времени произведено не было, но «залипания» интерфейса при Raft ушли.
DoEveryInterval
Вспомогательная функция, вызываемая каждую секунду (время конфигурируется). Внутри нее происходит изменение текущего времени (currentTime), которое влияет на то, какой момент времени исполнения алгоритма сейчас видит пользователь. Также происходит проверка того, что данных достаточно. Класс хранит в себе переменную timestampTo, который приходит от сервера вместе со всеми данными — timestamp до которого известно происходящее между нодами взаимодействие в данной порции данных.
Рисунок 6. Временная схема запроса данных.
ScrollTime
Метод-реакция на прокрутку ScrollBar. В зависимости от отступов от границ ScrollBar, метод высчитывает время, до которого прокрутил пользователь, и устанавливает currentTime.
HandleVisibilityChange
Функция остановки проигрывания, если пользователь свернул вкладку браузера.
Первой дочерней компонентой App является NodeState. Эта компонента отвечает за отображение стеков/очередей состояния узлов. Она почти не делает внутри себя никакую обработку, только визуализирует уже заранее пред обработанные на уровень выше данные внутри ProcessStates. Все методы этого класса касаются только визуальных эффектов, за исключением обработки кликов вызова «ручного режима».
GetPopupContent
Функция формирует view компоненты Popup и определяет обработчики событий. При клике по кнопкам внутри popup срабатывает вызов метода MakeInteractiveRequest компоненты App. Этот метод отправляет запрос backend-серверу через Websocket. Общение при «ручном режиме» происходит с тем же хостом, но по другому порту. Порт настраивается при создании конфигурации полем “Port for Interactive commands”. Данное поле в конфигурации определено только для данных Lamport Mutex и Raft, так как не имеет смысла для Paxos.
Следующая дочерняя компонента — SharedState, отображающая текущее состояние некого общего состояния системы, зависящего от алгоритма. Внутри данной компоненты основной метод — ProcessSharedState, где реализована логика по определению состояния системы из уже обработанных ранее локальных очередей внутри ProcessStates.
Actions — самая объемная по функционалу компонента и класс. В нем реализована вся логика по обработке и визуализации сообщений между нодами. Ниже приведены некоторые важные для интерфейса функции.
ProcessActions
Метод для обработки сообщений от узла к узлу в данных от сервера. В соответствии с API, для удобства формирования JSON с данными, действия приходят в виде map, где ключом является номер ноды, а значением — массив действий, которые были выполнены к данному моменту времени. Для отрисовки действий внутри Scalable Vector Graphic (SVG) удобным было выбрано хранение сообщений в виде array.
Для каждой ноды выбираются начатые раньше текущего времени события из пришедшего json. Каждое из них сохраняется в массив действий.
Важно заметить, что данные приходят от сервера порционно, из-за чего возникают ситуации, когда при запросе данных какое-то действие еще не было завершено, но было начато. В таких случаях сервер помечает время окончания действия 0, а на стороне UI такие события хранятся со временем окончания -1. При отправке следующей порции данных, если действие уже было завершено, то UI получает сообщение с тем же содержанием и идентификатором, но уже с проставленным корректным временем доставки сообщения. На стороне UI такие события обрабатываются и перезаписывают время доставки сообщения с указанным идентификатором.
После указанной выше обработки происходит вызов функции SetTimeOffset и пост-обработка для установки корректного времени «доставки» для всех сообщений.
Если доставка сообщения уже завершилось, то она помечается, как законченная и больше не обрабатывается[5]. Если сообщение на данный момент времени находится в процессе доставки, то его обрабатывает функция GetCurrentTo[6].