Java 使用 Grpc 快速入門

Grpc的原理

一個RPC框架必須有兩個基礎的組成部分:數據的序列化和進程數據通信的交互方式。

對於序列化gRPC採用了自家公司開源的Protobuf。什麼是Protobuf?
Google Protocol Buffer(簡稱 Protobuf) 是一種與語言無關,平臺無關的可擴展機制,用於序列化結構化數據。
使用Protocol Buffers 可以一次定義結構化的數據,然後可以使用特殊生成的源代碼輕鬆地在各種數據流中使用各種語言編寫和讀取結構化數據。

而關於進程間的通訊,無疑是Socket。Java方面gRPC同樣使用了成熟的開源框架Netty。使用Netty Channel作爲數據通道。傳輸協議使用了HTTP2。

通過以上的分析,我們可以將一個完整的gRPC流程總結爲以下幾步:

通過.proto文件定義傳輸的接口和消息體。
通過protocol編譯器生成server端和client端的stub程序。
將請求封裝成HTTP2的Stream。
通過Channel作爲數據通信通道使用Socket進行數據傳輸。

使用Java 結合 Grpc 實現遠程服務調用

下面創建一個SpringBoot 多模塊項目 與 Grpc 結合實現遠程服務調用
項目結構:

SpringBoot-Grpc-Lib

定義了兩個.proto文件用於測試,並配置Gradle 插件,以便快速生成Java相關的實體類。
user.proto

syntax = "proto3";

option java_package = "com.dashuai.learning.grpc.lib.proto";

service User {
    rpc SaveUser (UserData) returns (UserResponse) {
    }
    rpc GetUser (UserRequest) returns (UserData) {
    }

}
message UserData {
    int32 id = 1;
    string name = 2;
    string sex = 3;
    int32 age = 4;
    string remark = 5;
}
message UserRequest {
    int32 id = 1;
}
message UserResponse {
    bool isSuccess = 1;
}

上述聲明瞭兩個RPC服務,分別是SaveUser()和GetUser(),顧名思義,保存一個用戶和獲取一個用戶信息。
該模塊項目中,我配置了protobuf-gradle-plugin插件方便構建生成對應的Java類,build.gradle如下:

apply plugin: 'com.google.protobuf'
buildscript {
    repositories {
        mavenLocal()
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1'
    }
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.5.1"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1'
        }
    }
    //配置生成輸出目錄
    generatedFilesBaseDir = "$projectDir/src"
    generateProtoTasks {
        all()*.plugins {
            grpc { outputSubDir = "java" }
        }
    }
}
sourceSets {
    main {
        proto {
            // In addition to the default 'src/main/proto'
            srcDir 'src/main/protobuf'
            srcDir 'src/main/protocolbuffers'
            include '**/*.protodevel'
        }
        java {
        }
    }
    test {
        proto {
            // In addition to the default 'src/test/proto'
            srcDir 'src/test/protocolbuffers'
        }
    }
}

配置無誤後,點擊右側Tasks的GenerateProto生成對應的Java類

上述配置的.proto對應生成的Java文件如下圖:

到此,服務的定義就完成了,下面分別對定義的服務進行實現。

SpringBoot-Grpc-Server

對應的服務端,實現服務所做業務,業務實現代碼如下,使用一個全局Map存放用戶信息,模擬數據庫保存。

@GrpcService(UserOuterClass.class)
public class UserService extends UserGrpc.UserImplBase {

    private final static Logger log = LoggerFactory.getLogger(UserService.class);

    private Map<Integer, UserOuterClass.UserData> map = new HashMap<>();

    @Override
    public void saveUser(UserOuterClass.UserData request, StreamObserver<UserOuterClass.UserResponse> responseObserver) {
        try {
            map.put(request.getId(), request);
        } catch (Exception e) {
            log.error("保存失敗了!msg:{}", e.getMessage());
        }
        UserOuterClass.UserResponse.Builder builder = UserOuterClass.UserResponse.newBuilder().setIsSuccess(true);
        responseObserver.onNext(builder.build());
        responseObserver.onCompleted();

    }

    @Override
    public void getUser(UserOuterClass.UserRequest request, StreamObserver<UserOuterClass.UserData> responseObserver) {
        responseObserver.onNext(map.get(request.getId()));
        responseObserver.onCompleted();
    }
}

SpringBoot-Grpc-Client

對應的客戶端,調用對應的服務

@Service
@Slf4j
public class UserClientService {
    @GrpcClient("local-grpc-server")
    private Channel serverChannel;

    public boolean saveUser(UserOuterClass.UserData userData) {
        UserGrpc.UserBlockingStub userBlockingStub = UserGrpc.newBlockingStub(serverChannel);
        System.out.println(JSONParseUtils.object2JsonString(userData));
        UserOuterClass.UserResponse response = userBlockingStub.saveUser(userData);
        return response.getIsSuccess();
    }

    public UserOuterClass.UserData getUserData(int id) {
        UserGrpc.UserBlockingStub userBlockingStub = UserGrpc.newBlockingStub(serverChannel);
        UserOuterClass.UserData userData = userBlockingStub.getUser(UserOuterClass.UserRequest.newBuilder().setId(id).build());
        System.out.println(JSONParseUtils.object2JsonString(userData));
        return userData;

    }
}

使用兩個API調用測試,將請求體轉換爲JSON格式,在使用JsonFormat轉換成對應的對象進行服務的調用,
這裏我只是爲了測試而這樣做,實際使用可能有所區別。

   @PostMapping(value = "/user", produces = "application/json;charset=UTF-8")
    public boolean saveUser(HttpServletRequest request) throws IOException {
        String json = new String(IoUtils.toByteArray(request.getInputStream()), request.getCharacterEncoding());
        return userClientService.saveUser(ProtobufUtils.jsonToPf(json, UserOuterClass.UserData.newBuilder()));
    }

    @GetMapping(value = "/user/{id}")
    @ApiOperation(value = "獲取User實例", notes = "獲取用戶信息", response = UserOuterClass.UserData.class)
    @ApiImplicitParam(name = "id", value = "用戶id", required = true, dataType = "Integer")
    public ApiResult getUser(@PathVariable Integer id) throws InvalidProtocolBufferException {
        UserOuterClass.UserData userData = userClientService.getUserData(id);
        return ApiResult.prepare().success(ProtobufUtils.pfToJson(userData));
    }

調用測試,錄入一個用戶信息:

查詢用戶信息:

到此,調用流程演示完畢!
測試項目的源碼已上傳到Github:
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-Grpc

參考鏈接:
https://www.jianshu.com/p/30ef9b3780d9
Protocol Buffers 3 簡明教程
https://juejin.im/entry/59bb30f76fb9a00a616f1b73

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章