純乾貨:微服務開發手冊之GRPC

馬小諾諾諾發表於2020-10-21

1 簡介

在GRPC框架中,客戶端可以像呼叫本地物件一樣直接呼叫位於不同機器的服務端方法,如此我們就可以非常方便的建立一些分散式的應用服務。

在服務端,我們實現了所定義的服務和可供遠端呼叫的方法,執行一個gRPC server來處理客戶端的請求;在客戶端,gRPC實現了一個stub(可以簡單理解為一個client),其提供跟服務端相同的方法。

純乾貨:微服務開發手冊之GRPC

 

gRPC使用protocol buffers作為介面描述語言(IDL)以及底層的資訊交換格式,一般情況下推薦使用 proto3因為其能夠支援更多的語言,並減少一些相容性的問題。 由於gRPC涉及到幾個比較重要的技術點http2、protobuf,正是這幾個技術點才使得gRPC得到廣泛應用,這裡也順帶講一下這幾個技術點

1.1 http2

HTTP/2是最新的HTTP協議,提高了資源訪問效率。通過本篇科普小文,可以瞭解HTTP/2協議的概念以及優勢。

HTTP/2也被稱為HTTP 2.0,相對於HTTP 1.1新增多路複用、壓縮HTTP頭、劃分請求優先順序、服務端推送等特性,解決了在HTTP 1.1中一直存在的問題,優化了請求效能,同時相容了HTTP 1.1的語義。

2015年,HTTP/2 釋出。HTTP/2是現行HTTP協議(HTTP/1.1)的替代,但它不是重寫,HTTP方法、狀態碼、語義都與HTTP/1.1一樣。HTTP/2 相比於 HTTP/1.1,可以說是大幅度提高了網頁的效能,只需要升級到該協議就可以減少很多之前需要做的效能優化工作。HTTP/2基於SPDY,專注於效能,最大的一個目標是在使用者和網站間只用一個連線(connection)。

HTTP/2新特性

1.1.1二進位制傳輸

HTTP/2傳輸資料量的大幅減少,主要有兩個原因:以二進位制方式傳輸和Header 壓縮。先來介紹一下二進位制傳輸,HTTP/2 採用二進位制格式傳輸資料,而非HTTP/1.1 裡純文字形式的報文 ,二進位制協議解析起來更高效。HTTP/2 將請求和響應資料分割為更小的幀,並且它們採用二進位制編碼。HTTP/2所有效能增強的核心在於新的二進位制分幀層,它定義瞭如何封裝http訊息並在客戶端與伺服器之間傳輸。

1.1.2 Header壓縮

HTTP/1.1的header帶有大量資訊,而且每次都要重複傳送,HTTP/2並沒有使用傳統的壓縮演算法,而是開發了專門的“HPACK”演算法,在客戶端和伺服器兩端建立“字典”,用索引號表示重複的字串,還採用哈夫曼編碼來壓縮整數和字串,可以達到50%~90%的高壓縮率。

1.1.3 多路複用

多路複用允許同時通過單一的HTTP/2連線發起多重的請求-響應資訊,很好的解決了瀏覽器限制同一個域名下的請求數量的問題,同時也更容易實現全速傳輸。

1.1.4 伺服器推送

HTTP2還在一定程度上改變了傳統的“請求-應答”工作模式,伺服器不再是完全被動地響應請求,也可以新建“流”主動向客戶端傳送訊息。比如,在瀏覽器剛請求HTML的時候就提前把可能會用到的JS、CSS檔案發給客戶端,減少等待的延遲,這被稱為”伺服器推送”( Server Push,也叫 Cache push)。

1.2 Protobuf

Protocol buffers 是一種語言中立,平臺無關,可擴充套件的序列化資料的格式,可用於通訊協議,資料儲存等。

Protocol buffers 在序列化資料方面,它是靈活的,高效的。相比於 XML 來說,Protocol buffers 更加小巧,更加快速,更加簡單。一旦定義了要處理的資料的資料結構之後,就可以利用 Protocol buffers 的程式碼生成工具生成相關的程式碼。甚至可以在無需重新部署程式的情況下更新資料結構。只需使用 Protobuf 對資料結構進行一次描述,即可利用各種不同語言或從各種不同資料流中對你的結構化資料輕鬆讀寫。

Protocol buffers 很適合做資料儲存或 RPC 資料交換格式。可用於通訊協議、資料儲存等領域的語言無關、平臺無關、可擴充套件的序列化結構資料格式。

1.2.1 優點

1、效能好/效率高

時間開銷: XML格式化(序列化)的開銷還好;但是XML解析(反序列化)的開銷就不敢恭維了。 但是protobuf在這個方面就進行了優化。可以使序列化和反序列化的時間開銷都減短。

空間開銷:也減少了很多

2、有程式碼生成機制

比如你你寫個一下類似結構體的內容


 message testA  
{  
    required int32 m_testA = 1;  
}  

像寫一個這樣的結構,protobuf可以自動生成它的.h 檔案和點.cpp檔案。 protobuf將對結構體testA的操作封裝成一個類。

3、支援向後相容和向前相容

當客戶端和伺服器同時使用一塊協議的時候, 當客戶端在協議中增加一個位元組,並不會影響客戶端的使用

4、支援多種程式語言

在Google官方釋出的原始碼中包含了c++、java、Python三種語言

1.2.2 缺點

1、二進位制格式導致可讀性差

為了提高效能,protobuf採用了二進位制格式進行編碼。這直接導致了可讀性差。這個直接影響開發測試時候的效率。當然,一般情況下,protobuf非常可靠,並不會出現太大的問題。

2、缺乏自描述

一般來說,XML是自描述的,而protobuf格式則不是。 給你一段二進位制格式的協議內容,不配合你寫的結構體是看不出來什麼作用的。

3、通用性差

protobuf雖然支援了大量語言的序列化和反序列化,但仍然並不是一個跨平臺和語言的傳輸標準。在多平臺訊息傳遞中,對其他專案的相容性並不是很好,需要做相應的適配改造工作。相比json 和 XML,通用性還是沒那麼好。

1.2.3 與其他序列化方式對比

protobuf處理整型特別快,如果需要了解原理可以檢視高效的資料壓縮編碼方式 Protobuf

至於與其他序列化方式以及使用場景的對比可以參考下面的文章 Protobuf有沒有比JSON快5倍?用程式碼來擊破pb效能神話

全方位評測:Protobuf效能到底有沒有比JSON快5倍?

2 grpc四種模式

1,簡單模式:簡單模式只是使用引數和返回值作為伺服器與客戶端傳遞資料的方式,最簡單。

2,客戶端流模式:即從客戶端往伺服器端傳送資料使用的是流,即伺服器端的引數為流型別,然而在伺服器響應後返還資料給客戶端,使用的也是流的send方法。一般在伺服器端的程式碼,需要先recv再send,而客戶端與此相反。但是在後面的雙向模式中可以使用go的協程協作。

3,伺服器端流模式:即伺服器端返回結果的時候使用的是流模式,即傳入的資料是通過引數形式傳入的。但是在往客戶端傳送資料時使用send方法,與客戶端返回資料的方式大同小異。

4,雙向模式:客戶端如果不適用協程,那麼傳送必須在接收之前。如果使用協程,傳送與接收並沒有先後順序。為了保證協程的同步,可以使用互斥量進行約束。

如果想要了解詳細demo,可以檢視gRPC 官方文件中文版

3 整合springcloud

這裡整合grpc建議有兩種方案,

3.1 實現公共protobuf,通過java grpc來遠端呼叫

這個方案就是不用編寫protobuf了,可以直接類似dubbo一樣提供java api就可以實現rpc呼叫,這裡詳情可以參考github上的demo ttps://github.com/ChinaSilence/spring-boot-starter-grpc

純乾貨:微服務開發手冊之GRPC

 

  • facade:獨立的 Maven 模組,依賴 spring-boot-starter-grpc,需要遠端呼叫的方法,都定義在此模組,形式可以為介面(interface) 或者抽象類(abstract class)
  • server:服務提供方,依賴 facade 模組,需實現 facade 模組定義的介面或者抽象類的抽象方法
  • client:服務呼叫方,依賴 facade 模組,使用時,直接呼叫即可

優缺點分析

優點:

  • 不需要編寫probuff檔案,以java api形式來定義介面
  • 不依賴於eureka,完美適用於k8s

缺點:

  • 只支援java,如果要支援異構語言需要使用springcloudsidecar 或者 手動註冊到eureka
  • eureka支援有限,不支援負載均衡

3.2 每次使用protobuf定義介面(整合net.devh.grpc)

這裡內容還比較多,詳情可以參考我的部落格 springcloud整合grpc(一) 這種方式整合每次都需要編寫proto介面檔案並自動生成程式碼,客戶端和服務端都需要另外組裝引數。

不過優勢是,有詳細的介面規範(protobuf),並且可以支援異構語言呼叫。

4 服務發現與負載均衡

gRPC開源元件官方並未直接提供服務註冊與發現的功能實現,但其設計文件已提供實現的思路,並在不同語言的gRPC程式碼API中已提供了命名解析和負載均衡介面供擴充套件。

其基本實現原理:

服務啟動後gRPC客戶端向命名伺服器發出名稱解析請求,名稱將解析為一個或多個IP地址,每個IP地址標示它是伺服器地址還是負載均衡器地址,以及標示要使用哪個客戶端負載均衡策略或服務配置。

客戶端例項化負載均衡策略,如果解析返回的地址是負載均衡器地址,則客戶端將使用grpclb策略,否則客戶端使用服務配置請求的負載均衡策略。

負載均衡策略為每個伺服器地址建立一個子通道(channel)。

當有rpc請求時,負載均衡策略決定那個子通道即grpc伺服器將接收請求,當可用伺服器為空時客戶端的請求將被阻塞。

根據gRPC官方提供的設計思路,基於程式內LB方案(即第2個案,阿里開源的服務框架 Dubbo 也是採用類似機制),結合分散式一致的元件(如Zookeeper、Consul、Etcd),可找到gRPC服務發現和負載均衡的可行解決方案。接下來以GO語言為例,簡單介紹下基於Etcd3的關鍵程式碼實現:

4.1 dubbo

上面有描述,dubbo支援grpc,方案類似本文3.1提出的方案,不過是阿里提供了一整套微服務體系,包括註冊中心nacas、dubbo、sentinel都支援grpc

4.2 springcloud

這個方案同樣是類似3.2章,上面是整合了net.devh.grpc, 該外掛最新版本,同樣支援springcloud全家桶,詳細可以參考github: https://github.com/yidongnan/grpc-spring-boot-starter。 如果是異構語言則需要整合springcloud中的sidecar來實現服務發現,這裡也僅僅是支援java呼叫其他異構語言,如果需要其他語言也支援相互呼叫,則需要對應語言按照開源元件官方說的方案二來實現相應元件

4.3 istio

Istio是什麼:Istio是Google/IBM/Lyft聯合開發的開源專案,2017年5月釋出第一個release 0.1.0, 官方定義為:

Istio:一個連線,管理和保護微服務的開放平臺。

按照isito文件中給出的定義:

Istio提供一種簡單的方式來建立已部署的服務的網路,具備負載均衡,服務到服務認證,監控等等功能,而不需要改動任何服務程式碼。

簡單的說,有了Istio,你的服務就不再需要任何微服務開發框架(典型如Spring Cloud,Dubbo),也不再需要自己動手實現各種複雜的服務治理的功能(很多是Spring Cloud和Dubbo也不能提供的,需要自己動手)。只要服務的客戶端和伺服器可以進行簡單的直接網路訪問,就可以通過將網路層委託給Istio,從而獲得一系列的完備功能。 Istio的關鍵功能:

  • HTTP/1.1,HTTP/2,gRPC和TCP流量的自動區域感知負載平衡和故障切換。
  • 通過豐富的路由規則,容錯和故障注入,對流行為的細粒度控制。
  • 支援訪問控制,速率限制和配額的可插拔策略層和配置API。
  • 叢集內所有流量的自動量度,日誌和跟蹤,包括叢集入口和出口。
  • 安全的服務到服務身份驗證,在叢集中的服務之間具有強大的身份標識。

可以近似的理解為:Istio = 微服務框架 + 服務治理 以下是 Istio 的官方拓撲圖:

純乾貨:微服務開發手冊之GRPC

 

這裡的前提是使用了k8s,在k8s中可以無縫整合istio。具體操作步驟我這裡不做詳細描述,詳情可以參考下面連結使用Istio和Envoy實踐服務網格gRPC度量

5 grpc之攔截器

grpc自帶認證,不過有時候也需要在呼叫前,或者呼叫後做一些操作,比如說記錄監控資訊、或者透傳header等等,這時就需要用到grpc的攔截器,這裡僅以java語言來說一下攔截器用法。

5.1 server攔截器

public class MyServerInsterceptor implements ServerInterceptor{


    @Override
    public <reqt, respt> ServerCall.Listener<reqt> interceptCall(ServerCall<reqt, respt> call,
                                                                 Metadata headers, ServerCallHandler<reqt, respt> next) {
      
        return next.startCall(call,headers);
    }
}

這裡可以設定全域性攔截器

@Configuration
public class GrpcOpenConfig {

    //grpc-spring-boot-starter provides @GrpcGlobalInterceptor to allow server-side interceptors to be registered with all
    //server stubs, we are just taking advantage of that to install the server-side gRPC tracer.
    @Bean
    ServerInterceptor grpcServerInterceptor() {
        return new MyServerInterceptor();
    }

    @Bean
    public GlobalServerInterceptorConfigurer globalInterceptorConfigurerAdapter(ServerInterceptor grpcServerInterceptor) {
        return registry -> registry.addServerInterceptors(grpcServerInterceptor);
    }
}

同時如果整合了grpc-spring-boot-starter,也可以使用@GrpcGlobalInterceptor來增加全域性攔截器,hi需要將該註解加到類名上

5.2 client攔截器

public class MyClientInterceptor implements ClientInterceptor {


    Metadata.Key<string> token = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);

    @Override
    public <reqt, respt> ClientCall<reqt, respt> interceptCall(MethodDescriptor<reqt, respt> method,
            CallOptions callOptions, Channel next) {
        return new SimpleForwardingClientCall<reqt, respt>(next.newCall(method, callOptions)) {

            @Override
            public void start(Listener<respt> responseListener, Metadata headers) {
                //此處為你登入後獲得的token的值
                headers.put(token, "A2D05E5ED2414B1F8C6AEB19F40EF77C");
                super.start(new SimpleForwardingClientCallListener<respt>(responseListener) {
                    @Override
                    public void onHeaders(Metadata headers) {
                        super.onHeaders(headers);
                    }
                }, headers);
            }
        };
    }
}


這裡可以設定全域性攔截器

@Order(Ordered.LOWEST_PRECEDENCE)
@Configuration
@Slf4j
public class GrpcOpenTracingConfig {
    //We also create a client-side interceptor and put that in the context, this interceptor can then be injected into gRPC clients and
    //then applied to the managed channel.
    @Bean
    ClientInterceptor grpcClientInterceptor() {
        return new MyclientInterceptor();
    }

    @Bean
    public GlobalClientInterceptorConfigurer globalInterceptorConfigurerAdapter(ClientInterceptor grpcClientInterceptor) {
        return registry -> registry.addClientInterceptors(grpcClientInterceptor);
    }

}

同時如果整合了grpc-spring-boot-starter,也可以使用@GrpcGlobalInterceptor來增加全域性攔截器,hi需要將該註解加到類名上

6 grpc監控之全鏈路

關於全鏈路監控,可以參考我的全鏈路系列部落格

微服務全鏈路跟蹤:grpc整合zipkin

微服務全鏈路跟蹤:jaeger整合grpc

微服務全鏈路跟蹤:jaeger整合istio,併相容uber-trace-id與b3

7 grpc監控之prometheus

如果是整合了 grpc-spring-boot-starter,則只需要訪問 http://服務域名/actuator/prometheus ,可以看到grpc相關監控指標

純乾貨:微服務開發手冊之GRPC

 

純乾貨:微服務開發手冊之GRPC

 

純乾貨:微服務開發手冊之GRPC

 

如果是整合了grafana,就能看到下面的效果:

純乾貨:微服務開發手冊之GRPC

 

純乾貨:微服務開發手冊之GRPC

相關文章