gRPC-web現狀及測試

小米運維發表於2019-02-28

本文主要介紹了gRPC-web現狀及測試的簡介、好不好用等相關內容。

gRPC是什麼?

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

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

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

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

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

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

2.可靠一致的API設計

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

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

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

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

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

2gRPC web能不能用?好不好用?

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

gRPC web測試

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

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-web現狀及測試

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

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

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

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

let req = new ChatRequest()

req.setMessages("messages")

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

   console.log(res);

});

在HTML中直接引用這個JS,並使用特使代理進行gRPC的Web文字到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-web現狀及測試

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

gRPC-web現狀及測試

果然不出所料,是一個封裝在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-web現狀及測試

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

結論

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

gRPC-web現狀及測試

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

gRPC-web現狀及測試

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559359/viewspace-2637227/,如需轉載,請註明出處,否則將追究法律責任。

相關文章