gRPC是Google開源的新一代RPC框架,官網是http://www.grpc.io。正式發布於2016年8月,技術棧非常的新,基於HTTP/2,netty4.1,proto3。雖然目前在工程化方面gRPC還非常不足,但它也值得我們好好研究它,學習他。
1. 使用場景
按照Google的說法,使用廣泛,但主要使用場景還是在移動端:
Efficiently connecting polyglot services in microservices style architecture(微服務、多語言)
Connecting mobile devices, browser clients to backend services
Generating efficient client libraries
2. HTTP/2
HTTP/2,主要是基於Google發布的SPDY協議,是自HTTP/1.1從1999年發布16年后的首次更新。Servlet4.0將完全支持HTTP/2。目前支持HTTP/2的瀏覽器還不多,主要也只是支持基於TLS的HTTP/2。下圖是在caniuse.com網站統計的支持HTTP/2的瀏覽器,可以看到chrome也是在41版本后開始的,IE根本不支持。
HTTP/1.1會有什么問題呢?
假設一個網頁需要加載幾十個資源(css、js、jpg、等等),等到html文件加載成功后,瀏覽器會一個一個請求這些資源,並等待服務器按順序一個一個返回。這將會非常耗時。
與HTTP/1.1標准比較,HTTP/2帶來很多功能,如:
bidirectional streaming
flow control
header compression
multiplexing requests over a single TCP connection
在此不做詳解,還不清楚的同學自行Google,推薦一些入門鏈接
http://www.ruanyifeng.com/blog/2016/08/http.html
https://imququ.com/post/protocol-negotiation-in-http2.html
3. HTTP/2局限性
如上圖,gRPC離真正可工程化的rpc框架還有一段路要走,缺的組件也很多,如
- Configuration(配置化)
- Service Discovery(服務發現)
- 服務治理等等
也就是目前gRPC想用到微服務后端,需要自己開發很多東西。
4. 使用樣例
-
下載gRPC源碼
git clone git@github.com:grpc/grpc-java.git
gRPC支持了很多平台,當然我們講解的是Java版本,目錄結構如下圖:
-
編譯
首先我們先不編譯grpc code generation plugin(主要用於proto3從.proto文件編譯出Java文件的,一般不用修改)。在根目錄下新建gradle.properties文件,然后添加
skipCodegen=true這一行到文件中。
grpc-java has a C++ code generation plugin for protoc. Since many Java developers don't have C compilers installed and don't need to modify the codegen, the build can skip it. To skip, create the file
<project-root>/gradle.properties
and addskipCodegen=true
.
Java、maven版本如下
Java版本需要1.8
maven 3.2
- 編譯代碼
./gradlew build
- 安裝編譯好的jar包到本地maven庫
./gradlew install
-
樣例
先建立maven項目,定義服務.proto文件
在此默認大家對proto的語法已經熟悉,至少是可以使用的程度。借用Google官方的例子。
syntax = "proto3";
option java_multiple_files = true;
// 生產的Java的包
option java_package = "io.grpc.examples.helloworld.generated";
// 生產的Java類名
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// service 定義服務
service Greeter {
// 服務的一個方法
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
添加依賴到pom.xml文件,使用剛才install的版本1.1.0-SNAPSHOT,build里的插件是用來由proto文件生產Java源碼用的
在測試項目文件夾下,生產Java代碼
$ mvn compile
生產的代碼在target下,將其拷貝到自己的項目里
-
編寫測試代碼,借用Google官方代碼,懶得寫了。
Server
public class HelloWorldServer { private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName()); private Server server; private void start() throws IOException { /* The port on which the server should run */ int port = 50051; server = ServerBuilder.forPort(port) .addService(new GreeterImpl()) .build() .start(); logger.info("Server started, listening on " + port); 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"); HelloWorldServer.this.stop(); System.err.println("*** server shut down"); } }); } private void stop() { if (server != null) { server.shutdown(); } } /** * Await termination on the main thread since the grpc library uses daemon threads. */ 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 HelloWorldServer server = new HelloWorldServer(); server.start(); server.blockUntilShutdown(); } static class GreeterImpl extends GreeterGrpc.GreeterImplBase { @Override public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) { HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } } }
Client
public class HelloWorldClient { private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName()); private final ManagedChannel channel; private final GreeterGrpc.GreeterBlockingStub blockingStub; /** Construct client connecting to HelloWorld server at {@code host:port}. */ public HelloWorldClient(String host, int port) { this(ManagedChannelBuilder.forAddress(host, port) // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid // needing certificates. .usePlaintext(true)); } /** Construct client for accessing RouteGuide server using the existing channel. */ HelloWorldClient(ManagedChannelBuilder<?> channelBuilder) { channel = channelBuilder.build(); blockingStub = GreeterGrpc.newBlockingStub(channel); } public void shutdown() throws InterruptedException { channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } /** Say hello to server. */ public void greet(String name) { logger.info("Will try to greet " + name + " ..."); HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { response = blockingStub.sayHello(request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; } logger.info("Greeting: " + response.getMessage()); } /** * Greet server. If provided, the first element of {@code args} is the name to use in the * greeting. */ public static void main(String[] args) throws Exception { HelloWorldClient client = new HelloWorldClient("localhost", 50051); try { /* Access a service running on the local machine on port 50051 */ String user = "world"; if (args.length > 0) { user = args[0]; /* Use the arg as the name to greet if provided */ } client.greet(user); } finally { client.shutdown(); } } }
如上可以看到,用gRPC編寫服務是非常簡單的。幾行代碼搞定。
源碼分析導讀
gRPC的代碼與dubbo、rocketmq相比,還是很少的,主要是因為目前很多組件還沒有。后面將會根據以下內容來講解源碼
-
通信
-
消息編解碼
-
steam流
- 框架
附錄
gRPC 官方文檔中文版
- https://doc.oschina.net/grpc
GRPC的產生動機和設計原則
- http://www.jianshu.com/p/8cc077f6dbb9
gRPC學習筆記
- https://skyao.gitbooks.io/leaning-grpc/content/