Request scheduling

Отрисовка глобусов, 3D-моделей, тайлов местности, спутниковых снимков и векторных данных требует постоянной загрузки большого количества ресурсов по сети. Одновременное выполнение сотен HTTP-запросов приводит к перегрузке браузера, росту задержек и ухудшению производительности приложения.

Для решения этой проблемы в CesiumJS реализован механизм Request Scheduling — централизованная система управления сетевыми запросами. Она контролирует:

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

Практически каждая подсистема движка использует планировщик запросов:

  • 3D Tiles;
  • Imagery Layers;
  • Terrain Providers;
  • GeoJSON;
  • KML;
  • CZML;
  • модели glTF;
  • векторные данные.

Архитектура системы запросов

В основе механизма находится класс:

Cesium.RequestScheduler

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

Упрощённая схема работы выглядит следующим образом:

Источник данных
       ↓
    Request
       ↓
RequestScheduler
       ↓
Очередь запросов
       ↓
Проверка лимитов
       ↓
Выполнение
       ↓
Получение данных

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

Вместо этого создаётся объект:

Cesium.Request

который передаётся в планировщик.


Объект Request

Каждый сетевой запрос описывается экземпляром класса:

const request = new Cesium.Request({
    url: url
});

На практике используется гораздо больше параметров.

Пример:

const request = new Cesium.Request({
    url: "tiles/15/1203/987.b3dm",
    throttle: true,
    throttleByServer: true,
    type: Cesium.RequestType.TILES3D
});

Основные свойства:

Свойство Назначение
url адрес ресурса
throttle участвует в ограничении количества запросов
throttleByServer учитывает лимиты сервера
priority числовой приоритет
priorityFunction функция вычисления приоритета
type тип запроса
serverKey ключ сервера

Ограничение количества запросов

Современные браузеры ограничивают число одновременных соединений с одним сервером.

Cesium дополнительно вводит собственные лимиты.

Глобальное ограничение:

Cesium.RequestScheduler.maximumRequests

По умолчанию:

console.log(
    Cesium.RequestScheduler.maximumRequests
);

Типичное значение:

50

Если количество активных запросов достигло лимита:

50 активных запросов
+
новый запрос
↓
помещается в очередь

Выполнение начнётся только после освобождения слота.


Ограничение запросов на сервер

Даже если глобальный лимит не исчерпан, один сервер может быть перегружен.

Для этого существует параметр:

Cesium.RequestScheduler.maximumRequestsPerServer

Пример:

console.log(
    Cesium.RequestScheduler.maximumRequestsPerServer
);

Обычно:

18

Сценарий:

server-a.com
18 активных запросов

server-b.com
3 активных запроса

Новый запрос к:

server-a.com

будет ожидать освобождения соединения, несмотря на наличие свободных глобальных слотов.


Приоритет запросов

Не все данные одинаково важны.

Например:

  • тайл перед камерой критически важен;
  • тайл за спиной камеры почти бесполезен;
  • дальние объекты можно загрузить позже.

Для этого используется система приоритетов.

Чем меньше значение:

priority

тем выше приоритет.

Пример:

const request = new Cesium.Request({
    priority: 1
});

Более низкий приоритет:

const request = new Cesium.Request({
    priority: 100
});

Очередь будет обслуживаться следующим образом:

priority 1
priority 2
priority 5
priority 10
priority 100

Динамический приоритет

Статический приоритет подходит не всегда.

Положение камеры постоянно меняется:

  • приближение;
  • удаление;
  • вращение;
  • изменение угла обзора.

Поэтому Cesium позволяет вычислять приоритет динамически.

Пример:

const request = new Cesium.Request({
    priorityFunction: function () {
        return distanceToCamera;
    }
});

При каждом обновлении очередь получает актуальное значение.

Чем ближе объект к камере:

меньше расстояние
↓
выше приоритет
↓
раньше загрузка

Типы запросов

Для оптимизации движок классифицирует запросы.

Используется перечисление:

Cesium.RequestType

Основные значения:

Cesium.RequestType.TERRAIN
Cesium.RequestType.IMAGERY
Cesium.RequestType.TILES3D
Cesium.RequestType.OTHER

Пример:

const request = new Cesium.Request({
    type: Cesium.RequestType.IMAGERY
});

Такая классификация помогает подсистемам корректно управлять загрузкой данных.


Механизм троттлинга

Троттлинг предотвращает неконтролируемое создание запросов.

Свойство:

throttle

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

Пример:

const request = new Cesium.Request({
    throttle: true
});

Если все слоты заняты:

запрос
↓
очередь
↓
ожидание

Если указать:

throttle: false

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

Пример:

const request = new Cesium.Request({
    throttle: false
});

Такой режим применяется редко и требует осторожности.


Ограничение по серверу

Свойство:

throttleByServer

активирует учёт лимитов конкретного хоста.

Пример:

const request = new Cesium.Request({
    throttleByServer: true
});

Логика работы:

server.com
↓
проверка числа соединений
↓
если лимит достигнут
↓
ожидание

Server Key

Для контроля нагрузки Cesium определяет уникальный идентификатор сервера.

Пример ключа:

assets.example.com:443

или

localhost:8080

Получить ключ можно следующим образом:

const key =
    Cesium.RequestScheduler.getServerKey(
        "https://assets.example.com"
    );

console.log(key);

Результат:

assets.example.com:443

Этот ключ используется внутренними структурами планировщика.


Очередь запросов

Когда лимиты исчерпаны, запрос помещается в очередь.

Упрощённая схема:

Активные запросы
─────────────────
1
2
3
...
50

Очередь
─────────────────
51
52
53
54
...

После завершения активного запроса:

освобождение слота
↓
извлечение запроса
↓
запуск выполнения

Очередь постоянно пересортировывается согласно приоритетам.


Отмена устаревших запросов

Во время навигации камера может быстро перемещаться.

Например:

континент
↓
страна
↓
город
↓
улица

Пока загружаются тайлы континента, пользователь уже рассматривает улицу.

Загрузка старых тайлов становится бессмысленной.

Cesium автоматически отменяет подобные запросы:

запрос больше не нужен
↓
удаление из очереди
↓
освобождение ресурсов

Это значительно уменьшает сетевую нагрузку.


Использование RequestScheduler.request

Низкоуровневый интерфейс выглядит следующим образом:

Cesium.RequestScheduler.request(
    request
);

Пример:

const request = new Cesium.Request({
    url: "data.json",
    throttle: true
});

const promise =
    Cesium.RequestScheduler.request(
        request
    );

Метод возвращает Promise.

promise.then(function(data) {
    console.log(data);
});

Взаимодействие с Resource

На практике большинство сетевых операций выполняется через класс:

Cesium.Resource

Он автоматически интегрирован с планировщиком запросов.

Пример:

const resource =
    new Cesium.Resource({
        url: "tileset.json"
    });

Загрузка:

resource.fetchJson();

Внутри будет использован механизм Request Scheduling.


Request Scheduling в 3D Tiles

Подсистема 3D Tiles наиболее активно использует планировщик.

При движении камеры движок постоянно определяет:

  • какие тайлы видимы;
  • какие скоро попадут в кадр;
  • какие больше не нужны.

Для каждого тайла вычисляется приоритет.

Упрощённая последовательность:

Камера
↓
Видимые тайлы
↓
Расчёт SSE
↓
Приоритет
↓
Очередь запросов
↓
Загрузка

Ближайшие тайлы получают преимущество перед удалёнными.


Request Scheduling для Terrain

Поставщики рельефа также используют очередь запросов.

Типичный сценарий:

камера движется
↓
нужны новые terrain tiles
↓
создание запросов
↓
планировщик
↓
получение данных высот

Благодаря этому отсутствует лавинообразная загрузка при быстром перемещении по карте.


Request Scheduling для Imagery

Спутниковые снимки могут состоять из миллионов тайлов.

При масштабировании:

zoom in
↓
старые тайлы устаревают
↓
новые получают высокий приоритет
↓
загрузка только нужных изображений

Без планировщика браузер легко мог бы сформировать сотни лишних запросов.


Мониторинг активных запросов

Количество активных запросов доступно через внутренние структуры движка.

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

Chrome DevTools
→ Network

или

Firefox Developer Tools
→ Network

Здесь можно наблюдать:

  • число соединений;
  • время ожидания;
  • очередь запросов;
  • отменённые запросы;
  • скорость загрузки тайлов.

Настройка лимитов

При необходимости лимиты можно изменить.

Увеличение общего числа запросов:

Cesium.RequestScheduler.maximumRequests =
    100;

Изменение лимита на сервер:

Cesium.RequestScheduler.maximumRequestsPerServer =
    30;

Подобные настройки требуют тестирования.

Слишком высокие значения способны привести к:

  • перегрузке сети;
  • росту использования памяти;
  • увеличению нагрузки на сервер;
  • ухудшению отзывчивости интерфейса.

Практические рекомендации

Не отключать троттлинг без необходимости

throttle: true

остается оптимальным вариантом практически для всех сценариев.

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

Особенно для больших наборов пространственных данных.

priorityFunction

позволяет концентрировать ресурсы на области текущего обзора.

Следить за количеством серверов

Распределение данных между несколькими доменами помогает избежать локальных ограничений соединений.

Минимизировать объём ресурсов

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

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

Классы:

Cesium.Resource
Cesium.Cesium3DTileset
Cesium.ImageryLayer
Cesium.TerrainProvider

уже интегрированы с системой Request Scheduling и автоматически используют оптимальные алгоритмы управления сетевыми запросами.