Socket.D 基於消息的響應式應用層網絡協議

首先根據 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 就是自然而然的中心,知道整個集羣中所有的情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章