今天来和小伙伴们聊一聊该如何处理 gRPC 中遇到的异常。
在之前的几篇文章中,其实我们也遇到过异常问题,只是当时没有和小伙伴们细说,只是囫囵吞枣写了一个案例而已,今天我们就来把这个话题跟小伙伴们仔细捋一捋。
我们之前写过一个登录的案例,在之前的案例中,如果用户在登录时输入了错误的用户名密码的话,那么我们是通过一个普通的数据流返回异常信息,其实,对于异常信息,我们可以通过专门的异常通道来写回到客户端。
1. 服务端处理异常
先来看看服务端如何处理异常。
还是以我们之前的 gRPC 登录案例为例,我们修改服务端的登录逻辑如下(完整代码小伙伴们可以参考之前的 手把手教大家在 gRPC 中使用 JWT 完成身份校验 一文):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class LoginServiceImpl extends LoginServiceGrpc.LoginServiceImplBase { @Override public void login(LoginBody request, StreamObserver<LoginResponse> responseObserver) { String username = request.getUsername(); String password = request.getPassword(); if ("javaboy".equals(username) && "123".equals(password)) { System.out.println("login success"); String jwtToken = Jwts.builder().setSubject(username).signWith(AuthConstant.JWT_KEY).compact(); responseObserver.onNext(LoginResponse.newBuilder().setToken(jwtToken).build()); responseObserver.onCompleted(); }else{ System.out.println("login error"); responseObserver.onError(Status.UNAUTHENTICATED.withDescription("login error").asException()); } } }
|
小伙伴们看到,在登录失败时我们通过 responseObserver.onError
方法将异常信息写回到客户端。这个方法的参数是一个 Throwable 对象,对于这个对象,在 Status 这个枚举类中定义了一些常见的值,分别如下:
- OK(0):请求成功。
- CANCELLED(1):操作被取消。
- UNKNOWN(2):未知错误。
- INVALID_ARGUMENT(3):客户端给了无效的请求参数。
- DEADLINE_EXCEEDED(4):请求超过了截止时间。
- NOT_FOUND(5):请求资源未找到。
- ALREADY_EXISTS(6):添加的内容已经存在。
- PERMISSION_DENIED(7):请求权限不足。
- RESOURCE_EXHAUSTED(8):资源耗尽。
- FAILED_PRECONDITION(9):服务端上为准备好。
- ABORTED(10):请求被中止。
- OUT_OF_RANGE(11):请求超出范围。
- UNIMPLEMENTED(12):未实现的操作。
- INTERNAL(13):服务内部错误。
- UNAVAILABLE(14):服务不可用。
- DATA_LOSS(15):数据丢失或者损毁。
- UNAUTHENTICATED(16):请求未认证。
系统默认给出的请求类型大致上就这些。当然,如果这些并不能满足你的需求,我们也可以扩展这个枚举类。
2. 客户端处理异常
当服务端给出异常信息之后,客户端的处理分为两种情况。
2.1 异步请求
如果客户端是异步请求,则直接在异常回调中处理即可,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class LoginClient { public static void main(String[] args) throws InterruptedException { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); LoginServiceGrpc.LoginServiceStub stub = LoginServiceGrpc.newStub(channel).withDeadline(Deadline.after(3, TimeUnit.SECONDS)); login(stub); } private static void login(LoginServiceGrpc.LoginServiceStub stub) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); stub.login(LoginBody.newBuilder().setUsername("javaboy").setPassword("1234").build(), new StreamObserver<LoginResponse>() { @Override public void onNext(LoginResponse loginResponse) { System.out.println("loginResponse.getToken() = " + loginResponse.getToken()); }
@Override public void onError(Throwable throwable) { System.out.println("throwable = " + throwable); }
@Override public void onCompleted() { countDownLatch.countDown(); } }); countDownLatch.await(); } }
|
小伙伴们看到,直接在 onError 回到中处理异常即可。
2.2 同步请求
如果客户端请求是同步阻塞请求,那么就要通过异常捕获的方式获取服务端返回的异常信息了,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class LoginClient2 { public static void main(String[] args) throws InterruptedException { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); LoginServiceGrpc.LoginServiceBlockingStub stub = LoginServiceGrpc.newBlockingStub(channel).withDeadline(Deadline.after(3, TimeUnit.SECONDS)); login(stub); }
private static void login(LoginServiceGrpc.LoginServiceBlockingStub stub) throws InterruptedException { try { LoginResponse resp = stub.login(LoginBody.newBuilder().setUsername("javaboy").setPassword("1234").build()); System.out.println("resp.getToken() = " + resp.getToken()); } catch (Exception e) { System.out.println("e.getMessage() = " + e.getMessage()); } } }
|
同步阻塞请求就通过异常捕获去获取服务端返回的异常信息即可。
3. 题外话
最后,再来和小伙伴们说一个提高 gRPC 数据传输效率的小技巧,那就是传输的数据可以使用 gzip 进行压缩。
具体处理方式就是在客户端调用 withCompression
方法指定数据压缩,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class LoginClient2 { public static void main(String[] args) throws InterruptedException { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); LoginServiceGrpc.LoginServiceBlockingStub stub = LoginServiceGrpc.newBlockingStub(channel).withDeadline(Deadline.after(3, TimeUnit.SECONDS)); login(stub); }
private static void login(LoginServiceGrpc.LoginServiceBlockingStub stub) throws InterruptedException { try { LoginResponse resp = stub.withCompression("gzip").login(LoginBody.newBuilder().setUsername("javaboy").setPassword("123").build()); System.out.println("resp.getToken() = " + resp.getToken()); } catch (Exception e) { System.out.println("e.getMessage() = " + e.getMessage()); } } }
|
好啦,一个关于 gRPC 的小小知识点~