前回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のせいかもしれないので、もう少し複雑なメッセージのやり取りを行うようなものでも試してみたいと思います。