目录
gRPC相关介绍
什么是gPRC
gPRC的优点
gPRC的缺点
gPRC定位
协议缓冲区(Protocol Buffers)
四种调用方式
gRPC开发三大步骤
第一步:定义和编写proto服务文件
第二步:proto文件转化为gRPC代码
第三步:调用gRPC代码
详细开发细节
Server端-Java
Client端-Java
Client端-Golang
步骤一:准备插件
步骤二:准备proto服务描述文件
步骤三:基于proto文件生成gRPC代码
步骤四:编写客户端代码调用gRPC
Client端-Python
步骤一:安装grpc和相关工具:
步骤二:忽略
步骤三:基于proto文件生成gRPC代码
步骤四:编写客户端代码调用gRPC
避坑指南
Java
Golang
Python
总结
gRPC相关介绍
什么是gPRC
基于http2+protocol buffer技术,简单说就是编码压缩+缓存,追求高效率。
gPRC的优点
1、高性能,毕竟是rpc技术,这个优点是我们选择gRPC的主要出发点。
2、报文可在stream上双向流传输,这个是http协议做不到的,用到双向流的场景首选gRPC。
gPRC的缺点
1、开发步骤很复杂! 这个是被gRPC劝退的主要原因,开发过程一不小心就踩坑。
2、目前支持的不充分。简单说就是太新了,像浏览器啥的支持的不好,导致开发调试没那么方便。
gPRC定位
gPRC是CNCF里唯一的传输协议项目,它的定位就是云原生时代分布式、微服务集群的核心底层通信服务。基于这个定位,我们就有必须学习它的理由。
协议缓冲区(Protocol Buffers)
这个是gRPC协议的核心模块,.proto文件、protoc工具、protobuf插件等跟它有关的名词充斥着整个gRPC开发过程。
Protocol Buffers是google推出的一种序列化的协议,它的定位就像json、xml、对象二进制流等一样,是用于对象传输过程中编码和解码的。
它比json还要精简,主要体现在编码时丢弃了一些不必要的内容,解码的时候抛弃了全扫描的方式。
四种调用方式
1、同步函数式,一来一回
rpc SayHello(HelloRequest) returns (HelloResponse);
2、 客户端流式,多来一回
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
3、服务端流式,一来多回
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
4、双向流,多来多回
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
gRPC开发三大步骤
第一步:定义和编写proto服务文件
像定义API一样,直接编写一个.proto后缀的服务描述文件,如下:
syntax = "proto3";option java_multiple_files = true;
option java_package = "com.jingtao.library";
option java_outer_classname = "BookServiceProto";
option objc_class_prefix = "HLW";package mybook;
// The greeting service definition.
service BookService {// Sends a greetingrpc check (RequestData) returns (Book) {}
}// The request message containing the user's name.
message RequestData {string name = 1;
}message Book {string name = 1;string auther = 2;int32 price = 3;
}
syntax定义协议版本
option部分是跟着语言耦合的部分
package定义了服务的包名
service定义了服务的函数
message定义了服务函数需要打的入参、出参等对象
第二步:proto文件转化为gRPC代码
该步骤是把上面的.proto文件通过工具或插件生成可以被程序直接调用的gRPC代码,因为要被程序调用,所以这一步是与语言紧耦合的,不同语言转换的方式各不相同。
第三步:调用gRPC代码
到了第三步就相对轻松了,第二步生成的gRPC文件已经以接口的方式将服务封装好了,我们要干的只是import它,然后调用它。
详细开发细节
Server端-Java
java语言.proto文件转化为gRPC有三种方式
方式一:独立转换
在操作系统中安装转换可视化或者命令工具,例如protoc(下载地址https://github.com/protocolbuffers/protobuf/releases),在操作系统层做好转换后将转换后的代码copy到代码项目中。
方式二:开发工具插件转化
以IDEA为例,转换的插件有GenProtoBuf、Protocol Buffer,而且都是免费的。
方式三:依赖转换
直接在项目中引入相关依赖插件,利用它们生成代码。例如java中的protobuf-java-util
我采用的是方式三,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>BookFactory</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><protoc.version>3.12.0</protoc.version><grpc.version>1.56.1</grpc.version></properties><dependencies><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.12.0</version></dependency><!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util --><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java-util</artifactId><version>3.12.0</version></dependency><!-- https://mvnrepository.com/artifact/io.grpc/grpc-all --><dependency><groupId>io.grpc</groupId><artifactId>grpc-all</artifactId><version>1.56.1</version></dependency></dependencies><build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.6.2</version></extension></extensions><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.7.0</version><configuration><source>1.8</source><target>1.8</target></configuration></plugin><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version><configuration><protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact><protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot><outputDirectory>${project.basedir}/src/main/java/</outputDirectory><clearOutputDirectory>false</clearOutputDirectory></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins></build></project>
双击Plugin中protobuf的compile生成服务bean,通过custom生成可直接被上层调用的gRPC。
服务端引用并实现grpc代码
<dependency><groupId>io.grpc</groupId><artifactId>grpc-all</artifactId><version>1.56.1</version></dependency>
服务端代码如下:
package com.jingtao.library;import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.stub.StreamObserver;import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;public class BookServer {private static final Logger logger = Logger.getLogger(BookServer.class.getName());private Server server;int port = 8088;private void start() throws IOException {server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()).addService(new BookImpl()).build().start();logger.info("Server started, listening on " + port);Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic 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");try {BookServer.this.stop();} catch (InterruptedException e) {e.printStackTrace(System.err);}System.err.println("*** server shut down");}});}private void stop() throws InterruptedException {if (server != null) {server.shutdown().awaitTermination(30, TimeUnit.SECONDS);}}static class BookImpl extends BookServiceGrpc.BookServiceImplBase {@Overridepublic void check(RequestData req, StreamObserver<Book> responseObserver) {Book reply = Book.newBuilder().setName(req.getName()).setAuther("Shakespeare").setPrice(35).build();responseObserver.onNext(reply);responseObserver.onCompleted();}}private void blockUntilShutdown() throws InterruptedException {if (server != null) {server.awaitTermination();}}/*** Main launches the server from the command line.*/public static void main(String[] args) throws IOException, InterruptedException {final BookServer server = new BookServer();server.start();server.blockUntilShutdown();}}
Client端-Java
Java客户端前面部分跟服务端一样,只有最后一步不同:
package com.jingtao.library;import io.grpc.*;import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;public class BookClientor {private static final Logger logger = Logger.getLogger(BookClientor.class.getName());private final BookServiceGrpc.BookServiceBlockingStub blockingStub;public BookClientor(Channel channel) {blockingStub = BookServiceGrpc.newBlockingStub(channel);}public void say(String name) {logger.info("Will try to greet " + name + " ...");RequestData request = RequestData.newBuilder().setName(name).build();Book response;try{response = blockingStub.check(request);}catch (StatusRuntimeException e) {logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());return;}logger.info("Invoke say result is : " + response.getName()+ " " + response.getAuther()+ " " + response.getPrice());}public static void main(String[] args) throws Exception{String user = "les miserables";// Access a service running on the local machine on port 50051String target = "localhost:8088";ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();try {BookClientor client = new BookClientor(channel);client.say(user);} finally {// ManagedChannels use resources like threads and TCP connections. To prevent leaking these// resources the channel should be shut down when it will no longer be used. If it may be used// again leave it running.channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);}}
}
Client端-Golang
步骤一:准备插件
给protoc准备go相关插件和工具
git clone https://github.com/golang/protobuf.git $GOPATH/src/github.com/golang/protobuf
cd $GOPATH/src/
go install github.com/golang/protobuf/protoc-gen-go@latest
这一步的目标是准备好protoc-gen-go,搞定后一定要确保包含protoc-gen-go的bin在path中,可以被protoc用到。
步骤二:准备proto服务描述文件
注意需要显式的指定所生成gRPC代码的包名
option go_package="./;book";
步骤三:基于proto文件生成gRPC代码
准备好proto文件后,执行以下命令生成go的gRPG文件
protoc -I proto/ --go_out=plugins=grpc:proto proto/book_route.proto
步骤四:编写客户端代码调用gRPC
package bookimport (bookgrpc "book-client-go/proto""fmt""golang.org/x/net/context""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func MyClient() {conn, err := grpc.Dial(":8088", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {// handle errorpanic(err)}defer conn.Close()client := bookgrpc.NewBookServiceClient(conn)req := bookgrpc.RequestData{Name: "world",}reply, err := client.Check(context.Background(), &req)if err != nil {fmt.Println("client.bookservice error:", err)return}fmt.Printf("get msg from server:[%v] \n", reply)}
Client端-Python
步骤一:安装grpc和相关工具:
pip install grpcio
pip install grpcio-tools
pip install protobuf
步骤二:忽略
python不需要单独设置option_package,因为最终生成的gRPC代码在python中放在哪个目录下就引入哪个目录好了。
步骤三:基于proto文件生成gRPC代码
然后执行以下python命令将proto文件转换为gRPC的python代码
python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=. ./book_route.proto
步骤四:编写客户端代码调用gRPC
import grpcfrom book import book_route_pb2,book_route_pb2_grpcdef run():conn = grpc.insecure_channel('127.0.0.1:8088')client = book_route_pb2_grpc.BookServiceStub(channel=conn)reqeust = book_route_pb2.RequestData(name='jingtao')respnse = client.check(reqeust)print("received name:", respnse.name)print("received auther:", respnse.auther)print("received:", respnse.price)if __name__ == '__main__':run()
避坑指南
Java
1、java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
代码全部都编译过并切启动成功的前提下,客户端和服务端进行grpc交互时遇到这个报错
核心是jdk版本与grpc依赖包版本不一致导致的
我用的jdk1.8+protoc3.12.0+grpc1.57.0就会出这个错误,换成jdk1.8+protoc3.12.0+grpc1.56.1后问题解决。
2、UnusedPrivateParameter unused:
通过插件基于proto文件生产的gprc代码中出现了UnusedPrivateParameter编译错误,核心原因是maven导入的依赖版本与实际所需要的依赖版本不一致导致的。
错误配置:
正确配置:
Golang
1、protoc-gen-go: unable to determine Go import path for "book_route.proto"
protoc -I proto/ --go_out=plugins=grpc:proto proto/book_route.proto的时候报错:
原因是我从java项目中直接copy了proto过来,但里面缺少go需要的配置,修改proto后问题解决。
所以gRPC开发过程中proto文件不能直接复制,需要摘选出service和message部分,这是一个比较讨厌的地方。
Python
1、Cannot unpack file C:\Users\MGTV\AppData\Local\Temp\pip-unpack-iee13adu\simple
执行pip安装grpc时报错,原因是因为pip的源有问题。
开始pip安装网络太慢所以用到代理:
pip install grpcio-i http://pypi.mirrors.ustc.edu.cn/pypi/simple/ --trusted-host pypi.mirrors.ustc.edu.cn
把http://mirrors.aliyun.com/pypi/simple/替换成https://pypi.mirrors.ustc.edu.cn/simple/也解决不了,后来直接用了最原始的命令,没有走代理,问题解决
2、生成的_pd2.py缺少request和response的负载对象导致开发过程中编译不过
这个问题是grpc_tools在1.44版本以后由预定义变成了运行中自动生成对象了,不影响正常运行,这样虽然使代码更简洁了,但是可读性确实变差了。
总结
本文用java实现了gRPC服务端,然后分别用java、go、python来调用,只给了“一来一回”同步函数式调用,其实gRPC支持4中调用方式,真实情况比复杂的多。但比起对接gRPC来说,开发环境的准备更复杂,坑也很多,希望通过本篇博文能把gRPC开发所有步骤和需要注意的地方都解释清楚。