TIL: Java SpringBoot 대 Asp.Net REST API 성능

11238 단어 javadotnetspring

TIL: Java Spring 부트와 Asp.Net REST API 성능 비교



오늘 저는 Java SpringBoot 및 Asp.Net 성능에 대해 배웠습니다(#til).

곧 출시될 .Net 7 릴리스와 Minimal API를 테스트하고 있었는데 그 속도에 놀랐습니다. 그래서 Java 18 SpringBoot 프로젝트와 비교하기로 했습니다.

결과는 Asp.Net 7 솔루션이 Java 18의 SpringBoot 애플리케이션보다 거의 2배 빠르다는 것입니다!

결과는 다음과 같습니다.



Asp.Net 7 최소 API는 SpringBoot Java 18 애플리케이션의 경우 30초당 2,152,070 요청에 비해 30초에 4,084,163 요청을 제공합니다!

자바 18 스프링부트

$ k6 run --vus 16 --duration 30s config.json

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: config.json
     output: -

  scenarios: (100.00%) 1 scenario, 16 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 16 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 00/16 VUs, 2152070 complete and 0 interrupted iterations
default ✓ [======================================] 16 VUs  30s

     data_received..................: 373 MB  12 MB/s
     data_sent......................: 392 MB  13 MB/s
     http_req_blocked...............: avg=1.61µs   min=441ns   med=942ns    max=2.39ms p(90)=1.53µs   p(95)=1.75µs
     http_req_connecting............: avg=308ns    min=0s      med=0s       max=1.55ms p(90)=0s       p(95)=0s
     http_req_duration..............: avg=180.65µs min=114.4µs med=170.42µs max=4.11ms p(90)=202.21µs p(95)=216.99µs
       { expected_response:true }...: avg=180.65µs min=114.4µs med=170.42µs max=4.11ms p(90)=202.21µs p(95)=216.99µs
     http_req_failed................: 0.00%   ✓ 0            ✗ 2152070
     http_req_receiving.............: avg=22.51µs  min=4.59µs  med=20.95µs  max=3.95ms p(90)=32.44µs  p(95)=39.5µs
     http_req_sending...............: avg=6.3µs    min=2.63µs  med=5.61µs   max=3.03ms p(90)=8.87µs   p(95)=9.59µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s     p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=151.83µs min=92.24µs med=142.39µs max=3.43ms p(90)=172.73µs p(95)=187.11µs
     http_reqs......................: 2152070 71733.676043/s
     iteration_duration.............: avg=218.91µs min=138.9µs med=207.65µs max=4.54ms p(90)=244.87µs p(95)=263.15µs
     iterations.....................: 2152070 71733.676043/s
     vus............................: 16      min=16         max=16
     vus_max........................: 16      min=16         max=16


Asp.Net 7

$ k6 run --vus 16 --duration 30s config.json

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: config.json
     output: -

  scenarios: (100.00%) 1 scenario, 16 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 16 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 00/16 VUs, 4084163 complete and 0 interrupted iterations
default ✓ [======================================] 16 VUs  30s

     data_received..................: 845 MB  28 MB/s
     data_sent......................: 743 MB  25 MB/s
     http_req_blocked...............: avg=1.06µs   min=431ns   med=972ns    max=2.48ms p(90)=1.41µs   p(95)=1.62µs
     http_req_connecting............: avg=0ns      min=0s      med=0s       max=66.6µs p(90)=0s       p(95)=0s
     http_req_duration..............: avg=74.37µs  min=30.59µs med=67.84µs  max=7.33ms p(90)=91.37µs  p(95)=106.97µs
       { expected_response:true }...: avg=74.37µs  min=30.59µs med=67.84µs  max=7.33ms p(90)=91.37µs  p(95)=106.97µs
     http_req_failed................: 0.00%   ✓ 0             ✗ 4084163
     http_req_receiving.............: avg=13.64µs  min=4.43µs  med=12.72µs  max=7.05ms p(90)=17.75µs  p(95)=19.44µs
     http_req_sending...............: avg=6.36µs   min=2.77µs  med=5.96µs   max=5.83ms p(90)=8.24µs   p(95)=8.96µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s     p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=54.35µs  min=19.55µs med=48.45µs  max=5.34ms p(90)=70.1µs   p(95)=83.96µs
     http_reqs......................: 4084163 136135.973692/s
     iteration_duration.............: avg=113.11µs min=56.58µs med=105.71µs max=7.9ms  p(90)=133.41µs p(95)=153.52µs
     iterations.....................: 4084163 136135.973692/s
     vus............................: 16      min=16          max=16
     vus_max........................: 16      min=16          max=16


세부



목표는 JSON 본문을 가져오고 역직렬화하고 값을 수정하고 직렬화된 JSON 응답을 다시 반환하는 간단한 HTTP POST API를 테스트하는 것이었습니다.

요청 및 응답 개체는 동일하지만 다른 클래스로 표시됩니다. 데이터 수정은 단순히 문자열을 연결하고 정수 값을 추가하는 것입니다.

Asp.Net 프로젝트의 경우 .Net 7의 미리 보기7이 사용되었으며 Java 대응 항목의 경우 Oracle OpenJDK 18.0.2.1과 결합된 종속성으로 spring-boot-starter-weblombok가 포함된 새로운 SpringBoot 프로젝트가 사용되었습니다.

코드는 다음과 같습니다.

자바 18 스프링부트



pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>RestApiDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>RestApiDemo</name>
    <description>RestApiDemo</description>
    <properties>
        <java.version>18</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


RestApiDemoApplication.java

package com.example.restapidemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestApiDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestApiDemoApplication.class, args);
    }

}


입력모델.자바

package com.example.restapidemo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public final class InputModel {
    String FirstName;
    String LastName;
    Integer Age;
}


OutputModel.java

package com.example.restapidemo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
public final class OutputModel {
    String FirstName;
    String LastName;
    Integer Age;
}


DemoController.java

package com.example.restapidemo;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @PostMapping("/get")
    @ResponseBody
    public OutputModel GetData(@RequestBody InputModel input) {
        return new OutputModel(input.FirstName + "x", input.LastName + "x", input.Age + 10);
    }
}


Asp.Net 7



Program.cs

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/get", ([FromBody] InputModel input) => new OutputModel
    {
        FirstName = input.FirstName + "x",
        LastName = input.LastName + "x",
        Age = input.Age + 10
    }
);

app.Run();

internal sealed class InputModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

internal sealed class OutputModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}


K6.io 구성.json

import http from 'k6/http';

export default function () {

  const url = 'http://localhost:8080/get';

  const payload = JSON.stringify({
    FirstName: 'John',
    LastName: 'Doe',
    Age: 33
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
    },
  };

  http.post(url, payload, params);
}


응답 유효성을 위해 CURL로 먼저 테스트:
자바 18 스프링부트

$ curl -X POST http://localhost:8080/get -H "content-type: application/json" -d "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"Age\":33}"

{"firstName":"Johnx","lastName":"Doex","age":43}%


Asp.Net 7

$ curl -X POST http://localhost:8080/get -H "content-type: application/json" -d "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"Age\":33}"

{"firstName":"Johnx","lastName":"Doex","age":43}%


.Net 7이 포함된 Kestrel 서버는 Java 18 기반 SpringBoot 애플리케이션이 포함된 기본 Tomcat 서버와 비교하여 간단한 API를 처리하는 꽤 괜찮은 작업을 수행하는 것 같습니다.

일부 ORM 프레임워크 또는 로깅을 사용하기로 결정할 때까지 기본 언어가 매우 유사하기 때문에 간단한 REST API를 구현하기로 결정할 때 누군가 이것을 사용하는 것을 고려할 수 있습니다.

좋은 웹페이지 즐겨찾기