Практика рендеринга ThinkJS и Sprite.js на стороне сервера

Node.js внешний интерфейс JavaScript ThinkJS Canvas
Практика рендеринга ThinkJS и Sprite.js на стороне сервера

Примечание редактора: сегодня мы пригласили @Arima поделиться с нами своим опытом использования сервера для рендеринга анимации на большом экране при работе над проектом визуализации данных на большом экране. Когда дело доходит до рендеринга на стороне сервера, все обычно думают о SSR Vue или изоморфизме React, но анимацию также можно рендерить на стороне сервера! Итак, давайте быстро введем текст, чтобы посмотреть, как это реализовано~


представлять

ThinkJS — это серверная среда разработки корпоративного уровня, основанная на koa@2.0.В дополнение к базовым службам HTTP в этом проекте также используются временные задачи и функции веб-сокетов.

Sprite.js — это кросс-платформенная библиотека объектных моделей для 2D-рисования, которая поддерживает многотерминальное графическое рисование и реализацию анимации для Web, Node, настольных приложений и апплетов WeChat. Sprite.js использует node-canvas для рендеринга на стороне сервера, а это значит, что мы можем использовать Sprite.js в среде node и сохранять нарисованную графику как png или сохранять анимацию как gif. Следующие функции в основном используются в проекте в этой статье:

  • Сцена: sprite.js реализует управление слоями, создавая сцены сцен;
  • Слой: каждый слой представляет собой инкапсулированный 2D-объект холста;
  • Спрайт: визуализируемый объект с блочной моделью. Существует четыре типа спрайтов, поддерживаемых sprite.js по умолчанию, а именно Sprite, Label, Path и Group, среди которых Sprite является самым основным спрайтом;

🤔️ Вопросы

Зачем выполнять рендеринг холста на стороне сервера?

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

  • В качестве клиента веб-сокета сервер получает данные из восходящего потока веб-сокета, использует sprite.js для рисования изображений, делает снимки с помощью запланированных задач ThinkJS, загружает изображения в CDN и сохраняет URL-адрес;
  • В то же время сервер также действует как сервер веб-сокетов, фильтруя восходящие данные и отправляя их на внешний интерфейс, а внешний интерфейс будет отображать полученные данные на холсте в реальном времени через sprite.js.
  • Когда внешний интерфейс возвращается к исторической ситуации, ему необходимо запросить сервер для получения исторического моментального снимка. Сервер объединяет снимки в течение времени запроса в один, загружает его в CDN, возвращает URL-адрес интерфейсу и рисует его на холсте интерфейсом.

👀 Восхождение на яму перед разработкой

В процессе реализации этого решения я облазил много ям, самую большую яму вырыл node-canvas, по пути на яму я один раз повесил сервер (к счастью, это был просто докер-контейнер).

Установить узел-холст

node-canvas — это реализация холста, использующая поддерживаемую Cairo среду Node.js, откройте страницу со списком разработчиков, и вы увидите знакомое имя TJ Holowaychuk. Эти проблемы, которые встречались до сих пор, также были обнаружены в процессе смены серверов много раз, я надеюсь, что все обратят внимание, чтобы избежать ям и слез в будущем.

Отсутствуют предварительно скомпилированные двоичные файлы

node-canvas используется только на сервере node, поэтому он не добавляется в зависимости sprite.js, нам нужно выполнить его вручнуюnpm i canvas@nextПри установке в проект по умолчанию будет установлена ​​последняя версия.Во время установки будет принято решение о загрузке соответствующего бинарного файла в предварительно скомпилированном проекте в соответствии с архитектурой системы.Если вы столкнулись с ошибкой, показанной на рисунке 1, есть две решения:

  1. Скомпилируйте и установите node-canvas, в официальной документации четко указаны зависимости, необходимые для компиляции разных операционных систем;
  2. Установите самую последнюю версию с предварительно скомпилированными двоичными файлами, в настоящее время canvas@2.0.0-alpha.14;

图1

GLIBC_2.14 отсутствует

Вы все еще можете столкнуться с этой проблемой после решения предыдущей проблемы

Error: /lib64/libc.so.6: version `GLIBC_2.14` not found

图2

Это означает, что нетGLIBC_2.14библиотека, сначала поймите, что такое GLIBC:

GLIBC — это библиотека libc, выпущенная GNU, библиотека времени выполнения C. GLIBC — это API самого низкого уровня в системах Linux, и почти любая другая библиотека времени выполнения зависит от GLIBC. Помимо инкапсуляции системных служб, предоставляемых операционной системой Linux, сам GLIBC также обеспечивает реализацию многих других необходимых функциональных служб.

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

Чтобы проверить, поддерживает ли ядро ​​системы GLIBC_2.14, вы можете использовать эту команду

strings /lib64/libc.so.6 | grep GLIBC

Если в результатах действительно нет ключевого слова GLIBC_2.14, есть два способа решить проблему:

  1. Добавьте исходный код GLIBC в используемую вами операционную систему, а затем установите соответствующую версию GLIBC;
  2. Выберите операционную систему с версией GLIBC >= 2.14, например CentOS 7.

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

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

Поэтому лучше всего использовать поддерживаемую операционную систему напрямую.

отсутствует файл шрифта

После установки node-canvas вы можете использовать следующий код для тестирования. Если текст на выходном изображении отображается в виде прямоугольника, как показано на рисунке ниже, это означает, что в используемой вами системе отсутствует файл шрифта. Бывает, что у вас возникает необходимость снова отрендерить текст, и вам нужно решить эту проблему.

图3

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

// label.js
const {Scene,Label} = require('spritejs');
const fs = require('fs');
const writeFileAsync = think.promisify(fs.writeFile, fs);

(function () {
  // 创建scene和layer
  const scene = new Scene('#paper', {resolution: [1200, 1200]});
  const fglayer = scene.layer('fglayer');
  
  // 创建label并设置属性
  const text1 = new Label('Hello World !');
  text1.attr({
    anchor: [0.5, 0.5],
    pos: [600, 600],
    font: 'bold 48px Arial',
    color: '#ffdc45',
  });
  
  // 将label添加到layer上,并将将canvas存为图片
  fglayer.append(text1);
  await fglayer.prepareRender();
  await writeFileAsync('spritejs.png', fglayer.canvas.toBuffer());
}());

🌵 Рендеринг на стороне сервера

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

вывод изображения

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

// 重写 clearContext 方法确保 sprite,label,path,等元素移除后保留图像
layer.clearContext = () => {}

// 通过数据生成新的 sprite 元素
const sprite = drawSomething(data);
// 绘制到 layer 上
layer.append(sprite);
// 确保 sprite 绘制到 layer 上后
await layer.prepareRender();
// 将 sprite 元素移除,因为重写了 clearContext 方法,移除后图像仍在 layer 上
sprite.remove();

// 如果要清空 layer
const {width, height} = layer.context.canvas;
layer.context.clearRect(0, 0, width, height);

В объекте сцены sprite.js есть метод моментального снимка. Он делает снимок экрана текущей сцены и возвращает объект холста. Мы можем вызвать метод toBuffer для объекта холста, чтобы получить двоичные данные изображения, а затем использовать синхронизацию предоставляется модулем fs в node.js Напишите метод для создания изображения png.

const canvas = await scene.snapshot()
await fs.writeFile('snap.png', canvas.toBuffer())

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

async function snapshot(layer) {
  await layer.prepareRender();
  return layer.canvas.toBuffer();
};

Когда количество снэпшотов велико, вам необходимо регулярно загружать снэпшоты в CDN или на отдельный файловый сервер, а затем сохранять URL-адрес изображения в базе данных. В этом процессе используются временные задачи ThinkJS, которые можно найти вsrc/config/crontab.jsДобавьте следующую конфигурацию, а затем напишите соответствующий метод обработки. Если вы хотите, чтобы запланированная задача выполнялась в определенное время, например, задача выполняется в целое число, кратное 5 минутам, вы можете установить более мелкозернистый таймер, а затем судить о методе обработки. это не кратно 5 минутам, оно не будет выполнено.

// src/config/crontab.js
module.exports = [
  {
    enable: true,
    interval: '1m', // 每1分钟执行一次
    handle: 'crontab/snapshot'
  }
];

// src/controller/crontab.js
module.exports = class extends think.Controller {
  async snapshotAction() {
    // 拒绝非定时任务启动
    if (!this.isCli) return;
    const now = new Date();
    // 如果不是 5 分钟的整数倍,则不执行任务
    if (now.Minutes() % 5) {
      return;
    }
    // 下面实现拍快照 -> 上传 cdn -> 存数据库的逻辑
    // ...
  }
}

Обработка изображения

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

const spritejs = require('spritejs');
const fs = require('fs');
const writeFileAsync = think.promisify(fs.writeFile, fs);

(async function() {
  const {Scene, Sprite} = spritejs;
  const scene = new Scene('#paper', {resolution: [1200, 1200]});
  // 预加载图片
  await scene.preload(
    'https://p3.ssl.qhimg.com/t01ccaee34d3f92a10c.png',
    'https://p2.ssl.qhimg.com/t01eb096408038e7496.png'
  );
  // 是否代理DOM 事件,如果这个参数设置为false,那么这个 Layer 将不处理DOM事件
  // 可以提升性能
  const layer = scene.layer('fglayer', {
    handleEvent: false
  });
  const sprite = new Sprite();
  // 在 sprite 元素上添加多个 texture
  // http://spritejs.org/#/zh-cn/doc/attribute?id=textures
  sprite.attr({
    textures: [
      {
        src: 'https://p3.ssl.qhimg.com/t01ccaee34d3f92a10c.png'
      },
      {
        src: 'https://p2.ssl.qhimg.com/t01eb096408038e7496.png'
      }
    ]
  });
  // 添加到 layer 上
  layer.append(sprite);
  const buffer = await snapshot(layer);
  await writeFileAsync('test.png', buffer);
  layer.remove(sprite);
})();

websocket

ThinkJS использует многопроцессорную модель мастер/рабочие.Если проект не использует веб-сокет, мастер будет использовать алгоритм Round Robin для передачи запроса рабочим для обработки, что в основном обеспечивает балансировку нагрузки. Если связь через веб-сокет требуется на передней и задней частях проекта, ее необходимо настроить в ThinkJS.stickyCluster: true, после добавления этой конфигурации мастер будет выполнять хеширование IP-адресов, что гарантирует, что запросы от одного и того же клиента будут обрабатываться одним и тем же исполнителем, таким образом успешно устанавливая связь через веб-сокет, что снижает производительность. пожалуйста, посмотри«Разговор о многопроцессорной модели ThinkJS».

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

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

📓 Резюме

Это первый раз, когда мы делаем проект рендеринга большого экрана на стороне сервера, который сочетает в себе ThinkJS и Sprite.js, Это новая попытка для нас, но технология решает проблему того, как это реализовать, и какой дисплей осуществленный? А зачем так показывать? Это по-прежнему проблема, которую необходимо учитывать в первую очередь в процессе визуального представления.