本文主要介紹了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,擁有更高的序列化/反序列化效率,易於實現更高的吞吐效能
02gRPC web能不能用?好不好用?
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”。測試成功,抓包結果如下:
可以看到,兩方使用“不安全“的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 }}]複製程式碼
現在進行測試,訪問剛才的網頁,可以看到通訊情況:
一看就知道是的的base64編碼,果斷解碼:
果然不出所料,是一個封裝在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!")複製程式碼
控制檯返回伺服器收到:您好!
通訊完成,檢視協議:
可以看到,經過一次通訊後,伺服器直接返回升級升級連線的報頭,隨後雙方使用的WebSocket的進行通訊,比起gRPC的協議,更加簡易直白。
結論
在測試前我一直在思考,gRPCweb究竟是一個怎樣的存在第一眼看到gRPCweb文字這個詞,我的第一感覺是這個?
在Gmail中的郵箱裡,充滿了這種極其類似的的protobuf的XHR請求,當看到需要使用專用代理進行轉發的時候,我一度以為是這種高度壓縮的文字協議正式出現在了通用的框架裡,然而事實是,不論是gRPC-web還是gRPCweb-text,不論是易用性還是請求響應體長度,比起jon over brotli over websocket都仍然有很大的進步空間,要實現通過CDN分發js節省API伺服器頻寬的構思目前仍然需要自己造輪子,gRPCweb並不是銀彈,至少目前不是。
本文首發於公眾號”小米運維“,點選檢視原文。