概念

定义服务

默认情况下, gRPC 使用 protocol buffers 作为接口定义语言. 例如

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}
  • 单个请求, 单个响应

    rpc SayHello(HelloRequest) returns (HelloResponse){
    }
    
  • 单个请求, 响应为一个message顺序流

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
    }
    
  • 请求顺序流, 单个响应

    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
    }
    
  • 请求顺序流, 响应顺序流

    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
    }
    

使用 api

服务端: 服务器实现了声明的服务并运行一个 gRPC server 来处理请求

客户端: 有一个本地的对象称为 stub (其他语言可能更喜欢使用 client)

同步 VS 异步

同步 RPC 调用会一直阻塞直到服务器端返回数据.

异步 RPC 则不会一直等待服务器返回.

RPC 的生命周期

单个请求, 单个响应

  • 一旦 client 在 stub/client 对象调用方法时, server 端会通知 RPC 为该次请求调用相应的 client 的元数据(metadata)
  • server 端也可以返回它自己的 initial metadata (这必须在返回任何的 response 之前发送) 或者等待 client 的请求 message.
  • 一旦 server 已经有了 client 的请求 message, 它就可以处理了. 返回返回 response 及状态详情
  • 如果状态是 OK, 则 client 就会获取 response, 在 client 端就完成了整个调用.

server 为 streaming RPC

这跟上面的差不多, 除了 server 会返回一个 response stream .

即 response , status 状态, 结尾的 metadata

client 为 stream RPC

这跟上面的差不多, 除了 client 发送的是一个 request stream .

双向 streaming RPC

deadlines/timeouts

在 client 中, 允许设置等待多长时间, 超出则会触发 DEADLINE_EXCEDED 错误.

在 server 中, 它可以查询一个特定的 RPC 请求是否 timeouts 或者还有多久才完成 RPC

RPC 终止

client 和 server 都是独立的, 本地调用是否成功的结论, 它们可能不一致.

取消 RPC

client 和 server 在任意时间都可以取消一个 RPC.

注意, 它不是 undo, 即不会回滚的.

metadata 元数据

metadata 是一些关于特定 RPC 请求(例如 认证), 由一堆 k-v 键值对组成的. key 为 strings 类型, value 一般也是 strings 类型(也可以是二进制类型).

metadata 对于 gRPC 自身不是透明的.

访问 metadata 是取决于特定语言的.

channels

gRPC channel 提供了一个 connection 连接到 server 的特定的 host 和 port , 当创建一个 client stub 时会被使用.

client 可以指定 channel 的参数来修改 gRPC 的默认行为. 例如 message 的压缩开关.

一个 channel 有状态, 包含 connectedidel

认证

你可以使用 gRPC 支持的机制 - SSL/TLS 带有或不带 Google token-based 认证, 或者你可以添加你自定义的认证插件来扩展.

类型

  • Channel credentials
  • Call credentials

也可以使用组合: CompositeChannelCredentials

示例代码

没加密或没认证:


ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
    .usePlaintext(true)
    .build();
GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);

server SSL/TLS

Server server = ServerBuilder.forPort(8443)
    // Enable TLS
    .useTransportSecurity(certChainFile, privateKeyFile)
    .addService(TestServiceGrpc.bindService(serviceImplementation))
    .build();
server.start();

client SSL/TLS

// With server authentication SSL/TLS
ManagedChannel channel = ManagedChannelBuilder.forAddress("myservice.example.com", 443)
    .build();
GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);

// With server authentication SSL/TLS; custom CA root certificates; not on Android
ManagedChannel channel = NettyChannelBuilder.forAddress("myservice.example.com", 443)
    .sslContext(GrpcSslContexts.forClient().trustManager(new File("roots.pem")).build())
    .build();
GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);

错误码

doc

例子

grpc-java-examples

生成Java代码

https://github.com/grpc/grpc-java/blob/master/README.md

典型的 pom.xml

<project>
    <dependencies>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.5.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-all -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
            <version>1.8.0</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.8.0</version>
        </dependency>

    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>


            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.5.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>

将你的 proto 文件, 放在 src/main/proto 目录下即可. 然后打包的时候, 插件会自动生成相应的 grpc 代码到 target/generated-sources/protobuf 目录下面了.

打包时输出到指定目录

            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.5.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected.classifier}</pluginArtifact>
                    <outputDirectory>src/main/java</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>

                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

client 示例代码

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    private ManagedChannel channel;
    private GreeterGrpc.GreeterBlockingStub blockingStub;

    @PostConstruct
    public void init() {
        channel = ManagedChannelBuilder.forAddress("127.0.0.1", 8999).usePlaintext(true).build();
        blockingStub = GreeterGrpc.newBlockingStub(channel);

        GreeterMessage.GreeterRequest request = GreeterMessage.GreeterRequest.newBuilder().setName(" world").build();
        GreeterMessage.GreeterResponse response = blockingStub.sayHello(request);
        System.out.println(response.getName());
    }
}

server 示例代码

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(DemoApplication.class, args);
        server.awaitTermination();
    }

    private static Server server;

    @PostConstruct
    public void init() throws IOException, InterruptedException {
        server = ServerBuilder.forPort(8999).addService(new GreeterImpl()).build().start();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                // Use stderr here since the logger may have been reset by its JVM shutdown hook.
                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                server.shutdown();
                System.err.println("*** server shut down");
            }
        });
        server.awaitTermination();
    }

    @PreDestroy
    public void detroy() {
        if (server != null) {
            server.shutdown();
        }
    }
}

常见问题收集

传输文件(或二进制)

在 proto 文件中将该字段定义为 bytes 类型即可(在Java中时, 它表示是 ByteString 类型)