使用Spring Boot和Rust生成二维码的性能比较(附代码)

2023年 9月 25日 54.5k 0

本文重点比较使用虚拟线程的SpringBoot和使用Actix框架的Rust,来实现QR码生成器API。这两种技术都是成熟的,无需进一步介绍。接下来,让我们直接深入测试设置的细节。

一、测试设置

1. 环境

所有测试都在装有16GB RAM的MacBook Pro M1上进行。使用的测试工具是Bombardier的定制版本,支持在请求体中包含随机URL。这些测试使用的软件版本如下:

  • SpringBoot 3.1.3,带有Java v20(启用预览以获取虚拟线程)
  • Rust 1.72.0

2. 代码

这个QR码生成器应用程序被设计成接收一个JSON请求体,其中包含一个名为"urlToEmbed"的必需参数。该应用程序的主要功能是为指定的URL生成一个QR码,并在HTTP响应中以PNG格式传送QR码。为增加复杂性,该应用程序在HTTPS上运行。

(1) SpringBoot(虚拟线程)

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(
         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) -> impl Responder {
  if qr_request.urlToEmbed.is_empty() {
    return HttpResponse::BadRequest().into();
  }


  let result: Vec = 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
}


// 注意 ================================================
// 该应用程序已在发布模式下构建。
// =====================================================

二、结果

为了全面评估性能,这里进行了一系列细致的检查。每个检查包括10万个请求,并在10、50和100个并发连接的范围内评估它们的效率。考虑到QR码生成的资源密集型特性,故意保持了稍低的请求量,与其他场景相比。

结果如下:

根据以下公式,还生成了一个得分卡。对于每个测量,获取获胜的差距。如果获胜的差距是:

  • < 5%,不给予任何分数
  • 在5%到20%之间,获胜者得1分
  • 在20%到50%之间,获胜者得2分
  • 50%,获胜者得3分

得分卡如下:

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论