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