Bài viết này chúng ta sẽ cùng so sánh về hiệu năng của gRPC và REST.
Một số bài viết về gRPC có thể mọi người quan tâm.
Sample Application
Mục tiêu chính của chúng ta ở đây là tạo ra một ứng dụng - với 2 cách triển khai khác nhau (REST và gRPC) cho cùng một chức năng. Trong các bài viết trước chúng ta đã cùng thảo luận rằng gRPC sẽ phù hợp tốt cho việc phát triển ứng dụng Client - Server hoặc giao tiếp giữa các dịch vụ Microservices, khi mà việc trao đổi thông tin cần đến nhiều các tác vụ IO. Chúng ta hãy cùng thử làm một ví dụ tăng tải các cuộc gọi giữa các dịch vụ để có thể dễ dàng so sánh sự khác biệt về hiệu suất.
Để mọi thứ đơn giản, chúng ta sẽ tạo ra 2 dịch vụ aggregator-service và square-service. Dịch vụ square-service về sẽ tính bình phương của một số. Ví dụ nếu chúng ta gửi đến là 2, nó sẽ phản hồi với kết quả là 4.
Giả sử nghiệp vụ của chúng ta như sau, aggregator-service nhận được request với tham số là một số N và nó muốn tính bình phương tất cả các số từ 1 đến N. Để làm được điều này, aggregator-service sẽ gửi N request với tham số là các số từ 1 đến N đến máy chủ square-service và aggregator-service sẽ nhận được kết quả như dưới đây:
[ { "1":1 }, { "2":4 }, { "3":9 }, { "4":16 }, { "5":25 }
]
Khi N là 1000, aggregator-service sẽ gửi 1000 request đến square-service. Chúng ta sẽ có 2 cách triển khai khác nhau cho logic phía máy chủ square-service như được hiển thị ở đây. Dựa trên endpoint được gọi mà aggregator-service sẽ gọi tới rest service hoặc grpc service.
Proto Service Definition
Chúng ta sẽ tạo ra 4 module maven project như sau:
- File .proto định nghĩa model và service cho gRPC
syntax = "proto3"; package vinsmath; option java_package = "example.model";
option java_multiple_files = true; message Input { int32 number = 1;
} message Output { int32 number = 1; int32 result = 2;
} service SquareRpc { rpc findSquareUnary(Input) returns (Output) {}; rpc findSquareBiStream(stream Input) returns (stream Output) {};
}
REST Server
Đây là spring boot project với Controller có endpoint là /rest/square/unary/{number}
,
@RestController
public class RestSquareController { @Autowired private RestSquareService squareService; @GetMapping("/rest/square/unary/{number}") public int getSquareUnary(@PathVariable int number){ return number * number; } }
gRPC – Server
Chúng ta định nghĩa 2 implementation, một cho Unary, một cho gRPC bi-directional stream.
@GrpcService
public class MyGrpcService extends SquareRpcGrpc.SquareRpcImplBase { @Override public void findSquareUnary(Input request, StreamObserver<Output> responseObserver) { int number = request.getNumber(); responseObserver.onNext( Output.newBuilder().setNumber(number).setResult(number * number).build() ); responseObserver.onCompleted(); } @Override public StreamObserver<Input> findSquareBiStream(StreamObserver<Output> responseObserver) { return new StreamObserver<Input>() { @Override public void onNext(Input input) { var number = input.getNumber(); Output output = Output.newBuilder() .setNumber(number) .setResult(number * number).build(); responseObserver.onNext(output); } @Override public void onError(Throwable throwable) { responseObserver.onCompleted(); } @Override public void onCompleted() { responseObserver.onCompleted(); } }; } }
Server gRPC sẽ chạy ở port 6565.
Aggregator Service
Dịch vụ này sẽ đóng vai trò như một Client Application chứa 2 endpoint để truy cập đến 2 dịch vụ rest service hoặc grpc service đã tạo ở trên.
- Controller chứa endpoint gọi đến grpc service
@RestController
@RequestMapping("grpc")
public class GrpcAPIController { @Autowired private GrpcService service; @GetMapping("/unary/{number}") public Object getResponseUnary(@PathVariable int number){ return this.service.getSquareResponseUnary(number); } @GetMapping("/stream/{number}") public Object getResponseStream(@PathVariable int number){ return this.service.getSquareResponseStream(number); } }
- Controller chứa endpoint gọi đến rest service
@RestController
@RequestMapping("rest")
public class RestAPIController { @Autowired private RestService service; @GetMapping("/unary/{number}") public Object getResponseUnary(@PathVariable int number){ return this.service.getUnaryResponse(number); } }
- application.properties
server.port=8080
grpc.client.square.address=static://localhost:6565
grpc.client.square.negotiationType=plaintext
rest.square.service.endpoint=http://localhost:7575
Ví dụ kết quả:
gRPC vs REST Performance – Unary
Chúng ta sẽ thực hiện kiểm tra hiệu suất bằng cách gửi 1000 request đến aggregator-service với 100 request đồng thời tại một thời điểm (mô phỏng server chịu tải 100 request đồng thời). Chúng ta sử dụng công cụ ApacheBench để kiểm tra hiệu suất.
- Rest request
ab -n 1000 -c 100 http://localhost:8080/rest/unary/1000
Kết quả:
- gRPC request
ab -n 1000 -c 100 http://localhost:8080/grpc/unary/1000
Kết quả:
- So sánh
| | CPU Utilization | Throughput (Requests/Second) | 50th Percentile Response Time | 90th Percentile Response Time | | -------- | -------- | -------- | -------- | | REST | 29.43 |3383 | 3576 | | gRPC | 66.71 |1266 | 2660 |
Chú ý rằng là, cả 3 service trong ví dụ này cùng chạy trên một máy chủ vật lý. Kết quả sẽ có thể khác phụ thuộc vào CPU/Memory vào máy của mọi người.
gRPC vs REST Performance – Bi-Directional Stream
Với ví dụ trên chúng ta thấy gRPC unary/blocking stub
dường như đã hoạt động tốt hơn nhiều so với REST. Bây giờ chúng ta sẽ chạy thử nghiệm tương tự bằng cách sử dụng phương pháp gRPC bi-directional stream
, thông lượng lên đến ~ 150.67 requests/second, quá tuyệt vời!
- So sánh
| | CPU Utilization | Throughput (Requests/Second) | 50th Percentile Response Time | 90th Percentile Response Time | | -------- | -------- | -------- | -------- | | REST | 29.43 |3383 seconds | 3576 seconds | | gRPC-Unary | 66.71 |1266 seconds | 2660 seconds | | gRPC Bi-Directional Stream | 150.67 |462 seconds | 1762 seconds |
Chú ý: Throughtput càng cao càng tốt, Percentile Response Time càng thấp càng tốt.
Nguồn: https://thenewstack.wordpress.com/2021/11/24/grpc-grpc-vs-rest-performance/
Follow me: thenewstack.wordpress.com