Spring BootでgRPCとRESTを比べてみる

前回gRPCを試したので、RESTとgRPCで同じAPIのパフォーマンスを比較してみます。

受け取ったメッセージをそのまま返す単純なAPIです。

REST

package com.example.server;

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

import lombok.Value;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("api/echo")
@Slf4j
public class EchoRestController {

    @PostMapping
    public EchoResponse echo(@RequestBody EchoRequest request) {

        log.info("REST: " + request.getMessage());

        return new EchoResponse(request.getMessage());
    }

    @Value
    public static class EchoRequest {
        private final String message;
    }

    @Value
    public static class EchoResponse {
        private final String message;
    }
}

gRPC

syntax = "proto3";

option java_package = "com.example.server";

service Echo {
  rpc echo (EchoRequest) returns (EchoReply) {}
}

message EchoRequest {
  string message = 1;
}

message EchoReply {
  string message = 1;
}
package com.example.server;

import org.lognet.springboot.grpc.GRpcService;

import com.example.server.EchoGrpc.EchoImplBase;
import com.example.server.EchoOuterClass.EchoReply;
import com.example.server.EchoOuterClass.EchoRequest;

import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;

@GRpcService
@Slf4j
public class EchoGrpcService extends EchoImplBase {

    @Override
    public void echo(EchoRequest request, StreamObserver<EchoReply> responseObserver) {

        log.info("gRPC: " + request.getMessage());

        EchoReply reply = EchoReply.newBuilder()
                .setMessage(request.getMessage())
                .build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

パフォーマンス比較

10文字のメッセージを10万回投げて比較してみましたが、手元のPCでgRPCが71秒、RESTが86秒といった形で、思ったほど差がでませんでした。

それぞれのコードは下記の通りです。

REST

package com.example.client;

import java.util.stream.IntStream;

import org.springframework.web.client.RestTemplate;

import com.example.server.EchoRestController.EchoRequest;
import com.example.server.EchoRestController.EchoResponse;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class EchoRestClient {

    private final RestTemplate restTemplate = new RestTemplate();

    private final String url;

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        String message = "1234567890";
        EchoRestClient client = new EchoRestClient("http://localhost:8080/api/echo");

        IntStream.range(0, 100000)
                .forEach(x -> client.echo(message));

        long totalTime = System.currentTimeMillis() - startTime;

        System.out.println(
                String.format(
                        "Finish. Total time: %,.3f seconds",
                        totalTime / 1000d));
    }

    public String echo(String message) {

        EchoResponse response = restTemplate.postForObject(
                url,
                new EchoRequest(message),
                EchoResponse.class);

        return response.getMessage();
    }
}

gRPC

package com.example.client;

import java.io.Closeable;
import java.util.stream.IntStream;

import com.example.server.EchoGrpc;
import com.example.server.EchoGrpc.EchoBlockingStub;
import com.example.server.EchoOuterClass.EchoReply;
import com.example.server.EchoOuterClass.EchoRequest;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class EchoGrpcClient implements Closeable {

    private final ManagedChannel channel;
    private final EchoBlockingStub stub;

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        String message = "1234567890";
        try (EchoGrpcClient client = new EchoGrpcClient("localhost:6565")) {

            IntStream.range(0, 100000)
                    .forEach(x -> client.echo(message));
        }

        long totalTime = System.currentTimeMillis() - startTime;

        System.out.println(
                String.format(
                        "Finish. Total time: %,.3f seconds",
                        totalTime / 1000d));
    }

    public EchoGrpcClient(String target) {

        channel = ManagedChannelBuilder.forTarget(target)
                .usePlaintext()
                .build();

        stub = EchoGrpc.newBlockingStub(channel);
    }

    public String echo(String message) {

        EchoRequest request = EchoRequest.newBuilder()
                .setMessage(message)
                .build();

        EchoReply reply = stub.echo(request);

        return reply.getMessage();
    }

    @Override
    public void close() {

        channel.shutdown();
    }
}

終わりに

全体のコードは下記になります。

Spring Boot使うとRESTもgRPCも簡単ですね!

ちなみにREST側はSwagger使うことによってOpenAPIの定義作れて、クライアントの生成も楽なのですが、gRPCはprotoファイル作るのが結構面倒な気がします。OpenAPIの情報から作れるとREST→gRPCへの移行が楽なので、そのあたりも探ってみたいと思います。

あとはパフォーマンスの差が思ったほど出なかったのが、シンプルなAPIのせいかもしれないので、もう少し複雑なメッセージのやり取りを行うようなものでも試してみたいと思います。