我們之前寫過一個(gè)登錄的案例,在之前的案例中,如果用戶在登錄時(shí)輸入了錯(cuò)誤的用戶名密碼的話,那么我們是通過一個(gè)普通的數(shù)據(jù)流返回異常信息,其實(shí),對(duì)于異常信息,我們可以通過專門的異常通道來寫回到客戶端。
今天來和小伙伴們聊一聊該如何處理 gRPC 中遇到的異常。
在之前的幾篇文章中,其實(shí)我們也遇到過異常問題,只是當(dāng)時(shí)沒有和小伙伴們細(xì)說,只是囫圇吞棗寫了一個(gè)案例而已,今天我們就來把這個(gè)話題跟小伙伴們仔細(xì)捋一捋。
我們之前寫過一個(gè)登錄的案例,在之前的案例中,如果用戶在登錄時(shí)輸入了錯(cuò)誤的用戶名密碼的話,那么我們是通過一個(gè)普通的數(shù)據(jù)流返回異常信息,其實(shí),對(duì)于異常信息,我們可以通過專門的異常通道來寫回到客戶端。
1. 服務(wù)端處理異常
先來看看服務(wù)端如何處理異常。
還是以我們之前的 gRPC 登錄案例為例,我們修改服務(wù)端的登錄邏輯如下(完整代碼小伙伴們可以參考之前的 手把手教大家在 gRPC 中使用 JWT 完成身份校驗(yàn) 一文):
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());
}
}
}
小伙伴們看到,在登錄失敗時(shí)我們通過 responseObserver.onError 方法將異常信息寫回到客戶端。這個(gè)方法的參數(shù)是一個(gè) Throwable 對(duì)象,對(duì)于這個(gè)對(duì)象,在 Status 這個(gè)枚舉類中定義了一些常見的值,分別如下:
- OK(0):請(qǐng)求成功。
- CANCELLED(1):操作被取消。
- UNKNOWN(2):未知錯(cuò)誤。
- INVALID_ARGUMENT(3):客戶端給了無效的請(qǐng)求參數(shù)。
- DEADLINE_EXCEEDED(4):請(qǐng)求超過了截止時(shí)間。
- NOT_FOUND(5):請(qǐng)求資源未找到。
- ALREADY_EXISTS(6):添加的內(nèi)容已經(jīng)存在。
- PERMISSION_DENIED(7):請(qǐng)求權(quán)限不足。
- RESOURCE_EXHAUSTED(8):資源耗盡。
- FAILED_PRECONDITION(9):服務(wù)端上為準(zhǔn)備好。
- ABORTED(10):請(qǐng)求被中止。
- OUT_OF_RANGE(11):請(qǐng)求超出范圍。
- UNIMPLEMENTED(12):未實(shí)現(xiàn)的操作。
- INTERNAL(13):服務(wù)內(nèi)部錯(cuò)誤。
- UNAVAILABLE(14):服務(wù)不可用。
- DATA_LOSS(15):數(shù)據(jù)丟失或者損毀。
- UNAUTHENTICATED(16):請(qǐng)求未認(rèn)證。
系統(tǒng)默認(rèn)給出的請(qǐng)求類型大致上就這些。當(dāng)然,如果這些并不能滿足你的需求,我們也可以擴(kuò)展這個(gè)枚舉類。
2. 客戶端處理異常
當(dāng)服務(wù)端給出異常信息之后,客戶端的處理分為兩種情況。
2.1 異步請(qǐng)求
如果客戶端是異步請(qǐng)求,則直接在異常回調(diào)中處理即可,如下:
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 同步請(qǐng)求
如果客戶端請(qǐng)求是同步阻塞請(qǐng)求,那么就要通過異常捕獲的方式獲取服務(wù)端返回的異常信息了,如下:
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());
}
}
}
同步阻塞請(qǐng)求就通過異常捕獲去獲取服務(wù)端返回的異常信息即可。
3. 題外話
最后,再來和小伙伴們說一個(gè)提高 gRPC 數(shù)據(jù)傳輸效率的小技巧,那就是傳輸?shù)臄?shù)據(jù)可以使用 gzip 進(jìn)行壓縮。
具體處理方式就是在客戶端調(diào)用 withCompression 方法指定數(shù)據(jù)壓縮,如下:
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());
}
}
}
好啦,一個(gè)關(guān)于 gRPC 的小小知識(shí)點(diǎn)~