RSocket:又一個 REST 的挑戰者

RSocket:又一個 REST 的挑戰者

本文要點:

  1. 表徵狀態轉移(REST)已經成爲微服務通信事實上的標準。作者認爲,這不是一件好事——事實上,這是一件非常糟糕的事,尤其是對於微服務通信來說。
  2. REST 是基於 HTTP 實現的。使用 REST 的一個常見理由是,它很容易調試,因爲它是“人類可讀的”。不容易閱讀是工具問題。
  3. 在微服務通信協議設計中,我們需要的部分特性包括二進制序列化、雙向通信、多路複用以及元數據交換能力。
  4. 工程師希望能夠在數據到來時進行處理——他們希望能夠流化數據。對於通過流發送的數據,需要應用程序流控制。
  5. 我們需要一種現代化的方式來代替 HTTP,用於創建現代化的服務。開源項目 RSocket 是爲服務而設計的。它是面向連接的、消息驅動的協議,內置了應用程序級的流控制。
  6. 表徵狀態轉移(REST)已經成爲微服務通信事實上的標準。作者認爲,這不是一件好事——事實上,這是一件非常糟糕的事。這種情況是如何出現的呢?在 REST 出現的時候,還有更糟糕的選擇。在 2000 年,Roy Fielding提出“REST”的時候,在衆多味道差得多的三明治中,“REST”是羽衣甘藍三明治。

正文

人們在使用 SOAP、RMI、CORBA 和 EJB。JSON 從 XML 那裏獲得了不錯的喘息之機。使用 URL 輸出一些文本很容易。此外,JavaScript 開始真正在瀏覽器中流行起來,它比 SOAP 更容易處理 REST。與最近的微服務趨勢不同,大多數應用程序都是傳統的單體三層應用。與它們交互的大多數外部流量都來自於瀏覽器,所以當它們需要生成一些東西時,REST 是一個簡單的選擇。許多人開始從比較大的商業產品(如 WebSphere)轉向 Jetty 和 Tomcat。它們甚至沒有處理 EJB 的工具,所以 REST 是一個方便的選擇。

這和微服務有什麼關係呢?早期的微服務先驅者轉向微服務的原因與今天的人們不同。他們轉向微服務是因爲他們必須應對大規模的應用程序。他們開始擁有如此多的用戶,以至於他們無法在一個單體應用中提供所有的服務。與當今許多企業不同的是,成本不是激勵因素——時間纔是。他們昨天就需要把服務準備好。當越來越多的用戶使用他們的軟件時,他們的單體應用難以爲繼,所以他們把應用程序分割成更小的部分。他們可以將這些應用程序部署到數千臺服務器上,然後是虛擬機。

此外,他們可以非常迅速地部署他們的應用程序。採用這種模式的公司能夠生存下來。然而,在這場競賽中,他們沒有太多時間去考慮他們在做什麼。這些早期的先驅者必須應對用戶的指數級增長和競爭,所以他們選擇戰術解決方案是有道理的。其中之一就是使用 REST 進行服務通信。

爲什麼 REST 對微服務而言很糟糕?

在編寫應用程序時,編程語言最終會以機器碼的形式存在。這一點是顯而易見的。甚至像 Java 或 JavaScript 這樣的“解釋型”語言也是如此。它們不直接編譯成機器代碼,而是使用 JIT 或即時編譯器。在某些情況下,即時編譯的代碼可能比工程師手工編寫和調試的代碼還要快——VM 確實是現代計算機科學的精品。

那我們爲什麼要浪費這個精品呢?我們發送的不是針對機器進行過優化的二進制消息,而是在針對服務進行過優化的協議上發送針對人類進行過優化的消息。我們使用爲發送圖書而設計的協議發送 JSON 和 XML 這樣的東西。想想這是多麼可笑!你有一個二進制程序,它把一個二進制結構轉換成文本,通過網絡以文本的形式發送給一臺機器,後者再解析並把它轉換成二進制結構,然後在應用程序中進行處理。

避免現代 CPU 的緩存未命中是非常關鍵的。遺憾的是,解析大量 JSON 和字符串將導致緩存未命中!

使用 REST 的一個常見理由是,它很容易調試,因爲它是“人類可讀的”。不容易閱讀是工具問題。JSON 文本只是人類可讀的,因爲有一些工具讓你可以閱讀它——否則,它只是網絡上的字節。此外,發送出去的數據有一半被壓縮或加密——在這兩種情況下,都是人類不可讀的。此外,其中有多少是人們可以通過閱讀“調試”的?如果你有一個平均每秒 10 個請求的服務,它的 JSON 大小爲 1KB,即相當於每天 860MB 的數據,或者每天 250 本《戰爭與和平》。沒有人能讀懂,所以你只是在浪費錢。

然後,你有時候需要分發二進制數據,或者希望使用二進制格式而不是 JSON。爲此,必須對數據進行 Base64 編碼。這意味着,你實際上將數據序列化兩次——同樣,這不是使用現代硬件的有效方法。

最後,REST 基於 HTTP 實現。HTTP 被用作在服務之間發送傳輸數據。HTTP 被設計用於在互聯網上搬運圖書。它不應該用於服務之間的通信。相反,使用一種針對應用程序進行過優化的格式——它負責處理所有的數據。

什麼適合於微服務通信?

如果我們暫時假設 REST 不是服務之間通信的最佳選擇,那麼什麼纔是呢?讓我們來看一下,我們希望在一個爲微服務通信而設計的協議中包含的一些內容。

首先,我們希望它是雙向的。這是 REST 的一個大問題——客戶端只能調用服務器。當雙方具有對等的相互調用能力時,你就可以用一種自然的方式創建應用程序之間的交互。否則,你將不得不設計一些笨拙的變通方案,例如長輪詢,以模擬服務器發起的調用。你可以使用 HTTP/2 部分地解決這個問題,但是調用仍然需要由客戶端發起。你需要的是客戶端和服務器在必要時能夠自由地相互調用。

另一個要求是,服務之間的連接必須支持同一連接上的多請求,而且是同時。這叫做多路複用。現在,對於單個連接,需要有某種方法來區分一個請求和另一個請求。這不同於 HTTP,一個請求開始,另一個請求結束。使用多路複用,你需要跟蹤不同的請求。用二進制幀來表示每個請求是一個不錯的方法。每個幀都可以保存請求以及和請求有關的元數據。然後,可以用它把幀發送到正確的位置。

當通過單個連接發送數據時,需要分割請求的能力。單個連接上的大型請求會阻塞它後面的所有其他請求,也就是排頭阻塞。相反,我們需要的是將請求分割成較小的部分,並通過網絡發送它們。由於數據是按幀發送的,所以可以把它分解成更小的幀片段,然後在另一邊重新組裝。通過這種方式,請求可以相互交織。大請求不會再阻塞小請求。這樣就可以創建一個響應更快的系統。

此外,交換連接元數據的能力也很有用。有時候,需要發送的數據並不一定是業務事務的一部分——比如配置整體跟蹤級別或交換基於字典的壓縮信息。這些都與業務邏輯無關,但可以在連接級進行控制。交換元數據的能力可以爲此提供支持。

在應用程序代碼中,經常會調用一個函數或方法,該函數或方法接受一個列表,返回一個列表,或者兩者兼而有之。這在微服務中也經常發生。REST 不能很好地處理這些情況,這會導致各種各樣的問題和複雜性。

所需要的是一個能夠輕鬆自然地處理迭代數據的協議——就像你在應用程序中所做的那樣。讀取整個數據列表,對其進行處理,然後在處理完所有數據之後返回數據列表,這是沒有意義的。你需要的是處理數據的能力。你希望能夠流化數據。如果有一長串數據,你不希望在數據處理時等待——你希望在數據可用時將其發送出去,並在響應出現時獲取它。

這樣可以創建一個響應更快的系統。它可以用於各種各樣的事情,從從文件中讀取字節並在網絡上以流的方式傳輸,到返回數據庫查詢結果,再到向後端提供瀏覽器點擊流數據。如果協議中提供了一等的流支持,則不需要包含其他系統(如 Spark)來進行流處理。也沒有必要包括 Kafka 之類的東西,除非你想要存儲數據。

對於通過流發送的數據,接下來需要的是應用程序流控制。字節級的流控制對於 TCP 之類的東西是有效的,因爲從網卡的角度來看,所有東西的大小都是相同的,並且一般來說,處理的成本是相同的。然而,在應用程序中,並不是所有東西的成本都是相同的。一個 10KB 的消息可能需要 10 毫秒來處理,但另一個 10 字節的消息需要 10 秒。

在微服務中發現的另一個場景是,下游服務處理數據的速度比它能夠達到的速度慢。這意味着,TCP 緩衝區永遠不會滿。也需要有某種方法來控制流量,以避免淹沒下游服務,以保持它們的響應性。

應用程序必須能夠控制消息流速率,而不受底層網絡字節影響。對於應用程序開發人員來說,很難推斷一條消息在不同語言之間有多少字節。另一方面,對於開發人員來說,推斷他們發送了多少消息是很簡單的。通過這種方式,服務可以在網絡流控制和應用程序流控制之間進行尋租。有時,應用程序處理數據的速度比網絡快,有時,網絡處理數據的速度比應用程序快。應用程序流控制可以確保尾延遲穩定——又回到了創建響應性應用程序。它還可以防止對無界隊列的需求,這是在其他應用程序中發現的一種危險的黑客行爲。

如上所述,RESTful Web 服務有一個巨大的缺陷,它們實際上是基於文本實現的。要發送任何二進制數據,你需要對數據進行 Base64 編碼,並將所有內容序列化兩次。你真正想要的是二進制的東西——因爲它可以表示任何東西——包括文本。此外,與文本(尤其是數值)相比,應用程序處理二進制數據的效率要高得多。此外,它們天生更緊湊——它們沒有額外的大括號、花括號或尖括號。最後,如果數據是二進制的,根據格式的不同,也有可能實現零拷貝序列化和反序列化。這超出了本文的討論範圍,但讀者可以看下簡單二進制編碼(SBE)和Flatbuffers。它們比使用 JSON 快得多。

最後,你希望能夠通過不同的傳輸協議來發送請求。RESTful Web 服務通常使用 HTTP,而它僅使用 TCP。你真正想要的是一種將網絡抽象出來的方法,這樣,你只要按照規範進行編程就可以,而不必擔心傳輸問題。同時,如果它與瀏覽器對話,你的應用程序應該能夠基於 WebSocket 運行。不應該因爲想要更改應用程序部署的位置就必須切換到新的網絡工具包,傳輸協議的切換應該可以在不修改應用程序的情況下輕鬆完成。

什麼協議符合要求?

有人認爲 REST 和 HTTP/2 更合適,HTTP/2 優於 HTTP/1,但是,如果你閱讀下規範,就會發現,它唯一目的是創建更好的 Web 瀏覽器協議。它從來就不是爲微服務而設計的,也沒打算用於微服務。它就是應該用於服務器 HTML 到 Web 瀏覽器。同樣,它從來沒有打算用於微服務通信。此外,你仍然必須處理 URL 並將不同的 HTTP 方法匹配到你的應用程序——這些方法從來就沒有真得打算用於服務器到服務器的通信。

HTTP/2 確實提供了流,但它提供流只是爲了服務器推送。因此,使用基於 HTTP/2 的 REST 需要從客戶端發起請求,然後將數據推送到服務器。HTTP/2 的流控制是基於字節的流控制。對 Web 瀏覽器而言,這很好,但對應用程序來說,就不太好了。目前還無法像在應用程序上所做的那樣來控制應用程序流。

最近,圍繞使用 gRPC 有很多爭論。gRPC 在概念上與 SOAP 非常類似。它沒有使用 XML 來定義服務,而是使用了 Protobuf。像 SOAP 一樣,它結合了 URL 和 Header“幻數(magic)”——這次使用了 HTTP/2。這意味着,gRPC 明確地綁定到了 HTTP/2,這是一種爲 Web 瀏覽器設計的協議。更糟糕的是,Web 瀏覽器不支持它。

因此,你必須使用一個代理將 gRPC 調用轉成 REST 調用,違背其初衷來使用它。這凸顯了 gRPC 的設計有多麼糟糕。爲什麼要爲了一種協議而使用 HTTP/2,而且不能確保它在瀏覽器中有效?你將永遠受限於它最初的目的,卻不能把它用在應該使用它的地方。這引出了我的下一個觀點:REST 的最大限制是它與 HTTP 綁定。

你需要的是爲服務到服務的通信而設計的協議。使用專門爲服務之間的通信而設計的協議使你可以創建明顯更簡單、更可靠的應用程序。不會有任何非法用法、變通方案或阻抗不匹配。

建築材料是一個很好的類比。木頭是建造小橋樑的好材料。你可以用它跨越一條小溪,這不是問題。

當工程師開始使用它來跨越更大的距離時,事情就變得複雜起來。

像這樣的木橋很有用。但是,與使用更好的材料建造的現代橋樑相比,它們的故障率非常高。它們也非常複雜,建造起來要花很長時間。這就是爲什麼我們現在使用鋼筋和混凝土。它們更容易維護,建造成本更低,壽命更長,而且可以跨越更遠的距離。

我們需要一種現代化的材料來代替 HTTP 用於創建現代化的服務。開源項目RSocket就是爲服務而設計的。它是面向連接的、消息驅動的協議,內置了應用程序級的流控制。它在瀏覽器中和在服務器上一樣工作。事實上,Web 瀏覽器可以服務於後端微服務的流量。它也是二進制的。它可以同樣好地處理文本和二進制數據,並且可以分解有效工作負載。它將應用程序中的所有交互建模爲網絡原語。這意味着,你可以流化數據或執行發佈 / 訂閱,而無需設置應用程序隊列。

在合理的情況下,REST 是一個不錯的解決方案。其中一個不合理的地方就是微服務。分佈式系統本身就非常困難。我們最不需要的就是,使用不爲它們設計的東西使它們變得更加複雜。

作者簡介
Robert Roeser是 Netifi的聯合創始人兼 CEO。他在分佈式實時系統領域有 10 年的經驗。他在 Netflix 和耐克公司領導大規模技術項目。

查看英文原文:Give REST a Rest with RSocket

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