Socket.D 基於訊息的響應式應用層網路協議

帶刺的坐椅發表於2023-12-20

首先根據 Socket.D 官網的副標題,Socket.D 的自我定義是:

基於事件和語義訊息流的網路應用協議。

官網定義的特點是:

  • 基於事件,每個訊息都可事件路由
  • 所謂語義,透過元資訊進行語義描述
  • 流關聯性,有相關的訊息會串成一個流
  • 語言無關,使用二進位制輸傳資料(支援 tcp, ws, udp)。支援多語言、多平臺
  • 斷線重連,自動連線恢復
  • 多路複用,一個連線便可允許多個請求和響應訊息同時執行
  • 雙向通訊,單連結雙向互聽互發
  • 自動分片,資料超出 16Mb,會自動分片、自動重組(udp 除外)
  • 介面簡單,是響應式但用的是監聽與回撥風格(經典易用)

Socket.D 是基於這些特性需求誕生的一種新型響應式網路協議。Socket.D 借鑑了很多其他協議發展過程中遇到的問題,然後總結歸納進自己的實踐當中。

基於 Socket.D 的一些主要特性分別做一下介紹,並和 HTTP 之類的常見協議進行比較:

  • Destination (URL) 顯示連線地址
  • Event 事件
  • Multiplexed, Binary Protocol 多路複用的二進位制協議
  • Bidirectional Streaming 雙向流
  • Socket Resumption 連線恢復
  • Message passing 訊息傳遞模型
  • Transport independent 與傳輸層解耦的應用層協議

一、兩層路由能力

  • path 路由能力

Socket.D 是基於顯示連線地址的,可以實現像 http 或 websocket 一樣的“頻道”路由的效果。地址例:

//模擬聊天場景的使用者地址
sd:tcp://127.0.0.1:8602

//模擬聊天場景的管理員地址
sd:tcp://127.0.0.1:8602/admin?u=admin&p=1234
  • event 路由能力

Socket.D 每個訊息都有事件描述,可以起到 path 或 topic 或 cmd 類似的路由效果。示例:

//模擬訊息中介軟體的釋出指令
client.send("event.mq.publish", new StringEvent("{userId:1}").metaSet("topic","demo"));

//模擬訊息中介軟體的訂閱指令
client.send("event.mq.subscribe", new StringEvent("").metaSet("topic","demo"));

二、多路複用的二進位制協議

現在 Multiplexing,Asynchronous,Non-blocking I/O 已經被說爛了,基本上就是標配。這些特性意味著什麼?拿HTTP的發展史感受一下:

從 HTTP1.0 到 HTTP3.0 在傳輸效能上的進步

  • 在 HTTP1.0 時代,每個 HTTP request 都要新建一個網路連線。網路連線不能複用
  • HTTP1.1 時代,一個網路連線仍然在一個時候只能負責一個 request,但是整個 request/response 結束後連線可以得到複用。
  • 會有文章講到 HTTP1.1 的核心是pipeline功能,是也不是。pipelining 支援一個 TCP 連線上按照順序連續傳送多個 HTTP 請求而不需要等待前一個請求的響應,但是它同時要求HTTP response也要按照請求的順序逐個傳送,這對伺服器提出了很多要求,而且如果第一個響應很慢會拖累所有的後續響應(pipeling的隊頭阻塞),所以事實上並沒有得到多少運用。即使到今天大部分瀏覽器仍然是預設關閉HTTP pipelining功能的,所以說HTTP1.1的主要突破還只是連線複用。
  • HTTP2.0 是個飛躍,開始支援 multiplexing,一個TCP連線上可以同時承載多個request/response,用這種方式替代1.1的pipelining提升HTTP的並行效果,也自然不存在什麼隊頭阻塞了。每一個request/response的資訊流,我們把它稱作一個HTTP stream。這個時候一個HTTP client對於一個origin,只需要建立一個TCP就夠了。(但是multiplexing帶來了新的問題)
  • 現在HTTP3.0也差不多了。2.0解決了1.1pipelining的隊頭阻塞問題,但是卻無法解決TCP本身的隊頭阻塞。而因為TCP/IP在核心協議棧中,簡直無法升級,於是HTTP選擇了QUIC作為新的傳輸層協議。 QUIC基於UDP,在使用者模式中實現了類似TCP的connection oriented的功能同時解決TCP的隊頭阻塞,自帶multiplexing等等。

所以,HTTP/2具有的優點,Socket.D 都有。另外,Socket.D 是一個二進位制協議,也就是說在一個 Socket.D 連線上傳輸的訊息體對資料格式沒有任何要求,應用程式可以為所欲為的壓縮資料量的大小。

這樣的二進位制協議通常來說能給效能帶來極大的提升,但是產生的代價是,網路中介軟體也會因為無法解讀訊息體中的資料,喪失了在對具體應用流量進行監控,日誌和路由的能力。所以 Socket.D 透過把每個訊息體分成 sid, event, data 和 metaString 的方式,在保證高效傳輸的前提下,也提供了暴露後設資料給網路中介軟體的能力(方便做語義處理),同時還能路由訊息。

frame: {flag, message: {sid, event, entity: { metaString, data}}}

對於每個 data,應用可以採用不同的序列化方法。metaString 則採用標準的 url queryString 的通用格式(所有網路中介軟體通用)。

  • data 一般作為應用本身需要傳遞的業務資料,採取自定義的高效序列化方式,且對網路基礎設施不可見
  • metaString 採用標準的 url queryString 的通用格式。在分散式傳輸的過程中,這些中介軟體可以按需求對 metaString 進行讀寫,然後調整路由。

三、雙向流

上面提到,HTTP這幾年在傳輸效能上進步了很多。但說到底在應用層仍然僅支援client request/server response的互動模型。

這裡一些同學可能有疑問,比如:

那HTTP/2推出的Server Push是什麼

HTTP2.0推出了一個新的Server Push功能,但這個功能通常只是用來提前將一些靜態資源返還給使用者而已。舉個例子:一個簡單的網站有三個靜態資源組成: index.html, index.css, index.js。 我們開啟瀏覽器開啟index.html,就會發起一個HTTP request拿到index.html。在不使用server push的情況下,我們要等瀏覽器解析出index.css 和 index.js之後才會再次向伺服器發起請求。而運用server push,伺服器可以根據一些規則預知到瀏覽器也需要index.css和index.js,並在客戶端傳送新的請求之前直接推送給該客戶端。

所以,這個功能的使用場景非常有限,而且也不是一個真正雙向的互動模式。

結論:僅僅使用HTTP/2協議,不在其基礎之上再加一層其他協議的情況下是無法在應用層實現雙向流的(比如,gRPC)。

回到互動模式,Socket.D 的幾種模式:

  • Send
  • SendAndRequest -> Reply
  • SendAndSubscribe -> Stream Reply
  • Reply
  • ReplyEnd
  • Session(雙向使用上面的傳送與答覆)

通常來說,越複雜的互動模式,為了儲存互動狀態,就需要佔用更多的記憶體和計算資源。這也是為什麼 Socket.D 會提供多種不同的 API。另外,當 Socket.D 的 client 和 server 建立了長連線之後,任何一方都可以是 Requester 或是 Responder。伺服器也可以扮演 Requester 的角色,首先發起 Request。

四、非同步訊息傳遞

Socket.D 還有另外一個非常重要的概念使之完全區分於類HTTP協議,那就是非同步訊息傳遞。

不同於HTTP當中存在Request,Response。Socket.D在網路傳輸上只有Frame這一個訊息格式。有一個相同點是,類HTTP協議通常擁有一個顯式的destination (URL),使用起來會非常有親切感和簡單。

如果Requester的 Socket.D 訊息R首先透過了一個網路中介軟體(Broker),那麼請求者(Requester)並不關心該訊息的最終目的地在哪裡,網路中介軟體可以全權負責路由模組的實現。該架構可以支援微服務,也可以支援IOT場景,等等。

這種架構很有意思,不僅能在微服務中加入streaming支援,還有如下特點:

1.客戶端也可以暴露服務

由於 Socket.D 的雙向流特性,和Broker建立連線的客戶端即可以做Requester也可以做Responder,比如圖中的Device 1和Device 2雖然是移動終端或者IOT裝置,但仍然可以向其他裝置或資料中心的主機提供服務

2.自動服務註冊/發現

Socket.D 在成功建立連線時。如果該client想要暴露一個服務,則在連線地址上給自己取個名字(就像加入一個社交群,讓別人能At到你)。連線成功建立之後,Socket.D Broker可以直接透過記錄網路連線狀況來達到服務註冊和發現的效果。透過'@'引數取名示例:

sd:tcp://127.0.0.1:8602?@=demoapp

3.基於訊息 metaString 實現請求路由

前面說過,Socket.D 是一個二進位制協議,但仍然可以在訊息體中透過 metaString 來暴露資訊給網路中介軟體。每個訊息實體的 metaString 會帶有‘@’引數, 告訴 Socket.D Broker 訊息應該轉發給誰,從而實現路由效果。示例:

client.send("/demo", new StringEntity("").at("demoapp"));

與連線時給自己取名的 '@' 引數相乎應。取了名,就能被“別人” at 到!

4.暴露服務不需要Ip和Port

整個架構當中,所有節點都只需要知道Broker的地址和服務埠即可。只要成功連線資訊流就是雙向的。Borker可以直接透過建立的Connection定址服務節點,所有的服務呼叫者都不需要知道服務暴露方的地址

而Broker又可以透過連線的 url 地址,進行籤權控制。

5.天生的中心化管理

管理微服務叢集往往需要有一箇中心化的控制中心,在這個架構中 Socket.D Broker 就是自然而然的中心,知道整個叢集中所有的情況。

相關文章