Несколько месяцев назад мы выпустили Encore.ts — серверную среду с открытым исходным кодом для TypeScript.
Поскольку фреймворков уже существует множество, мы хотели поделиться некоторыми необычными дизайнерскими решениями, которые мы приняли, и тем, как они приводят к выдающимся показателям производительности.
Ранее мы публиковали тесты, показывающие, что Encore.ts в 9 раз быстрее, чем Express, и в 2 раза быстрее, чем Fastify.
На этот раз мы сравнили Encore.ts с ElysiaJS и Hono, двумя современными высокопроизводительными фреймворками TypeScript.
Мы протестировали каждую платформу как с проверкой схемы, так и без нее, используя TypeBox для проверки с ElsyiaJS и Hono, поскольку это встроенная библиотека проверки для этих фреймворков. (Encore.ts имеет собственную встроенную сквозную проверку типов.)
Для каждого теста мы брали лучший результат из пяти прогонов. Каждый запуск выполнялся путем выполнения как можно большего количества запросов с использованием 150 одновременных рабочих процессов, более 10 с. Генерация нагрузки выполнялась с помощью oha, инструмента HTTP-нагрузочного тестирования на базе Rust и Tokio.
Хватит разговоров, давайте посмотрим на цифры!
(Посмотрите код теста на GitHub.)
Помимо производительности, Encore.ts достигает этого, сохраняя при этом 100% совместимость с Node.js.
Как это возможно? В результате нашего тестирования мы выявили три основных источника производительности, каждый из которых связан с тем, как работает Encore.ts.
Node.js запускает код JavaScript с использованием однопоточного цикла событий. Несмотря на свою однопоточную природу, на практике он вполне масштабируем, поскольку использует неблокирующие операции ввода-вывода, а базовый движок JavaScript V8 (который также поддерживает Chrome) чрезвычайно оптимизирован.
Но знаете, что быстрее однопоточного цикла событий? Многопоточный.
Encore.ts состоит из двух частей:
TypeScript SDK, который вы используете при написании серверных частей с помощью Encore.ts.
Высокопроизводительная среда выполнения с многопоточным асинхронным циклом событий, написанная на Rust (с использованием Tokio и Hyper).
Encore Runtime обрабатывает все операции ввода-вывода, например принимает и обрабатывает входящие HTTP-запросы. Это работает как полностью независимый цикл событий, который использует столько потоков, сколько поддерживает базовое оборудование.
После того, как запрос полностью обработан и декодирован, он передается в цикл событий Node.js, а затем принимает ответ от обработчика API и записывает его обратно клиенту.
(Прежде чем вы это скажете: да, мы добавили цикл событий в ваш цикл событий, чтобы вы могли выполнять цикл событий во время цикла событий.)
Encore.ts, как следует из названия, с нуля разработан для TypeScript. Но на самом деле вы не можете запустить TypeScript: сначала его нужно скомпилировать в JavaScript, удалив всю информацию о типах. Это означает, что обеспечить безопасность типов во время выполнения гораздо сложнее, что затрудняет такие вещи, как проверка входящих запросов, что приводит к тому, что такие решения, как Zod, становятся популярными для определения схем API во время выполнения.
Encore.ts работает по-другому. С помощью Encore вы определяете типобезопасные API, используя собственные типы TypeScript:
import { api } from "encore.dev/api"; interface BlogPost { id: number; title: string; body: string; likes: number; } export const getBlogPost = api( { method: "GET", path: "/blog/:id", expose: true }, async ({ id }: { id: number }) => Promise{ // ... }, );
Затем Encore.ts анализирует исходный код, чтобы понять схему запроса и ответа, которую ожидает каждая конечная точка API, включая такие вещи, как заголовки HTTP, параметры запроса и т. д. Затем схемы обрабатываются, оптимизируются и сохраняются в виде файла Protobuf.
Когда среда выполнения Encore запускается, она считывает этот файл Protobuf и предварительно вычисляет декодер запроса и кодировщик ответа, оптимизированные для каждой конечной точки API, используя точное определение типа, которое ожидает каждая конечная точка API. Фактически, Encore.ts даже обрабатывает проверку запросов непосредственно в Rust, гарантируя, что недействительные запросы даже не затрагивают уровень JS, что смягчает многие атаки типа «отказ в обслуживании».
Понимание Encore схемы запроса также оказывается полезным с точки зрения производительности. Среды выполнения JavaScript, такие как Deno и Bun, используют архитектуру, аналогичную архитектуре среды выполнения Encore на основе Rust (на самом деле Deno также использует Rust Tokio Hyper), но им не хватает понимания Encore схемы запроса. В результате им приходится передавать необработанные HTTP-запросы однопоточному движку JavaScript для выполнения.
Encore.ts, с другой стороны, выполняет гораздо большую часть обработки запросов внутри Rust и передает только декодированные объекты запроса. Обрабатывая гораздо больше жизненного цикла запросов в многопоточном Rust, цикл событий JavaScript освобождается и позволяет сосредоточиться на выполнении бизнес-логики приложения, а не на анализе HTTP-запросов, что дает еще больший прирост производительности.
Внимательные читатели, возможно, заметили тенденцию: ключом к производительности является разгрузка как можно большего объема работы из однопоточного цикла событий JavaScript.
Мы уже рассмотрели, как Encore.ts перекладывает большую часть жизненного цикла запросов/ответов на Rust. Так что же еще делать?
Ну, серверные приложения похожи на бутерброды. У вас есть твердый верхний уровень, на котором вы обрабатываете входящие запросы. В центре у вас вкусные начинки (то есть, конечно, ваша бизнес-логика). Внизу у вас есть жесткий уровень доступа к данным, на котором вы запрашиваете базы данных, вызываете другие конечные точки API и так далее.
Мы ничего не можем поделать с бизнес-логикой — в конце концов, мы хотим написать ее на TypeScript! — но нет особого смысла в том, чтобы все операции доступа к данным перегружали наш цикл событий JS. Если бы мы перенесли их в Rust, мы бы дополнительно освободили цикл событий, чтобы иметь возможность сосредоточиться на выполнении кода нашего приложения.
И вот что мы сделали.
С помощью Encore.ts вы можете объявлять ресурсы инфраструктуры непосредственно в исходном коде.
Например, чтобы определить тему Pub/Sub:
import { Topic } from "encore.dev/pubsub"; interface UserSignupEvent { userID: string; email: string; } export const UserSignups = new Topic("user-signups", { deliveryGuarantee: "at-least-once", }); // To publish: await UserSignups.publish({ userID: "123", email: "[email protected]" });
"Итак, какую технологию Pub/Sub он использует?"
— Все!
Среда выполнения Encore Rust включает реализации для большинства распространенных технологий Pub/Sub, включая AWS SQS SNS, GCP Pub/Sub и NSQ, а также запланировано еще больше (Kafka, NATS, Azure Service Bus и т. д.). Вы можете указать реализацию для каждого ресурса в конфигурации среды выполнения при загрузке приложения или позволить автоматизации Encore Cloud DevOps сделать это за вас.
Помимо Pub/Sub, Encore.ts включает в себя интеграцию инфраструктуры для баз данных PostgreSQL, секретов, заданий Cron и многого другого.
Все эти инфраструктурные интеграции реализованы в среде выполнения Encore.ts Rust.
Это означает, что как только вы вызываете .publish(), полезная нагрузка передается Rust, который позаботится о публикации сообщения, повторив попытку при необходимости и так далее. То же самое происходит с запросами к базе данных, подпиской на сообщения Pub/Sub и многим другим.
Конечным результатом является то, что с помощью Encore.ts практически вся небизнес-логика выгружается из цикла событий JS.
По сути, с Encore.ts вы получаете по-настоящему многопоточный бэкэнд «бесплатно», но при этом можете писать всю свою бизнес-логику на TypeScript.
Важна ли эта производительность, зависит от вашего варианта использования. Если вы создаете небольшой хобби-проект, он в основном академический. Но если вы переносите производственный сервер в облако, это может иметь довольно большое влияние.
Меньшая задержка напрямую влияет на удобство использования. Сформулируем очевидное: более быстрый бэкэнд означает более быстрый интерфейс, а значит, более счастливых пользователей.
Более высокая пропускная способность означает, что вы можете обслуживать такое же количество пользователей с меньшим количеством серверов, что напрямую соответствует более низким счетам за облако. Или, наоборот, вы можете обслуживать больше пользователей с тем же количеством серверов, гарантируя дальнейшее масштабирование без возникновения узких мест в производительности.
Хотя мы предвзяты, мы считаем, что Encore предлагает отличное, лучшее в мире решение для создания высокопроизводительных бэкэндов на TypeScript. Он быстрый, типобезопасный и совместим со всей экосистемой Node.js.
И все это с открытым исходным кодом, так что вы можете проверить код и внести свой вклад на GitHub.
Или просто попробуйте и дайте нам знать, что вы думаете!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3