1. RPC 入門
1.1 RPC 框架原理
RPC 框架的目標就是讓遠程服務調用更加簡單、透明,RPC 框架負責屏蔽底層的傳輸方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二進制)和通訊細節。服務調用者能夠像調用本地接口同樣調用遠程的服務提供者,而不須要關心底層通訊細節和調用過程。前端
RPC 框架的調用原理圖以下所示:java
1.2 業界主流的 RPC 框架
業界主流的 RPC 框架總體上分爲三類:git
- 支持多語言的 RPC 框架,比較成熟的有 Google 的 gRPC、Apache(Facebook)的 Thrift;
- 只支持特定語言的 RPC 框架,例如新浪微博的 Motan;
- 支持服務治理等服務化特性的分佈式服務框架,其底層內核仍然是 RPC 框架, 例如阿里的 Dubbo。
隨着微服務的發展,基於語言中立性原則構建微服務,逐漸成爲一種主流模式,例如對於後端併發處理要求高的微服務,比較適合採用 Go 語言構建,而對於前端的 Web 界面,則更適合 Java 和 JavaScript。github
所以,基於多語言的 RPC 框架來構建微服務,是一種比較好的技術選擇。例如 Netflix,API 服務編排層和後端的微服務之間就採用 gRPC 進行通訊。後端
1.3 gRPC 簡介
gRPC 是一個高性能、開源和通用的 RPC 框架,面向服務端和移動端,基於 HTTP/2 設計。數組
1.3.1 gRPC 概覽
gRPC 是由 Google 開發並開源的一種語言中立的 RPC 框架,當前支持 C、Java 和 Go 語言,其中 C 版本支持 C、C++、Node.js、C# 等。當前 Java 版本最新 Release 版爲 1.5.0,Git 地址以下:promise
https://github.com/grpc/grpc-java緩存
gRPC 的調用示例以下所示:網絡
1.3.2 gRPC 特色
- 語言中立,支持多種語言;
- 基於 IDL 文件定義服務,經過 proto3 工具生成指定語言的數據結構、服務端接口以及客戶端 Stub;
- 通訊協議基於標準的 HTTP/2 設計,支持雙向流、消息頭壓縮、單 TCP 的多路複用、服務端推送等特性,這些特性使得 gRPC 在移動端設備上更加省電和節省網絡流量;
- 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高性能序列化框架,基於 HTTP/2 + PB, 保障了 RPC 調用的高性能。
2. gRPC 服務端建立
以官方的 helloworld 爲例,介紹 gRPC 服務端建立以及 service 調用流程(採用簡單 RPC 模式)。數據結構
2.1 服務端建立業務代碼
服務定義以下(helloworld.proto):
service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
服務端建立代碼以下(HelloWorldServer 類):
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(); ...
其中,服務端接口實現類(GreeterImpl)以下所示:
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(); } }
2.2 服務端建立流程
gRPC 服務端建立採用 Build 模式,對底層服務綁定、transportServer 和 NettyServer 的建立和實例化作了封裝和屏蔽,讓服務調用者不用關心 RPC 調用細節,總體上分爲三個過程:
- 建立 Netty HTTP/2 服務端;
- 將須要調用的服務端接口實現類註冊到內部的 Registry 中,RPC 調用時,能夠根據 RPC 請求消息中的服務定義信息查詢到服務接口實現類;
- 建立 gRPC Server,它是 gRPC 服務端的抽象,聚合了各類 Listener,用於 RPC 消息的統一調度和處理。
下面咱們看下 gRPC 服務端建立流程:
gRPC 服務端建立關鍵流程分析:
-
NettyServer 實例建立:gRPC 服務端建立,首先須要初始化 NettyServer,它是 gRPC 基於 Netty 4.1 HTTP/2 協議棧之上封裝的 HTTP/2 服務端。NettyServer 實例由 NettyServerBuilder 的 buildTransportServer 方法構建,NettyServer 構建完成以後,監聽指定的 Socket 地址,便可實現基於 HTTP/2 協議的請求消息接入。
-
綁定 IDL 定義的服務接口實現類:gRPC 與其它一些 RPC 框架的差別點是服務接口實現類的調用並非經過動態代理和反射機制,而是經過 proto 工具生成代碼,在服務端啓動時,將服務接口實現類實例註冊到 gRPC 內部的服務註冊中心上。請求消息接入以後,能夠根據服務名和方法名,直接調用啓動時註冊的服務實例,而不須要經過反射的方式進行調用,性能更優。
-
gRPC 服務實例(ServerImpl)構建:ServerImpl 負責整個 gRPC 服務端消息的調度和處理,建立 ServerImpl 實例過程當中,會對服務端依賴的對象進行初始化,例如 Netty 的線程池資源、gRPC 的線程池、內部的服務註冊類(InternalHandlerRegistry)等,ServerImpl 初始化完成以後,就能夠調用 NettyServer 的 start 方法啓動 HTTP/2 服務端,接收 gRPC 客戶端的服務調用請求。
2.3 服務端 service 調用流程
gRPC 的客戶端請求消息由 Netty Http2ConnectionHandler 接入,由 gRPC 負責將 PB 消息(或者 JSON)反序列化爲 POJO 對象,而後經過服務定義查詢到該消息對應的接口實例,發起本地 Java 接口調用,調用完成以後,將響應消息反序列化爲 PB(或者 JSON),經過 HTTP2 Frame 發送給客戶端。 流程並不複雜,可是細節卻比較多,整個 service 調用能夠劃分爲以下四個過程:
- gRPC 請求消息接入;
- gRPC 消息頭和消息體處理;
- 內部的服務路由和調用;
- 響應消息發送。
2.3.1 gRPC 請求消息接入
gRPC 的請求消息由 Netty HTTP/2 協議棧接入,經過 gRPC 註冊的 Http2FrameListener,將解碼成功以後的 HTTP Header 和 HTTP Body 發送到 gRPC 的 NettyServerHandler 中,實現基於 HTTP/2 的 RPC 請求消息接入。
gRPC 請求消息接入流程以下:
關鍵流程解讀以下:
-
Netty 4.1 提供了 HTTP/2 底層協議棧,經過 Http2ConnectionHandler 及其依賴的其它類庫,實現了 HTTP/2 消息的統一接入和處理。
經過註冊 Http2FrameListener 監聽器,能夠回調接收 HTTP2 協議的消息頭、消息體、優先級、Ping、SETTINGS 等。
gRPC 經過 FrameListener 重載 Http2FrameListener 的 onDataRead、onHeadersRead 等方法,將 Netty 的 HTTP/2 消息轉發到 gRPC 的 NettyServerHandler 中;
-
Netty 的 HTTP/2 協議接入仍然是經過 ChannelHandler 的 CodeC 機制實現,它並不影響 NIO 線程模型。
所以,理論上各類協議、以及同一個協議的多個服務端實例能夠共用同一個 NIO 線程池(NioEventLoopGroup), 也能夠獨佔。
在實踐中獨佔模式廣泛會存在線程資源佔用過載問題,很容易出現句柄等資源泄漏。
在 gRPC 中,爲了不該問題,默認採用共享池模式建立 NioEventLoopGroup,全部的 gRPC 服務端實例,都統一從 SharedResourceHolder 分配 NioEventLoopGroup 資源,實現 NioEventLoopGroup 的共享。
2.3.2 gRPC 消息頭和消息體處理
gRPC 消息頭的處理入口是 NettyServerHandler 的 onHeadersRead(),處理流程以下所示:
處理流程以下:
-
對 HTTP Header 的 Content-Type 校驗,此處必須是 "application/grpc";
-
從 HTTP Header 的 URL 中提取接口和方法名,以 HelloWorldServer 爲例,它的 method 爲:"helloworld.Greeter/SayHello";
-
將 Netty 的 HTTP Header 轉換成 gRPC 內部的 Metadata,Metadata 內部維護了一個鍵值對的二維數組 namesAndValues,以及一系列的類型轉換方法(點擊放大圖片):
-
建立 NettyServerStream 對象,它持有了 Sink 和 TransportState 類,負責將消息封裝成 GrpcFrameCommand,與底層 Netty 進行交互,實現協議消息的處理;
-
建立 NettyServerStream 以後,會觸發 ServerTransportListener 的 streamCreated 方法,在該方法中,主要完成了消息上下文和 gRPC 業務監聽器的建立;
-
gRPC 上下文建立:CancellableContext 建立以後,支持超時取消,若是 gRPC 客戶端請求消息在 Http Header 中攜帶了「grpc-timeout」,系統在建立 CancellableContext 的同時會啓動一個延時定時任務,延時週期爲超時時間,一旦該定時器成功執行,就會調用 CancellableContext.CancellationListener 的 cancel 方法,發送 CancelServerStreamCommand 指令;
-
JumpToApplicationThreadServerStreamListener 的建立:它是 ServerImpl 的內部類,從命名上基本能夠看出它的用途,即從 ServerStream 跳轉到應用線程中進行服務調用,gRPC 服務端的接口調用主要經過 JumpToApplicationThreadServerStreamListener 的 messageRead 和 halfClosed 方法完成;
-
將 NettyServerStream 的 TransportState 緩存到 Netty 的 Http2Stream 中,當處理請求消息體時,能夠根據 streamId 獲取到 Http2Stream,進而根據「streamKey」還原 NettyServerStream 的 TransportState,進行後續處理。
gRPC 消息體的處理入口是 NettyServerHandler 的 onDataRead(),處理流程以下所示:
消息體處理比較簡單,下面就關鍵技術點進行講解:
-
由於 Netty HTTP/2 協議 Http2FrameListener 分別提供了 onDataRead 和 onHeadersRead 回調方法,因此 gRPC NettyServerHandler 在處理完消息頭以後須要緩存上下文,以便後續處理消息體時使用;
-
onDataRead 和 onHeadersRead 方法都是由 Netty 的 NIO 線程負責調度,可是在執行 onDataRead 的過程當中發生了線程切換,以下所示(ServerTransportListenerImpl 類):
wrappedExecutor.execute(new ContextRunnable(context) { @Override public void runInContext() { ServerStreamListener listener = NOOP_LISTENER; try { ServerMethodDefinition<?, ?> method = registry.lookupMethod(methodName); if (method == null) { method = fallbackRegistry.lookupMethod(methodName, stream.getAuthority()); }
所以,實際上它們是並行 + 交叉串行實行的,後續章節介紹線程模型時會介紹切換原則。
2.3.3 內部的服務路由和調用
內部的服務路由和調用,主要包括以下幾個步驟:
- 將請求消息體反序列爲 Java 的 POJO 對象,即 IDL 中定義的請求參數對象;
- 根據請求消息頭中的方法名到註冊中心查詢到對應的服務定義信息;
- 經過 Java 本地接口調用方式,調用服務端啓動時註冊的 IDL 接口實現類。
具體流程以下所示:
中間的交互流程比較複雜,涉及的類較多,可是關鍵步驟主要有三個:
-
解碼:對 HTTP/2 Body 進行應用層解碼,轉換成服務端接口的請求參數,解碼的關鍵就是調用 requestMarshaller.parse(input),將 PB 碼流轉換成 Java 對象;
-
路由:根據 URL 中的方法名從內部服務註冊中心查詢到對應的服務實例,路由的關鍵是調用 registry.lookupMethod(methodName) 獲取到 ServerMethodDefinition 對象;
-
調用:調用服務端接口實現類的指定方法,實現 RPC 調用,與一些 RPC 框架不一樣的是,此處調用是 Java 本地接口調用,非反射調用,性能更優,它的實現關鍵是 UnaryRequestMethod.invoke(request, responseObserver) 方法。
2.3.4 響應消息發送
響應消息的發送由 StreamObserver 的 onNext 觸發,流程以下所示:
響應消息的發送原理以下:
-
分別發送 gRPC HTTP/2 響應消息頭和消息體,由 NettyServerStream 的 Sink 將響應消息封裝成 SendResponseHeadersCommand 和 SendGrpcFrameCommand,加入到 WriteQueue 中;
-
WriteQueue 經過 Netty 的 NioEventLoop 線程進行消息處理,NioEventLoop 將 SendResponseHeadersCommand 和 SendGrpcFrameCommand 寫入到 Netty 的 Channel 中,進而觸發 DefaultChannelPipeline 的 write(Object msg, ChannelPromise promise) 操做;
-
響應消息經過 ChannelPipeline 職責鏈進行調度,觸發 NettyServerHandler 的 sendResponseHeaders 和 sendGrpcFrame 方法,調用 Http2ConnectionEncoder 的 writeHeaders 和 writeData 方法,將響應消息經過 Netty 的 HTTP/2 協議棧發送給客戶端。
須要指出的是,請求消息的接收、服務調用以及響應消息發送,屢次發生 NIO 線程和應用線程之間的互相切換,以及並行處理。所以上述流程中的一些步驟,並非嚴格按照圖示中的順序執行的,後續線程模型章節,會作分析和介紹。
3. 源碼分析
3.1 主要類和功能交互流程
3.1.1 gRPC 請求消息頭處理
gRPC 請求消息頭處理涉及的主要類庫以下:
- NettyServerHandler:gRPC Netty Server 的 ChannelHandler 實現,負責 HTTP/2 請求消息和響應消息的處理;
- SerializingExecutor:應用調用線程池,負責 RPC 請求消息的解碼、響應消息編碼以及服務接口的調用等;
- MessageDeframer:負責請求 Framer 的解析,主要用於處理 HTTP/2 Header 和 Body 的讀取。
- ServerCallHandler:真正的服務接口處理類,提供 onMessage(ReqT request) 和 onHalfClose() 方法,用於服務接口的調用。
3.1.2 gRPC 請求消息體處理和服務調用
3.1.3 gRPC 響應消息處理
須要說明的是,響應消息的發送由調用服務端接口的應用線程執行,在本示例中,由 SerializingExecutor 進行調用。
當請求消息頭被封裝成 SendResponseHeadersCommand 並被插入到 WriteQueue 以後,後續操做由 Netty 的 NIO 線程 NioEventLoop 負責處理。
應用線程繼續發送響應消息體,將其封裝成 SendGrpcFrameCommand 並插入到 WriteQueue 隊列中,由 Netty 的 NIO 線程 NioEventLoop 處理。響應消息的發送嚴格按照順序:即先消息頭,後消息體。
3.2 源碼分析
瞭解 gRPC 服務端消息接入和 service 調用流程以後,針對主要的流程和類庫,進行源碼分析,以加深對 gRPC 服務端工做原理的瞭解。
3.2.1 Netty 服務端建立
基於 Netty 的 HTTP/2 協議棧,構建 gRPC 服務端,Netty HTTP/2 協議棧初始化代碼以下所示(建立 NettyServerHandler,NettyServerHandler 類):
frameWriter = new WriteMonitoringFrameWriter(frameWriter, keepAliveEnforcer); Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, frameWriter); Http2ConnectionDecoder decoder = new FixedHttp2ConnectionDecoder(connection, encoder, frameReader); Http2Settings settings = new Http2Settings(); settings.initialWindowSize(flowControlWindow); settings.maxConcurrentStreams(maxStreams); settings.maxHeaderListSize(maxHeaderListSize); return new NettyServerHandler( transportListener, streamTracerFactories, decoder, encoder, settings, maxMessageSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos, maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos, keepAliveEnforcer);
建立 gRPC FrameListener,做爲 Http2FrameListener,監聽 HTTP/2 消息的讀取,回調到 NettyServerHandler 中(NettyServerHandler 類):
decoder().frameListener(new FrameListener());
將 NettyServerHandler 添加到 Netty 的 ChannelPipeline 中,接收和發送 HTTP/2 消息(NettyServerTransport 類):
ChannelHandler negotiationHandler = protocolNegotiator.newHandler(grpcHandler); channel.pipeline().addLast(negotiationHandler);
gRPC 服務端請求和響應消息統一由 NettyServerHandler 攔截處理,相關方法以下:
NettyServerHandler 是 gRPC 應用側和底層協議棧的橋接類,負責將原生的 HTTP/2 消息調度到 gRPC 應用側,同時將應用側的消息發送到協議棧。
3.2.2 服務實例建立和綁定
gRPC 服務端啓動時,須要將調用的接口實現類實例註冊到內部的服務註冊中心,用於後續的接口調用,關鍵代碼以下(InternalHandlerRegistry 類):
Builder addService(ServerServiceDefinition service) { services.put(service.getServiceDescriptor().getName(), service); return this; }
服務接口綁定時,由 Proto3 工具生成代碼,重載 bindService() 方法(GreeterImplBase 類):
@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) .addMethod( METHOD_SAY_HELLO, asyncUnaryCall( new MethodHandlers< io.grpc.examples.helloworld.HelloRequest, io.grpc.examples.helloworld.HelloReply>( this, METHODID_SAY_HELLO))) .build(); }
3.2.3 service 調用
-
gRPC 消息的接收: gRPC 消息的接入由 Netty HTTP/2 協議棧回調 gRPC 的 FrameListener,進而調用 NettyServerHandler 的 onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers) 和 onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream),代碼以下所示:
消息頭和消息體的處理,主要由 MessageDeframer 的 deliver 方法完成,相關代碼以下(MessageDeframer 類):
if (inDelivery) { return; } inDelivery = true; try { while (pendingDeliveries > 0 && readRequiredBytes()) { switch (state) { case HEADER: processHeader(); break; case BODY: processBody(); pendingDeliveries--; break; default: throw new AssertionError("Invalid state: " + state);
gRPC 請求消息(PB)的解碼由 PrototypeMarshaller 負責,代碼以下 (ProtoLiteUtils 類):
public T parse(InputStream stream) { if (stream instanceof ProtoInputStream) { ProtoInputStream protoStream = (ProtoInputStream) stream; if (protoStream.parser() == parser) { try { T message = (T) ((ProtoInputStream) stream).message();
... -
gRPC 響應消息發送: 響應消息分爲兩部分發送:響應消息頭和消息體,分別被封裝成不一樣的 WriteQueue.AbstractQueuedCommand,插入到 WriteQueue 中。 消息頭封裝代碼(NettyServerStream 類):
public void writeHeaders(Metadata headers) { writeQueue.enqueue(new SendResponseHeadersCommand(transportState(), Utils.convertServerHeaders(headers), false), true); }
消息體封裝代碼(NettyServerStream 類):
ByteBuf bytebuf = ((NettyWritableBuffer) frame).bytebuf(); final int numBytes = bytebuf.readableBytes(); onSendingBytes(numBytes); writeQueue.enqueue( new SendGrpcFrameCommand(transportState(), bytebuf, false), channel.newPromise().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { transportState().onSentBytes(numBytes); } }), flush);
Netty 的 NioEventLoop 將響應消息發送到 ChannelPipeline,最終被 NettyServerHandler 攔截並處理。 響應消息頭處理代碼以下(NettyServerHandler 類):
private void sendResponseHeaders(ChannelHandlerContext ctx, SendResponseHeadersCommand cmd, ChannelPromise promise) throws Http2Exception { int streamId = cmd.stream().id(); Http2Stream stream = connection().stream(streamId); if (stream == null) { resetStream(ctx, streamId, Http2Error.CANCEL.code(), promise); return; } if (cmd.endOfStream()) { closeStreamWhenDone(promise, streamId); } encoder().writeHeaders(ctx, streamId, cmd.headers(), 0, cmd.endOfStream(), promise); }
響應消息體處理代碼以下(NettyServerHandler 類):
private void sendGrpcFrame(ChannelHandlerContext ctx, SendGrpcFrameCommand cmd, ChannelPromise promise) throws Http2Exception { if (cmd.endStream()) { closeStreamWhenDone(promise, cmd.streamId()); } encoder().writeData(ctx, cmd.streamId(), cmd.content(), 0, cmd.endStream(), promise); }
-
服務接口實例調用: 通過一系列預處理,最終由 ServerCalls 的 ServerCallHandler 調用服務接口實例,代碼以下(ServerCalls 類):
return new EmptyServerCallListener<ReqT>() { ReqT request; @Override public void onMessage(ReqT request) { this.request = request; } @Override public void onHalfClose() { if (request != null) { method.invoke(request, responseObserver); responseObserver.freeze(); if (call.isReady()) { onReady(); }
最終的服務實現類調用以下(GreeterGrpc 類):
public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) { switch (methodId) { case METHODID_SAY_HELLO: serviceImpl.sayHello((io.grpc.examples.helloworld.HelloRequest) request, (io.grpc.stub.StreamObserver<io.grpc.examples.helloworld.HelloReply>) responseObserver); break; default: throw new AssertionError(); }
3.3 服務端線程模型
gRPC 的線程由 Netty 線程 + gRPC 應用線程組成,它們之間的交互和切換比較複雜,下面作下詳細介紹。
3.3.1 Netty Server 線程模型
它的工做流程總結以下:
-
從主線程池(bossGroup)中隨機選擇一個 Reactor 線程做爲 Acceptor 線程,用於綁定監聽端口,接收客戶端鏈接;
-
Acceptor 線程接收客戶端鏈接請求以後建立新的 SocketChannel,將其註冊到主線程池(bossGroup)的其它 Reactor 線程上,由其負責接入認證、握手等操做;
-
步驟 2 完成以後,應用層的鏈路正式創建,將 SocketChannel 從主線程池的 Reactor 線程的多路複用器上摘除,從新註冊到 Sub 線程池(workerGroup)的線程上,用於處理 I/O 的讀寫操做。
Netty Server 使用的 NIO 線程實現是 NioEventLoop,它的職責以下:
-
做爲服務端 Acceptor 線程,負責處理客戶端的請求接入;
-
做爲客戶端 Connecor 線程,負責註冊監聽鏈接操做位,用於判斷異步鏈接結果;
-
做爲 I/O 線程,監聽網絡讀操做位,負責從 SocketChannel 中讀取報文;
-
做爲 I/O 線程,負責向 SocketChannel 寫入報文發送給對方,若是發生寫半包,會自動註冊監聽寫事件,用於後續繼續發送半包數據,直到數據所有發送完成;
-
做爲定時任務線程,能夠執行定時任務,例如鏈路空閒檢測和發送心跳消息等;
-
做爲線程執行器能夠執行普通的任務 Task(Runnable)。
3.3.2 gRPC service 線程模型
gRPC 服務端調度線程爲 SerializingExecutor,它實現了 Executor 和 Runnable 接口,經過外部傳入的 Executor 對象,調度和處理 Runnable,同時內部又維護了一個任務隊列 ConcurrentLinkedQueue,經過 run 方法循環處理隊列中存放的 Runnable 對象,代碼示例以下:
3.3.3 線程調度和切換策略
Netty Server I/O 線程的職責:
- gRPC 請求消息的讀取、響應消息的發送
- HTTP/2 協議消息的編碼和解碼
- NettyServerHandler 的調度
gRPC service 線程的職責:
- 將 gRPC 請求消息(PB 碼流)反序列化爲接口的請求參數對象
- 將接口響應對象序列化爲 PB 碼流
- gRPC 服務端接口實現類調用
gRPC 的線程模型遵循 Netty 的線程分工原則,即:協議層消息的接收和編解碼由 Netty 的 I/O(NioEventLoop) 線程負責;後續應用層的處理由應用線程負責,防止因爲應用處理耗時而阻塞 Netty 的 I/O 線程。
基於上述分工原則,在 gRPC 請求消息的接入和響應發送過程當中,系統不斷的在 Netty I/O 線程和 gRPC 應用線程之間進行切換。明白了分工原則,也就可以理解爲何要作頻繁的線程切換。
gRPC 線程模型存在的一個缺點,就是在一次 RPC 調用過程當中,作了屢次 I/O 線程到應用線程之間的切換,頻繁切換會致使性能降低,這也是爲何 gRPC 性能比一些基於私有協議構建的 RPC 框架性能低的一個緣由。儘管 gRPC 的性能已經比較優異,可是仍有必定的優化空間。