使用Spring Boot和Rust生成二維碼的性能比較(附代碼)
本文重點(diǎn)比較使用虛擬線(xiàn)程的SpringBoot和使用Actix框架的Rust,來(lái)實(shí)現(xiàn)QR碼生成器API。這兩種技術(shù)都是成熟的,無(wú)需進(jìn)一步介紹。接下來(lái),讓我們直接深入測(cè)試設(shè)置的細(xì)節(jié)。
一、測(cè)試設(shè)置
1. 環(huán)境
所有測(cè)試都在裝有16GB RAM的MacBook Pro M1上進(jìn)行。使用的測(cè)試工具是Bombardier的定制版本,支持在請(qǐng)求體中包含隨機(jī)URL。這些測(cè)試使用的軟件版本如下:
- SpringBoot 3.1.3,帶有Java v20(啟用預(yù)覽以獲取虛擬線(xiàn)程)
- Rust 1.72.0
2. 代碼
這個(gè)QR碼生成器應(yīng)用程序被設(shè)計(jì)成接收一個(gè)JSON請(qǐng)求體,其中包含一個(gè)名為"urlToEmbed"的必需參數(shù)。該應(yīng)用程序的主要功能是為指定的URL生成一個(gè)QR碼,并在HTTP響應(yīng)中以PNG格式傳送QR碼。為增加復(fù)雜性,該應(yīng)用程序在HTTPS上運(yùn)行。
(1) SpringBoot(虛擬線(xiàn)程)
server.port=3000
server.ssl.certificate=/Users/mayankc/Work/source/certs/cert.pem
server.ssl.certificate-private-key=/Users/mayankc/Work/source/certs/key.pem
package com.example.qr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;
@SpringBootApplication
public class QrApplication {
public static void main(String[] args) {
SpringApplication.run(QrApplication.class, args);
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
package com.example.qr;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
import com.example.qr.QrRequest;
import com.example.qr.QrGenerator;
@RestController
public class QrController {
@PostMapping("/qr")
public ResponseEntity handleRequest(@RequestBody QrRequest qrRequest) {
if(qrRequest.getUrlToEmbed() == null) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, "image/png");
return new ResponseEntity<byte[]>(
QrGenerator.generateQR(qrRequest.getUrlToEmbed(), 512, 512),
httpHeaders,
HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
package com.example.qr;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
public class QrGenerator {
public static byte[] generateQR(String text, int width, int height) throws WriterException, IOException {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, width, height);
ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream();
MatrixToImageConfig con = new MatrixToImageConfig() ;
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", pngOutputStream, con);
byte[] pngData = pngOutputStream.toByteArray();
return pngData;
}
}
package com.example.qr;
public class QrRequest {
private String urlToEmbed;
public String getUrlToEmbed() {
return this.urlToEmbed;
}
public void setUrlToEmbed(String urlToEmbed) {
this.urlToEmbed = urlToEmbed;
}
}
(2) Rust
[package]
name = "actix_qr_generator"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = { version = "4", features = ["openssl"] }
qrcode-generator = "4.1.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
openssl = { version = "0.10" , features = ["vendored"] }
use actix_web::{web, post, App, HttpServer, HttpResponse, Responder};
use qrcode_generator::QrCodeEcc;
use serde::Deserialize;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
#[derive(Deserialize)]
struct QrRequest {
urlToEmbed: String,
}
#[post("/qr")]
async fn generate_qr(qr_request: web::Json<QrRequest>) -> impl Responder {
if qr_request.urlToEmbed.is_empty() {
return HttpResponse::BadRequest().into();
}
let result: Vec<u8> = qrcode_generator::to_png_to_vec(qr_request.urlToEmbed.clone(), QrCodeEcc::Low, 512)
.unwrap();
return HttpResponse::Ok()
.content_type("image/png")
.body(result);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("/Users/mayankc/Work/source/perfComparisons/certs/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("/Users/mayankc/Work/source/perfComparisons/certs/cert.pem")
.unwrap();
HttpServer::new(|| App::new().service(generate_qr))
.bind_openssl("127.0.0.1:3000", builder)?
.run()
.await
}
// 注意 ================================================
// 該應(yīng)用程序已在發(fā)布模式下構(gòu)建。
// =====================================================
二、結(jié)果
為了全面評(píng)估性能,這里進(jìn)行了一系列細(xì)致的檢查。每個(gè)檢查包括10萬(wàn)個(gè)請(qǐng)求,并在10、50和100個(gè)并發(fā)連接的范圍內(nèi)評(píng)估它們的效率??紤]到QR碼生成的資源密集型特性,故意保持了稍低的請(qǐng)求量,與其他場(chǎng)景相比。
結(jié)果如下:
根據(jù)以下公式,還生成了一個(gè)得分卡。對(duì)于每個(gè)測(cè)量,獲取獲勝的差距。如果獲勝的差距是:
- < 5%,不給予任何分?jǐn)?shù)
- 在5%到20%之間,獲勝者得1分
- 在20%到50%之間,獲勝者得2分
- 50%,獲勝者得3分
得分卡如下: