В этой статье я собираюсь сравнить собственный HTTP-сервер Java с Hyper HTTP-сервером Rust. Основываясь на своих исследованиях, я обнаружил, что Hyper - самый популярный сервер на стороне Rust. Если есть лучшие и популярные альтернативы, пожалуйста, дайте мне знать.
Rust - это скомпилированный язык, который создает машинный код, в то время как Java также является скомпилированным языком, который создает байт-код, который выполняется внутри JVM. Сравнение, возможно, не настолько справедливо. В любом случае, давайте проведем тесты и проверим результаты.
Тестовая настройка
Тесты выполняются на MacBook Pro M1 с 16 ГБ оперативной памяти.
Версии программного обеспечения следующие:
- Java v20
- Rust v1.68.2
Код HTTP-сервера hello world в обоих случаях выглядит следующим образом:
Java
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class NativeHelloWorldServer {
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);
server.createContext("/", new MyHandler());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
}
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
String response = "Hello world!";
t.sendResponseHeaders(200, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}
Rust
use std::convert::Infallible;
use std::net::SocketAddr;
use bytes::Bytes;
use http_body_util::Full;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use tokio::net::TcpListener;
async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
let response = Response::builder()
.header("Content-type", "text/plain")
.body(Full::new(Bytes::from("Hello World!")))
.unwrap();
Ok(response)
}
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
let listener = TcpListener::bind(addr).await?;
loop {
let (stream, _) = listener.accept().await?;
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(stream, service_fn(hello)).await {
println!("Error serving connection: {:?}", err);
}
});
}
}
Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
bytes = "1"
hyper = { version = "1.0.0-rc.3", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1.0-rc.2"
Код Rust был собран в режиме релиза:
> cargo build --release
...
Compiling hyper v1.0.0-rc.3
Compiling hello_world v0.1.0 (/Users/mayankc/Work/source/perfComparisons/rust)
Finished release [optimized] target(s) in 19.88s
Тестирование
Каждый тест выполняется для 5 миллионов запросов.
Тесты выполняются для 10, 100 и 300 одновременных подключений.
Нагрузочный тест выполняется с помощью инструмента HTTP-тестирования Bombardier.
Ниже приведены таблицы, показывающие результаты для каждого уровня параллелизма:
Анализ
Rust полностью превосходит Java по всем аспектам, будь то RPS, задержки, процессор или память.
ПОБЕДИТЕЛЬ: Rust Hyper