Битва фреймворков: сравнение высокопроизводительных HTTP-библиотек
Мир веб-разработки предлагает бесконечное количество вариантов HTTP-фреймворков для разных языков программирования. Но как разработчикам понять, какие из них обеспечивают действительно высокую производительность? Под катом команда блога CodeReliant* проводит прямое сравнение некоторых из лучших претендентов на быстродействие. Рассматривает популярные варианты на Javascript/Bun, Java, C#, Go и Rust, проводит бенчмаркинг, оценивает их пропускную способность и время отклика при тестировании.
Выбранные для сравнительного анализа фреймворки имеют репутацию высокопроизводительных, — но посмотрим, как это понятие реализуется в разных стеках.
*Обращаем ваше внимание, что позиция автора может не всегда совпадать с мнением МойОфис.
Вот пять участников тестирования:
Java 21 + vertex 4.4.6
JS/Bun 1.0.6 + elysiajs 0.7
C# 12 + dotnet/ASP.NET 8.0 RC2
Go 1.21.3 + fiber 2.49.2
Rust 1.73.0 + actix-web 4
С помощью стресс-тестирования мы получим достоверные данные об их возможностях. Наше сравнение "лоб в лоб" фокусируется на скорости и масштабируемости с нулевой конфигурацией, чтобы вы могли выбрать правильный фреймворк для своего следующего веб-проекта с высоким трафиком. Если вы хотите ускорить работу API, создать системы с низкой задержкой или выжать максимум из ваших серверов, это сравнение поможет вам выбрать высокопроизводительный HTTP-фреймворк, подходящий для вашего технологического стека.
Окружение и тестовая система
Для тестирования мы запустим минимальную версию HTTP-сервера, возвращающую ответ hello world при запросе /
.
Запустим сервер на машине Hetzner:
ОС: Ubuntu 22.04.3 LTS
Ядро: 5.15.0-86-generic
Архитектура: ARM aarch64
Ресурсы: 4 vCPU & 8 GB RAM
Генератор клиентской нагрузки будет находиться на отдельной машине с аналогичными характеристиками, но большими ресурсами — 8 vCPU и 16 GB RAM.
Java и Vertex
Сгенерируем стартовый проект vertex, используя сайт. Также мы будем использовать java 21-oracle
, которую только что установили с помощью sdkman.
package io.codereliant.performance;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
vertx.createHttpServer().requestHandler(req -> {
req.response()
.putHeader("content-type", "text/plain")
.end("Hello World!");
}).listen(80, http -> {
if (http.succeeded()) {
startPromise.complete();
System.out.println("HTTP server started on port 80");
} else {
startPromise.fail(http.cause());
}
});
}
}
Просто изменив порт на 80
вместо 8888
и изменив возвращаемую строку на Hello World
вместо текста по умолчанию, мы соберем наш сервер с помощью mvn package
и запустим его с помощью java -jar target/performance-1.0.0-SNAPSHOT-fat.jar
.
java -jar target/performance-1.0.0-SNAPSHOT-fat.jar
HTTP server started on port 80
Oct 15, 2023 10:56:15 PM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
INFO: Succeeded in deploying verticle
Bun и Elysia
Elysia упрощает создание проекта: сперва вызываем bun create elysia perf-app
, а затем — bun run index.ts
.
Для справки мы используем Bun 1.0.6
и Elysia 0.7.0.
import { Elysia } from "elysia";
const app = new Elysia().get("/", () => "Hello World").listen(80);
console.log(
`? Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
C# и Dotnet
Для C# 12 и dotnet 8.0 RC мы будем использовать ASP.NET Core Minimal APIs.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
dotnet new web -o perf-app
cd perf-app
dotnet build -c Release
ASPNETCORE_URLS="http://*:80/" ./bin/Release/net8.0/dotnet-app
Golang и Fiber
Fiber — это основанный на Fasthttp веб-фреймворк для Go, который был разработан для обеспечения высокой производительности.
mkdir go-app
cd go-app
go mod init github.com/codereliant/go-app
go get -u github.com/gofiber/fiber/v2
touch main.go
Затем в файле main.go мы используем пример hello world с лендинга https://gofiber.io/.
package main
import (
"log"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/", func (c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
log.Fatal(app.Listen(":80"))
}
Для сборки и запуска примера можно использовать эти две строки:
go build main.go
./main
Rust & Actix-web
Информация с главного сайта actix-web:
Actix Web — это мощный, практичный и чрезвычайно быстрый веб-фреймворк для Rust.
Мы можем создать проект с помощью:
cargo new actix-hello
cd actix-hello
Затем заменяем содержимое src/main.rs
на содержимое ниже, взятое со страницы actix-web getting started:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn manual_hello() -> impl Responder {
HttpResponse::Ok().body("Hey there!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(manual_hello))
})
.bind(("0.0.0.0", 80))?
.run()
.await
}
Наконец, просто собираем бинарник и запускаем его:
cargo build --release
# .......
# Finished release [optimized] target(s) in 1m 58s
./target/release/actix-hello
Клиент
В качестве клиента будем использовать oha — инструмент для бенчмаркинга HTTP, написанный на rust и вдохновленный Hey.
Как установить oha, вы можете посмотреть на странице github.
Запустим bombardier с 500 соединениями для 3 миллионов запросов, и повторим этот эксперимент трижды.
oha -c 500 -n 3000000 http://perf-experiment-host/
Ответ должен выглядеть примерно так:
Результаты
Для каждого из вариантов «язык + фреймворк» мы трижды провели бенчмаркинг приложения, а затем отобрали лучшие данные для сравнения.
Пропускная способность
Rust/Actix-web лидирует с самой высокой пропускной способностью, за ним следует Go/fiber.
C#/ASP.NET, несмотря на свою популярность, отстает от лидеров в этом бенчмарке.
Java/Vertex и Bun/Elysia демонстрируют сопоставимые и средние значения пропускной способности.
Задержка
На графике показаны задержки различных HTTP-фреймворков по трем различным параметрам:
99,9% Latency: задержка, при которой обрабатывается 99,9% запросов.
99 % Latency: задержка, при которой обрабатывается 99 % запросов.
Средняя задержка: среднее время ожидания для всех запросов.
Из графика можно сделать несколько выводов:
Rust/Actix-web и Go/fiber демонстрируют не только впечатляющую пропускную способность, но и более низкие задержки по всем трем метрикам.
C#/ASP.NET демонстрирует относительно меньшую среднюю задержку по сравнению с некоторыми другими фреймворками, несмотря на более низкую пропускную способность.
Заметна разница между задержками 99 % и 99,9 %, что подчеркивает вариативность времени обработки запросов в рамках фреймворков.
Выводы
В нашем исследовании Rust/Actix-web стал явным победителем, получив не только самую высокую пропускную способность, но и сверхнизкие задержки по всем метрикам. Следом идет Go/fiber, который впечатляет сочетанием высокой скорости обработки запросов и эффективного времени отклика. Хотя C#/ASP.NET, возможно, и не дотягивает до двух лидеров по пропускной способности, его среднее время задержки было вполне конкурентоспособным, и это говорит о том, что он по-прежнему является оптимальным выбором для многих приложений. С другой стороны, такие фреймворки, как Java/Vertex и Bun/Elysia, показали неплохую производительность, и, возможно, им потребуется дополнительная настройка, чтобы конкурировать с другими фреймворками.
Наверняка показатели выглядели бы иначе, если бы мы потратили некоторое время на настройку языка/фреймворков. Однако мы хотели, чтобы это сравнение было проведено с нулевой конфигурацией.