1. 程式人生 > >面試官又來喊你造飛機了,你來說說看微服務介面怎麼設計?

面試官又來喊你造飛機了,你來說說看微服務介面怎麼設計?

文章每週持續更新,各位的「三連」是對我最大的肯定。可以微信搜尋公眾號「 後端技術學堂 」第一時間閱讀(一般比部落格早更新一到兩篇)

本文是後端微服務架構系列的第二篇文章。在微服務架構中服務之間的通訊方式常見的有兩種:RPC REST,關於微服務和 RPC 的更多細節,可以參考我上一篇文章 面試都在問的微服務、服務治理、RPC、下一代微服務框架... 一文帶你徹底搞懂!

這篇文章主要介紹什麼是 REST 風格設計以及 RESTful 介面。閱讀完本文你將收穫以下知識點:

  • 什麼是 RESTRESTful
  • REST 介面設計規範是什麼
  • REST 為什麼要設計成無狀態
  • 介面無狀態真的是沒有狀態嗎
  • RPCREST 適用場景

REST和RESTful

REST(Representational State Transfer,表述性狀態轉移) 是一種軟體架構風格。REST提出了一組架構約束條件和原則,任何滿足 REST 約束條件和原則的架構,都稱為 RESTful 架構。

微服務之間需要相互通訊以完成特定的業務處理,在典型的客戶端-服務端設計模型中,客戶端和服務端通通過訊息請求-響應的方式互動協作,REST 就是這樣一套微服務之間互動介面的設計約束和原則規範。

乍一看 REST「表述性狀態轉移」每個字都認得,連起來不知道什麼意思。也難怪,這是作者 Roy Thomas Fielding

在他的博士論文裡提出的概念,論文自然都是學術用語,不過感興趣的同學可以去看看作者論文原文,地址我貼出來:https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm

今天 lemon 用大白話幫你透徹理解這個概念,我們把「表述性狀態轉移」掰開來看,先搞明白什麼是「表述性」,什麼是「狀態轉移」。

表述性

「表述性」其實是缺少了主語的,主語是「資源」。完整的描述是「資源表述性」,也就是「資源的描述」。在網路通訊中用什麼描述資源呢?沒錯就是 URI(Uniform Resource Identifier,統一資源識別符號)

這裡有幾個近義詞先給大家先科普一下:

URI 是統一資源識別符號,用來唯一的標識一個資源。

URL 是統一資源定位器,它是一種具體的 URI,即 URL 可以用來標識一個資源,而且還指明瞭如何定位這個資源,URLURI 的子集。

URN 統一資源命名,是通過名字來標識資源。URN 也是 URI 的子集。

HTTP 協議中用 URL 標識資源,也就是瀏覽器位址列你看到的那一串網址。

資源表述性

為了說明「資源描述性」介面設計的優點,我們來做一個介面設計方法的對比,舉個栗子就清楚了。

傳統的介面設計

先來看下傳統的網路通訊模式是怎麼樣的。假設lemon這個人物物件,在服務端的儲存形式是一個c++class型別儲存。

下面的過程展示,客戶端傳送請求服務端建立一個 lemon 物件的過程。

  1. 服務端定義儲存結構標頭檔案 lemon.h
class lemon{
  string name;
  string address;
  uint64 phone;
}
  1. 客戶端程式碼引用服務端定義的lemon.h,互相引用標頭檔案,增加了服務耦合性!

  2. 客戶端初始化一個 lemon 例項並序列化後通過網路介面傳送給服務端。

class lemon lm;
lm.name = "lemon";
lm.address = "Shenzhen";
lm.phone = 18666666666;
  1. 服務端接收訊息,反序列化,儲存傳輸過來的 lemon 物件
資源表述性介面設計

lemon 這個服務內部的物件,對外表現可以用一張圖片來表示,也可以用包含lemon 的姓名、地址、電話等資訊的 xmljson 格式的資料表示。

{
name : "lemon",
address:  "ShenZhen",
phone  :  18666666666
}
<?xml version="1.0" encoding="UTF-8" ?>
	<name>lemon</name>
	<address>ShenZhen</address>
	<phone>18666666666</phone>

也就是說,lemon 這個「資源」在服務內部的存放形式對外不可見,外界客戶端發起請求可以用不同的資源表述格式來獲取服務端的資源。

(如果伺服器會說話,他內心os 大概是這樣的: 客戶端你不用管我是如何儲存這個物件的,只要你說的清楚想要什麼物件,只管發來請求便是!)。

這樣做最顯然的好處是,減少了服務之間的耦合。客戶端訪問服務資源之前不需要知道資源在服務端的具體儲存格式,只需描述資源形式即可修改、建立、更新、刪除服務端的資源。

狀態轉移

搞懂了「資源描述性」接下來看下什麼是「狀態轉移」?狀態轉移就是客戶端通過一系列請求動作,推動服務端的資源狀態發生變化,資源的狀態可以在「建立-修改-檢視-刪除」之間轉移。

資源狀態的變化在巨集觀上的反應就是業務流程推進。打個比方,你去銀行系統開戶、查餘額、銷戶,這個過程你推動了你的銀行賬戶這個「資源」經歷了不同的狀態轉移讓你完成了不同的業務操作。

REST的約束條件

協議選擇

REST 本身並沒有提到底層應該使用什麼協議,日常實踐案例中最常用的是基於 HTTPRESTful 實現。

這是因為 HTTP 協議自帶的動詞 GET/POST/PUT/DELETE 可以作為推動狀態轉移的方法,另外HTTP 的制定了規範的狀態碼。還有其他的一些 HTTP 特性,這些特性使得在HTTP 之上實現 REST 要簡單得多,而如果使用其他協議的話,就需要自己實現這些特性。

請求規範

RESTful 架構中,發生狀態轉換的是「資源」,所以URI 中一般只能包含代表「資源」的名詞,並且推薦是複數,而不應該在 URI 中包對資源進行操作的動詞。

對資源執行的CURD「增刪改查」動作應該在HTTP請求方法的GET/POST/PUT/DELETE中體現。

符合REST規範的寫法:

POST http://www.test.com/lemon   // 建立
Get http://www.test.com/lemon    // 查詢
PUT http://www.test.com/lemon    // 修改
DELETE http://www.test.com/lemon //刪除

不符合REST規範的寫法:

POST http://www.test.com/Createlemon  // 建立
POST http://www.test.com/Querylemon   // 查詢
POST http://www.test.com/Modifylemon  // 修改
POST http://www.test.com/Deletelemon  //刪除

狀態碼

服務端訊息響應攜帶狀態碼,指示客戶端進行下一步處理。符合 RESTful 規範的介面返回狀態碼都是通用的,不需要額外約定,利用HTTP Status Code 狀態碼 表示請求處理結果,降低了微服務間互操作成本。

狀態碼 狀態碼含義
2xx 成功,操作被成功接收並處理
3xx 重定向,需要進一步的操作以完成請求
4xx 客戶端錯誤,請求包含語法錯誤或無法完成請求
5xx 伺服器錯誤,伺服器在處理請求的過程中發生了錯誤

下面是常見的HTTP狀態碼:

  • 200 - 請求成功
  • 301 - 資源(網頁等)被永久轉移到其它URL
  • 404 - 請求的資源(網頁等)不存在
  • 500 - 內部伺服器錯誤

無狀態

RESTful介面要求是「無狀態」。無狀態指的是任意一個Web請求必須完全與其他請求隔離,當客戶端發起請求時,訊息本身包含了服務端識別這一請求上下文所需的全部資訊。

無狀態不是真的沒有狀態

介面「無狀態」更確切的說是服務端無狀態,整個會話還是需要狀態維持的。要完成一個業務流程,一般客戶端與服務端需要多次的訊息互動,我們知道HTTP 協議是「無狀態協議」,這就需要服務端能夠識別幾個獨立 HTTP 請求的「狀態資訊」,從而將他們關聯到一個業務流程中。

還是舉例子銀行系統取款的例子:

  • 使用者lemon要登入銀行系統,首先需要在登入頁面輸入使用者名稱和密碼,這時候產生一個登入請求
  • 服務端收到登入請求,執行登入邏輯並返回操作結果
  • lemon登入之後點選取款100萬,產生一個取款請求
  • 服務端收到取款請求,執行取款邏輯並返回操作結果

這裡有個問題,服務端在不同時間點收到登入請求和取款請求,這兩個請求都是使用者 lemon 產生的,如果不在技術層面做對獨立的 HTTP 請求做關聯的話,服務端就無法知道這兩個請求其實是都是使用者lemon 「取款業務」的組成部分。

技術方案

服務端要能識別請求的「狀態資訊」,有兩種技術方案:

  1. Session 方式。服務端儲存會話狀態,客戶端每次請求攜帶session-id

    服務端維護一個會話狀態資訊列表,用session-id唯一標識一個狀態資訊,session-id一般包含在HTTP響應的Set-Cookie頭部返回給客戶端,後續客戶端請求攜帶包含session-id資訊的cookie頭部,服務端解析cookie取出session-id,去維護的狀態列表中取回該訊息對應的狀態資訊,這樣就把無狀態的HTTP變成有狀態的了。

  1. Token 方式。服務端不儲存會話狀態,客戶端每次請求都攜帶完整的會話狀態資訊(一般是加密的)給服務端。

    Token也稱作是「令牌」或臨時證書籤名,狀態資訊都被加密到token中,這樣每當伺服器收到請求後解密token就能獲取該請求對應的狀態資訊,也就能把不同的請求訊息關聯到同一個業務流程中來,和session方式有類似的效果,只不過這次的狀態資訊不儲存在服務端。

以上兩種實現中,第一種 Session 方式是有狀態的,第二種 Token 方式是無狀態的。

如果你要實現 RESTful 介面最好按第二種技術方案實現,當然要實現無狀態也還有其他方式,思路都是「服務端不保持會話狀態」就對了。

為什麼要無狀態

為了高可用性和負載均衡需求,多個微服務通過負載均衡實現分散式叢集化部署,叢集中每個服務都是獨立和對等的。如果伺服器在收到客戶端請求之時不可用或者宕機,無狀態請求可以由任何其他可用伺服器處理並作出應答,這在分散式應用中非常重要。

想象一下如果服務端儲存狀態,一個事務內的每個請求都必須落到同一臺伺服器去處理,這就失去了分散式的意義和優勢。

所以, RESTful 介面要求是無狀態的,是為了更好的適應分散式業務場景,發揮微服務叢集優勢。

REST 和 RPC

這兩個概念經常出現在微服務架構設計中,REST 是一種軟體架構介面設計風格,RPC 是一種計算機通訊協議,看起來是兩個不同的概念,要把他們放在一起比較的話,我個人傾向於把 REST 具體化為一種基於HTTP 並按照 REST 約束設計的通訊協議,兩個通訊協議之間還是可以比較一下的。

回顧下RPC

RPC (Remote Procedure Call)遠端過程呼叫是一個計算機通訊協議。我們一般的程式呼叫是本地程式內部的呼叫,RPC允許你像呼叫本地函式一樣去呼叫另一個程式的函式,這中間會涉及網路通訊和程序間通訊,但你無需知道實現細節,RPC框架為你遮蔽了底層實現。RPC 是一種伺服器-客戶端Client/Serv er模式,經典實現是一個通過傳送請求-接受迴應進行資訊互動的系統。

適用場景

很多 RPC 框架提供的訊息傳輸都是基於二進位制的,比如ThriftProtocol buffers。這樣做的好處是訊息結構比較緊湊,對於頻繁呼叫或者大流量、低時延要求的應用場景,能夠顯著減少網路開銷;另一個約束是某些 RPC 框架有很強的技術耦合性,比如 Dubbo 只能用於 java 技術棧。綜上,RPC 更加適用於系統內部微服務之間的高效通訊。

RESTful介面由於提供了統一的基於 HTTP REST 設計標準,只需 web 框架支援 HTTP 協議,並設計RESTful 風格的介面即可,極大的方便了第三方服務接入呼叫,適合用於微服務系統對外暴露的介面設計標準。

寫在最後

本文是微服務架構設計中介面選型的一個小方面,很多人會覺得現在工作面試,不管是大廠還是小公司,都是面試造飛機,工作擰螺絲。個人認為即使你在入職之後接觸不到架構方面的工作,也要有一顆架構的心,高度決定認知,如果只盯著手上的那顆螺絲那和鹹魚有什麼區別?

老規矩。感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習。

好了,今天的技術分享就到這裡,本文是後端開發微服務設計系列的第二篇,這個系列應該還會繼續更新,我們下期再見。

我的更多精彩文章:

非常詳細的 Linux C/C++ 學習路線總結!已拿騰訊offer
Linux下「程序」出問題不要慌,資深程式設計師教你6招搞定!
面試官:你說對MySQL事務很熟?那我問你10個問題
我用大資料分析了一線城市1000多份崗位招聘需求,告訴你如何科學找工作
騰訊後臺開發面試筆試C++知識點參考筆記
還能這麼玩?我用VsCode畫類圖、流程圖、時序圖、狀態圖不要太爽!
面試官:你會幾種redis分散式鎖?我會三種!
最詳細的個人部落格教程搭建教程GithubPages+Jekyll 簡約風格部落格

創作不易,點贊關注支援一下吧

可以微信搜尋公眾號「 後端技術學堂 」回覆「資料」有我給你準備的各種程式設計學習資料。文章每週持續更新,我們下期見!