- vừa được xem lúc

[gRPC] - gRPC Error Handling

0 0 25

Người đăng: TheLight

Theo Viblo Asia

Trong các ứng dụng microservices ngày nay việc giao tiếp giữa các service thì gRPC là một lựa chọn tốt. Trong một ứng dụng Client-Server đơn giản, Client gửi yêu cầu đến Server vá Server xử lý yêu cầu và gửi lại phản hồi. Nhưng không phải khi nào Client cũng gửi một yêu cầu hợp lệ. Client có thể gửi yêu cầu mà không kèm thông tin xác thực (access_token) hoặc các giá trị tham số có thể nằm ngoài phạm vi mà Server không thể xử lý, v.v. Trong những trường hợp đó, Server phải gửi lại thông báo/mã lỗi đến Client.

Sample Application

Chúng ta hãy xem xét một ứng dụng để tính bình phương cho một số. Client gửi một số mà Server phản hồi bình phương của số đó.

Giả sử Server chỉ có khả năng tính bình phương cho các số từ 2 đến 20. Bất kỳ số nào nằm ngoài phạm vi này sẽ bị từ chối với thông báo lỗi thích hợp.

Protobuf – Service Definition

Chúng ta sẽ tiến hành định nghĩa dịch vụ cho kịch bản trên.

syntax = "proto3"; package calculator; option java_package = "example.calculator";
option java_multiple_files = true; message Request { int32 number = 1;
} message Response { int32 result = 1;
} service CalculatorService { rpc findSquare(Request) returns (Response) {};
}

Khi chúng ta chạy lệnh maven dưới đây, maven sẽ tự động tạo code cho client application và server application bằng công cụ protoc.

mvn clean compile

Một file định dạng ".proto" định nghĩa dịch vụ thực hiện hầu hết các công việc tạo ra các class (gen code) đối với việc giao tiếp giữa Client và Server.

Class CalculatorServiceImplBase là abstract class được tạo tự động khi gen code cần được phía Server implements. Tương tự CalculatorServiceStub là class mà phía client application sử dụng để gửi yêu cầu đến server.

Server Side

Đầu tiên chúng ta tạo ra class GrpcSquareService cho phép thực hiện phép tính bình phương, class này là implementation của class base CalculatorServiceImplBase để implements phương thức findSquare (phương thức triển khai nghiệp vụ tính bình phương).

public class GrpcSquareService extends CalculatorServiceGrpc.CalculatorServiceImplBase { @Override public void findSquare(Request request, StreamObserver<Response> responseObserver) { int number = request.getNumber(); Response response = Response.newBuilder() .setResult(number * number) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); }
}

Tiếp theo, chúng ta cần start gRPC server để cung cấp dịch vụ cho Client.

public class CalculatorServer { public static void main(String[] args) throws IOException, InterruptedException { // build gRPC server Server server = ServerBuilder.forPort(6565) .addService(new GrpcSquareService()) .build(); // start server.start(); // shutdown hook Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("gRPC server is shutting down!"); server.shutdown(); })); server.awaitTermination(); } }

Success Response

Trước tiên, chúng ta cùng xem happy case, trường hợp Client gửi request và nhận về response thành công.

public class SquareServiceTest { private ManagedChannel channel; private CalculatorServiceGrpc.CalculatorServiceBlockingStub clientStub; @Before public void setup(){ this.channel = ManagedChannelBuilder.forAddress("localhost", 6565) .usePlaintext() .build(); this.clientStub = CalculatorServiceGrpc.newBlockingStub(channel); } @Test public void squareServiceHappyPath(){ // build the request object Request request = Request.newBuilder() .setNumber(50) .build(); Response response = this.clientStub.findSquare(request); System.out.println("Success Response : " + response.getResult()); } @After public void teardown(){ this.channel.shutdown(); } }

Output:

Success Response : 2500

gRPC Error Handling – OnError

Server sẽ xác thực đầu vào và nếu nó không nằm trong phạm vi đã cho, nó có thể sử dụng phương thức onError của StreamObserver để cho phản hồi lại cho Client biết rằng request gửi đến không hợp lệ.

public class GrpcSquareService extends CalculatorServiceGrpc.CalculatorServiceImplBase { @Override public void findSquare(Request request, StreamObserver<Response> responseObserver) { int number = request.getNumber(); if(number < 2 || number > 20){ Status status = Status.FAILED_PRECONDITION.withDescription("Not between 2 and 20"); responseObserver.onError(status.asRuntimeException()); return; } // only valid ranges Response response = Response.newBuilder() .setResult(number * number). build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }

Nếu Client gửi một request không hợp lệ, Client sẽ nhận lại thông báo lỗi như dưới đây.

io.grpc.StatusRuntimeException: FAILED_PRECONDITION: Not between 2 and 20 at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:244) at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:225) at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:142)

Chúng ta có thể bắt ngoại lệ trong khối try-catch như bình thường và truy cập đối tượng Status từ ngoại lệ.

try{ Response response = this.clientStub.findSquare(request); System.out.println("Success Response : " + response.getResult());
}catch (Exception e){ Status status = Status.fromThrowable(e); System.out.println(status.getCode() + " : " + status.getDescription());
}

gRPC Error Handling – Metadata

Cách tiếp cận trên hoạt động tốt. Tuy nhiên, chúng ta chỉ có thể gửi một trong các mã lỗi được xác định trước của gRPC. Nếu chúng ta cần gửi một số mã lỗi/thông điệp/đối tượng mà chúng ta định nghĩa thì trong trường hợp này, trước tiên chúng ta phải xác định cách phản hồi lỗi của chúng ta bằng cách định nghĩa chúng trên file ".proto" mà chúng ta định nghĩa dịch vụ.

  • Ví dụ chúng ta sử dụng một số mã lỗi bằng cách sử dụng enum
  • Chúng ta cũng định nghĩa một đối tượng là ErrorResponse với các tham số tùy chỉnh.
syntax = "proto3"; package calculator; option java_package = "example.calculator";
option java_multiple_files = true; message Request { int32 number = 1;
} message Response { int32 result = 1;
} enum ErrorCode { ABOVE_20 = 0; BELOW_2 = 1;
} message ErrorResponse { int32 input = 1; ErrorCode error_code = 2;
} service CalculatorService { rpc findSquare(Request) returns (Response) {};
}
  • Ở phía Server, chúng ta buid đối tượng ErrorResponse và gửi nó đến Client thông qua Metadata khi Client gửi request không hợp lệ.
@Override
public void findSquare(Request request, StreamObserver<Response> responseObserver) { int number = request.getNumber(); if(number < 2 || number > 20){ Metadata metadata = new Metadata(); Metadata.Key<ErrorResponse> responseKey = ProtoUtils.keyForProto(ErrorResponse.getDefaultInstance()); ErrorCode errorCode = number > 20 ? ErrorCode.ABOVE_20 : ErrorCode.BELOW_2; ErrorResponse errorResponse = ErrorResponse.newBuilder() .setErrorCode(errorCode) .setInput(number) .build(); // pass the error object via metadata metadata.put(responseKey, errorResponse); responseObserver.onError(Status.FAILED_PRECONDITION.asRuntimeException(metadata)); return; } // only valid ranges Response response = Response.newBuilder() .setResult(number * number). build(); responseObserver.onNext(response); responseObserver.onCompleted();
}
  • Ở phía Client, chúng ta thực hiện try/catch để bắt lỗi. Nhưng chúng ta có thể truy cập metadata và đối tượng ErrorResponse từ ngoại lệ xảy ra.
try{ Response response = this.clientStub.findSquare(request); System.out.println("Success Response : " + response.getResult());
}catch (Exception e){ Metadata metadata = Status.trailersFromThrowable(e); ErrorResponse errorResponse = metadata.get(ProtoUtils.keyForProto(ErrorResponse.getDefaultInstance())); System.out.println(errorResponse.getInput() + " : " + errorResponse.getErrorCode());
}

Output:

50 : ABOVE_20

gRPC Error Handling – OneOf

Trong một số trường hợp chúng ta không muốn coi số nằm ngoài phạm vi cho phép là lỗi là ngoại lệ, chúng ta cũng có thể gửi lại response thích hợp thay vì ngoại lệ. Server có thể gửi lại một trong hai message phản hồi bằng cách sử dụng oneof

File định nghĩa dịch vụ protobuf khi đó như sau:

message Request { int32 number = 1;
} message SuccessResponse { int32 result = 1;
} enum ErrorCode { ABOVE_20 = 0; BELOW_2 = 1;
} message ErrorResponse { int32 input = 1; ErrorCode error_code = 2;
} message Response { oneof response { SuccessResponse success_response = 1; ErrorResponse error_response = 2; }
} service CalculatorService { rpc findSquare(Request) returns (Response) {};
}
  • Server side
@Override
public void findSquare(Request request, StreamObserver<Response> responseObserver) { int number = request.getNumber(); Response.Builder builder = Response.newBuilder(); if(number < 2 || number > 20){ ErrorCode errorCode = number > 20 ? ErrorCode.ABOVE_20 : ErrorCode.BELOW_2; ErrorResponse errorResponse = ErrorResponse.newBuilder() .setInput(number) .setErrorCode(errorCode) .build(); builder.setErrorResponse(errorResponse); }else{ // only valid ranges builder.setSuccessResponse(SuccessResponse.newBuilder().setResult(number * number).build()); } responseObserver.onNext(builder.build()); responseObserver.onCompleted();
}
  • Bây giờ Client sẽ không nhận được bất kỳ ngoại lệ nào. Thay vào đó, nó sẽ nhận được 2 loại phản hồi có thể là SUCCESS_RESPONSE hoặc ERROR_RESPONSE. Tùy thuộc vào loại đối tượng mà chúng ta nhận được, chúng ta ta sẽ đưa ra những xử lý thích hợp.
Response response = this.clientStub.findSquare(request); switch (response.getResponseCase()){ case SUCCESS_RESPONSE: System.out.println("Success Response : " + response.getSuccessResponse().getResult()); break; case ERROR_RESPONSE: System.out.println("Error Response : " + response.getErrorResponse().getErrorCode()); break;
}

Ví dụ, gửi 2 request với 2 số là 10 và 50, chúng ta nhận được kết quả như sau:

Success Response : 100
Error Response : ABOVE_20

Tổng kết

Trên đây là một số các cách xử lý lỗi vởi gRPC. Mọi người có thể sử dụng bất kỳ tùy chọn nào trong số các cách này tùy vào trường hợp sử dụng. Hi vọng bài viết hữu ích với mọi người.

Nguồn: https://thenewstack.wordpress.com/2021/11/24/grpc-grpc-error-handling/

Follow me: thenewstack.wordpress.com

Bình luận

Bài viết tương tự

- vừa được xem lúc

gRPC - Nó là gì và có nên sử dụng hay không?

Nhân một ngày rảnh rỗi, mình ngồi đọc lại RPC cũng như gRPC viết lại để nhớ lâu hơn. Vấn đề là gì và tại sao cần nó .

0 0 112

- vừa được xem lúc

Build gRPC client iOS đơn giản

Introduction. Trong bài viết trước, chúng ta đã cùng nhau tìm hiểu về gRPC và cách để build một gRPC server bằng node.js với các chức năng CRUD đơn giản:. https://viblo.

0 0 24

- vừa được xem lúc

Build CRUD Server đơn giản với gRPC và node.js

. gRPC là một framework RPC (remote procedure call) do Google phát triển, đã thu hút được nhiều sự quan tâm của cộng đồng software developer trong những năm vừa qua. Đặc biệt, gRPC được ứng dụng nhiều trong các hệ thống microservice bởi nhiều đặc tính vượt trội như: open source, không phụ thuộc vào

0 0 154

- vừa được xem lúc

Microservice với Golang, NodeJS và gRPC (Phần 2)

Tiếp tục phần 1, phần này mình sẽ tạo một con node server để connect đến core server và cũng chỉ để hiển thị hello world. Node Server.

0 0 103

- vừa được xem lúc

Microservice với Golang, NodeJS và gRPC (Phần 1)

Đặt vấn đề. .

0 0 40

- vừa được xem lúc

Biến ứng dụng Laravel của bạn trở nên phức tạp hơn với gRPC

gRPC là gì . RPC.

0 0 278