gRPC-網路現狀及測試

小米運維發表於2019-02-28
本文主要介紹了gRPC,網路現狀及測試的簡介,好不好用等相關內容。
上篇文章回顧:
golang中的切片操作

                                gRPC是什麼?

gRPC是谷歌開源的一款不那麼快的基於原型緩衝區的RPC框架。

既然不那麼快,為什麼還要提它呢?相較於節儉,gRPC會慢一點,但是,本文的著眼點並不在於RPC吞吐量的極限值,也不是框架的通訊時間減少幾十幾千納秒,本文要介紹的是除GraphQL外,JSON RPC優化的另一個取向--gRPCweb。

眾所周知,GraphQL著眼的優化點在於通過移交一部分查詢的邏輯到客戶端,從而減少了資料的交換量,而RPC則著眼於使用可壓縮的二進位制/文字協議,減少JSON文字傳輸帶來的不必要的協議損失。本文著眼於此,對比gRPCweb當前的進展,對易用性,便捷性,成本方面進行了評價,結論雖然有些武斷,但是仍然反映了當前gRPCweb仍然有待進一步發展的事實。

01gRPC web能給我們帶來什麼?

1.傳輸資料量減少,傳輸延遲降低

HTTP / 2天生具有頭壓縮等特性,解決了大量頻繁的RPC互動通訊帶來的頭部重複傳輸問題;使用二進位制流或壓縮文字傳輸,減少了一部分稀疏編碼帶來的位元組空洞,提高了資訊密度。傳輸速度更快,資料量更小,不僅降低成本,而且可以減少延遲。

2.可靠一致的API設計

客戶端服務端使用相同的原檔案進行介面定義,雙方介面暴露完全相同,一致性更強,相較於傳統的招搖介面管理,其資料結構更加直觀精確,不再需要維護介面-URL間的複雜對應關係,API升級管理更加簡單

3.對傳輸基礎設施無感知的通訊協議

節儉不能推出類似gRPCweb的方案的原因也正在於此.Thrift使用私有Tprotocol傳輸協議,與gRPC的HTTP / 2相比起來通用性大打折扣,Nginx的在最新的穩定版中已經提供了grpc_pass負載均衡支援,我們可以無痛使用原有的四層/七層負載均衡器提供大規模的呼叫支援

4.高效的序列化/反序列化支援

gRPC相較於JSON,擁有更高的序列化/反序列化效率,易於實現更高的吞吐效能

02
gRPC web能不能用?好不好用?

話不多說,接下來我們就開始進行一套gRPCserver + envoy代理+ gRPCweb on TypeScript的echo通訊測試,作為對比,筆者將實現一套同樣功能的websocket鏈路,用以測試雙方通訊效能,比較這些功能的實現難度並評價兩種“看似比較底層”的網路通訊協議。

                                 gRPC網站測試

首先定義一個我們的回聲服務的原型:

syntax = "proto3";package chatman;


message ChatRequest {
    string messages = 1;
}


message ChatResponse {
    string messages = 1;
}


service Chat {    rpc chat (ChatRequest) returns (ChatResponse);
}複製程式碼

然後寫伺服器端程式碼,此處使用的Python的實現:

生成伺服器端的protobuf的的檔案,使用gRPC工具生成程式碼即可,然後我們引入grpcio和這些的protobuf的庫檔案:

import grpcimport service_pb2,service_pb2_grpc
from concurrent.futures import ThreadPoolExecutorimport time


class ChatServicer(service_pb2_grpc.ChatServicer):
    def chat(self,request,context):        print(request.messages)
        return service_pb2.ChatResponse(messages="Server Received: %s" % request.messages)def server_start():
    server = grpc.server(ThreadPoolExecutor(max_workers=5))
    service_pb2_grpc.add_ChatServicer_to_server(ChatServicer(),server)
    server.add_insecure_port('0.0.0.0:8800')
    server.start()
    time.sleep(3600*24)if __name__ == '__main__':
server_start()複製程式碼

我們先測試下HTTP / 2二進位制流模式的gRPC:

Python中的程式碼如下:

import grpcimport service_pb2_grpcimport service_pb2


channel = grpc.insecure_channel('0.0.0.0:8800')
stub = service_pb2_grpc.ChatStub(channel)


message = service_pb2.ChatRequest(messages="test message")
msg = stub.chat(message)
print(msg)複製程式碼

返回訊息:“Server Received:test message”。測試成功,抓包結果如下:

gRPC-網路現狀及測試

可以看到,兩方使用“不安全“的HTTP協議交換了資料,通訊成功完成。

接下來,我們使用gRPC的網路實現相同功能。值得注意的一點是,目前gRPC幅還不能直接執行在瀏覽器上,因為瀏覽器尚未提供裸HTTP協議的介面,因此需要進行一層簡單的封裝,並通過代理剝去這層封裝才能與真正的gRPC後端通訊,這種通訊方式被稱為gRPC的網頁文字。

同理,使用gRPC的工具生成打字稿檔案,生成檔案包含一個原始的JS庫,一個描述檔案以及一個TS客戶端庫,編寫程式碼:

import { ChatRequest,ChatResponse } from './service_pb'import { ChatClient } from './ServiceServiceClientPb'const client1 = new ChatClient("http://127.0.0.1:8001",{},{});

let req = new ChatRequest()

req.setMessages("messages")

client1.chat(req,{},(err: any, res)=>{

    console.log(res);

});複製程式碼

在HTML中直接引用這個JS,並使用特使代理進行gRPC的網頁文字到gRPC協議的轉換,配置如下:

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }


static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 127.0.0.1, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: echo_service
                  max_grpc_timeout: 0s
              cors:
                allow_origin:
                - "*"
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                max_age: "1728000"
                expose_headers: custom-header-1,grpc-status,grpc-message
                enabled: true
          http_filters:
          - name: envoy.grpc_web
          - name: envoy.cors
          - name: envoy.router
  clusters:
  - name: echo_service
    connect_timeout: 0.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
hosts: [{ socket_address: { address: 127.0.0.1, port_value: 8800 }}]複製程式碼

現在進行測試,訪問剛才的網頁,可以看到通訊情況:

gRPC-網路現狀及測試

一看就知道是的的base64編碼,果斷解碼:

gRPC-網路現狀及測試

果然不出所料,是一個封裝在HTTP裡的HTTP通訊

                                Web Socket的測試

接下來,使用的WebSocket的進行對比:

編寫服務端程式:

console.log("Server started");
var Msg = '';
var WebSocketServer = require('ws').Server
    , wss = new WebSocketServer({port: 8010});
    wss.on('connection', function(ws) {
        ws.on('message', function(message) {
        console.log('Received: %s', message);
        ws.send('Server received: ' + message);
    });
 });複製程式碼

使用節點啟動後,在瀏覽器的控制檯裡連線並訪問:

const ws_echo = new WebSocket("ws://127.0.0.1:8081/")
ws_echo.addEventListener('message', (event) => {
    console.log(event.data);
})
ws_echo.send("Hello!")複製程式碼

控制檯返回伺服器收到:您好!

通訊完成,檢視協議:

gRPC-網路現狀及測試

可以看到,經過一次通訊後,伺服器直接返回升級升級連線的報頭,隨後雙方使用的WebSocket的進行通訊,比起gRPC的協議,更加簡易直白。

                                       結論

在測試前我一直在思考,gRPCweb究竟是一個怎樣的存在第一眼看到gRPCweb文字這個詞,我的第一感覺是這個?

gRPC-網路現狀及測試

在Gmail中的郵箱裡,充滿了這種極其類似的的protobuf的XHR請求,當看到需要使用專用代理進行轉發的時候,我一度以為是這種高度壓縮的文字協議正式出現在了通用的框架裡,然而事實是,不論是gRPC-web還是gRPCweb-text,不論是易用性還是請求響應體長度,比起jon over brotli over websocket都仍然有很大的進步空間,要實現通過CDN分發js節省API伺服器頻寬的構思目前仍然需要自己造輪子,gRPCweb並不是銀彈,至少目前不是。

本文首發於公眾號”小米運維“,點選檢視原文


相關文章