Перейти к содержанию

Архитектура

Общая схема

flowchart TB
    visitor([Посетитель]) -->|HTTPS| traefik

    subgraph node["Узел: Ubuntu 24.04, 4 ядра, 4 ГБ памяти, k3s"]
        traefik["Traefik<br/>вход, TLS, ограничение частоты"]

        subgraph ns_app["Пространство metroskop"]
            web["web<br/>Nginx + собранный React"]
            app["metroskop<br/>Spring Boot, 2 реплики"]
            pg[("PostgreSQL 16<br/>обращения жителей")]
            load["loadgen<br/>синтетическая нагрузка"]
        end

        subgraph ns_obs["Пространство observability"]
            prom["Prometheus<br/>метрики и правила"]
            graf["Grafana<br/>дашборды"]
            loki["Loki<br/>логи"]
            tempo["Tempo<br/>трейсы"]
            alloy["Alloy<br/>сбор логов"]
            am["Alertmanager"]
            adapter["prometheus-adapter<br/>прикладная метрика для HPA"]
        end

        subgraph ns_del["Доставка"]
            rollouts["Argo Rollouts<br/>канарейка и откат"]
            cm["cert-manager<br/>Let's Encrypt"]
        end
    end

    traefik --> web
    traefik --> app
    traefik --> graf
    traefik --> prom
    traefik --> am

    app --> pg
    app -->|OTLP| tempo
    load -->|запросы| app

    prom -->|срез метрик| app
    prom -->|срез метрик| traefik
    prom --> am
    alloy -->|поток логов| loki
    graf --> prom
    graf --> loki
    graf --> tempo
    adapter --> prom
    rollouts -->|анализ канарейки| prom
    cm -.->|сертификаты| traefik

Как рождается кадр карты

  1. Планировщик приложения раз в секунду просит TrainService рассчитать состояние сети. Для каждой линии берётся её длина, время хода по перегонам и интервал выпуска, зависящий от времени суток.
  2. Положение каждого состава — чистая функция от модельного времени: одна и та же секунда всегда даёт одну и ту же картинку. Это важно и для тестов, и для того, чтобы две реплики показывали одно и то же.
  3. Кадр сериализуется один раз и рассылается всем подписчикам потока событий. Сериализация на каждого подписчика была бы самой дорогой операцией в сервисе.
  4. Браузер получает кадр и до следующего плавно доводит составы между двумя положениями. Поэтому движение выглядит непрерывным при одном кадре в секунду от сервера.

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

Почему такой набор технологий

Java 21 и виртуальные потоки. Поток событий — это сотни соединений, которые почти всё время ждут. На платформенных потоках каждое соединение стоило бы мегабайта стека; на виртуальных — сотни байт. Приложение живёт в лимите 512 МБ памяти и держит расчёт четырёхсот составов в секунду.

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

Canvas 2D, а не WebGL. Четыреста точек и триста сегментов линий — задача, которую двумерное полотно решает за две миллисекунды на кадр. WebGL добавил бы шейдеры и сложность там, где нечего оптимизировать.

Traefik. Он уже входит в k3s, умеет ограничивать частоту запросов и проставлять заголовки безопасности через свои промежуточные обработчики, и его метрики сразу попадают в Prometheus.

Что чем управляется

Слой Инструмент Где лежит
Узел: подкачка, параметры ядра, файрвол, k3s Ansible ansible/
Платформа: пространства имён, cert-manager, стек наблюдаемости, Argo Rollouts Terraform + Helm terraform/
Приложение, база, публикация, правила, дашборды Kustomize deploy/
Справочник линий и станций Python + Overpass API data/

Разделение не случайное. Всё, что переживает пересоздание кластера, живёт в Ansible. Всё, что описывает платформу и требует управления версиями чартов, — в Terraform. Всё, что меняется вместе с кодом приложения, — в Kustomize, чтобы конвейер обновлял только образ и не трогал платформу.

Данные

Справочник линий и станций собирается из OpenStreetMap через Overpass API: скрипт data/build_dataset.py вытягивает отношения маршрутов, упорядочивает станции, считает расстояния и сохраняет metro.json. Файл попадает в образ приложения на этапе сборки, поэтому во время работы сервис не зависит ни от одной внешней системы.