很多開發者都表示他們基於HTTP的API是RESTful的。但是,如同Fielding在他的博客中所說,這些API可能並不都是RESTful的。Leonard Richardson為REST定義了一個成熟度模型,具體包含以下4個層次(摘自IBM):
- 第一個層次(Level 0)的 Web 服務只是使用 HTTP 作為傳輸方式,實際上只是遠程方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬於此類。
- 第二個層次(Level 1)的 Web 服務引入了資源的概念。每個資源有對應的標識符和表達。
- 第三個層次(Level 2)的 Web 服務使用不同的 HTTP 方法來進行不同的操作,並且使用 HTTP 狀態碼來表示不同的結果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。
- 第四個層次(Level 3)的 Web 服務使用 HATEOAS。在資源的表達中包含了鏈接信息。客戶端可以根據鏈接來發現可以執行的動作。
使用基於HTTP的協議有如下好處:
• HTTP非常簡單並且大家都很熟悉。
• 可以使用瀏覽器擴展(比如Postman)或者curl之類的命令行來測試API。
• 內置支持請求/響應模式的通信。
• HTTP對防火牆友好的。
• 不需要中間代理,簡化了系統架構。
不足之處包括:
• 只支持請求/響應模式交互。可以使用HTTP通知,但是服務端必須一直發送HTTP響應才行。
• 因為客戶端和服務端直接通信(沒有代理或者buffer機制),在交互期間必須都在線。
• 客戶端必須知道每個服務實例的URL。如之前那篇關於API Gateway的文章所述,這也是個煩人的問題。客戶端必須使用服務實例發現機制。
開發者社區最近重新發現了RESTful API接口定義語言的價值。於是就有了一些RESTful風格的服務框架,包括RAML和Swagger。一些IDL,例如Swagger允許定義請求和響應消息的格式。其它的,例如RAML,需要使用另外的標識,例如JSON Schema。對於描述API,IDL一般都有工具來定義客戶端和服務端骨架接口。
Thrift
Apache Thrift是一個很有趣的REST的替代品。它是Facebook實現的一種高效的、支持多種編程語言的遠程服務調用的框架。Thrift提供了一個C風格的IDL定義API。使用Thrift編譯器可以生成客戶端和服務器端代碼框架。編譯器可以生成多種語言的代碼,包括C++、Java、Python、PHP、Ruby, Erlang和Node.js。
Thrift接口包括一個或者多個服務。服務定義類似於一個JAVA接口,是一組方法。Thrift方法可以返回響應,也可以被定義為單向的。返回值的方法其實就是請求/響應類型交互模式的實現。客戶端等待響應,並可能拋出異常。單向方法對應於通知類型的交互模式,服務端並不返回響應。
Thrift支持多種消息格式:JSON、二進制和壓縮二進制。二進制比JSON更高效,因為二進制解碼更快。同樣原因,壓縮二進制格式可以提供更高級別的壓縮效率。JSON,是易讀的。Thrift也可以在裸TCP和HTTP中間選擇,裸TCP看起來比HTTP更加有效。然而,HTTP對防火牆,瀏覽器和人來說更加友好。
消息格式
了解完HTTP和Thrift后,我們來看下消息格式方面的問題。如果使用消息系統或者REST,就可以選擇消息格式。其它的IPC機制,例如Thrift可能只支持部分消息格式,也許只有一種。無論哪種方式,我們必須使用一個跨語言的消息格式,這非常重要。因為指不定哪天你會使用其它語言。
有兩類消息格式:文本和二進制。文本格式的例子包括JSON和XML。這種格式的優點在於不僅可讀,而且是自描述的。在JSON中,一個對象就是一組鍵值對。類似的,在XML中,屬性是由名字和值構成。消費者可以從中選擇感興趣的元素而忽略其它部分。同時,小幅度的格式修改可以很容器向后兼容。
XML文檔結構是由XML schema定義的。隨着時間發展,開發者社區意識到JSON也需要一個類似的機制。一個選擇是使用JSON Schema,要么是獨立的,要么是例如Swagger的IDL。
基於文本的消息格式最大的缺點是消息會變得冗長,特別是XML。因為消息是自描述的,所以每個消息都包含屬性和值。另外一個缺點是解析文本的負擔過大。所以,你可能需要考慮使用二進制格式。
二進制的格式也有很多。如果使用的是Thrift RPC,那可以使用二進制Thrift。如果選擇消息格式,常用的還包括Protocol Buffers和Apache Avro。它們都提供典型的IDL來定義消息架構。一個不同點在於Protocol Buffers使用的是加標記(tag)的字段,而Avro消費者需要知道模式(schema)來解析消息。因此,使用前者,API更容易演進。這篇博客很好的比較了Thrift、Protocol Buffers、Avro三者的區別。
總結
微服務必須使用進程間通信機制來交互。當設計服務的通信模式時,你需要考慮幾個問題:服務如何交互,每個服務如何標識API,如何升級API,以及如何處理部分失敗。微服務架構有兩類IPC機制可選,異步消息機制和同步請求/響應機制。在下一篇文章中,我們將會討論微服務架構中的服務發現問題。
原文鏈接:Building Microservices: Inter-Process Communication in a Microservices Architecture(翻譯:楊峰 校對:李穎傑)
或許很多人會說 Spring Cloud 和 Dubbo 的對比有點不公平,Dubbo 只是實現了服務治理,而 Spring Cloud 下面有 17 個子項目(可能還會新增)分別覆蓋了微服務架構下的方方面面,服務治理只是其中的一個方面,一定程度來說,Dubbo 只是 Spring Cloud Netflix 中的一個子集。但是在選擇框架上,方案完整度恰恰是一個需要重點關注的內容。
根據 Martin Fowler 對微服務架構的描述中,雖然該架構相較於單體架構有模塊化解耦、可獨立部署、技術多樣性等諸多優點,但是由於分布式環境下解耦,也帶出了不少測試與運維復雜度。
根據微服務架構在各方面的要素,看看 Spring Cloud 和 Dubbo 都提供了哪些支持。
以上列舉了一些核心部件,大致可以理解為什么之前說 Dubbo 只是類似 Netflix 的一個子集了吧。當然這里需要申明一點,Dubbo 對於上表中總結為“無”的組件不代表不能實現,而只是 Dubbo 框架自身不提供,需要另外整合以實現對應的功能,比如:
- 分布式配置:可以使用淘寶的 diamond、百度的 disconf 來實現分布式配置管理。但是 Spring Cloud 中的 Config 組件除了提供配置管理之外,由於其存儲可以使用 Git,因此它天然的實現了配置內容的版本管理,可以完美的與應用版本管理整合起來。
- 服務跟蹤:可以使用京東開源的 Hydra
- 批量任務:可以使用當當開源的 Elastic-Job
- ……
雖然,Dubbo 自身只是實現了服務治理的基礎,其他為保證集群安全、可維護、可測試等特性方面都沒有很好的實現,但是幾乎大部分關鍵組件都能找到第三方開源來實現,這些組件主要來自於國內各家大型互聯網企業的開源產品。
RPC vs REST
另外,由於 Dubbo 是基礎框架,其實現的內容對於我們實施微服務架構是否合理,也需要我們根據自身需求去考慮是否要修改,比如 Dubbo 的服務調用是通過 RPC 實現的,但是如果仔細拜讀過 Martin Fowler 的 microservices 一文,其定義的服務間通信是 HTTP協議的 REST API。那么這兩種有何區別呢?
先來說說,使用 Dubbo 的 RPC 來實現服務間調用的一些痛點:
- 服務提供方與調用方接口依賴方式太強:我們為每個微服務定義了各自的 service 抽象接口,並通過持續集成發布到私有倉庫中,調用方應用對微服務提供的抽象接口存在強依賴關系,因此不論開發、測試、集成環境都需要嚴格的管理版本依賴,才不會出現服務方與調用方的不一致導致應用無法編譯成功等一系列問題,以及這也會直接影響本地開發的環境要求,往往一個依賴很多服務的上層應用,每天都要更新很多代碼並 install 之后才能進行后續的開發。若沒有嚴格的版本管理制度或開發一些自動化工具,這樣的依賴關系會成為開發團隊的一大噩夢。而 REST 接口相比 RPC 更為輕量化,服務提供方和調用方的依賴只是依靠一紙契約,不存在代碼級別的強依賴,當然 REST 接口也有痛點,因為接口定義過輕,很容易導致定義文檔與實際實現不一致導致服務集成時的問題,但是該問題很好解決,只需要通過每個服務整合 swagger,讓每個服務的代碼與文檔一體化,就能解決。所以在分布式環境下,REST 方式的服務依賴要比 RPC 方式的依賴更為靈活。
- 服務對平台敏感,難以簡單復用:通常我們在提供對外服務時,都會以 REST 的方式提供出去,這樣可以實現跨平台的特點,任何一個語言的調用方都可以根據接口定義來實現。那么在 Dubbo 中我們要提供 REST 接口時,不得不實現一層代理,用來將 RPC 接口轉換成 REST 接口進行對外發布。若我們每個服務本身就以 REST 接口方式存在,當要對外提供服務時,主要在 API 網關中配置映射關系和權限控制就可實現服務的復用了。
相信這些痛點也是為什么當當網在 dubbox(基於 Dubbo 的開源擴展)中增加了對 REST 支持的原因之一。
小結:Dubbo 實現了服務治理的基礎,但是要完成一個完備的微服務架構,還需要在各環節去擴展和完善以保證集群的健康,以減輕開發、測試以及運維各個環節上增加出來的壓力,這樣才能讓各環節人員真正的專注於業務邏輯。而 Spring Cloud 依然發揚了 Spring Source 整合一切的作風,以標准化的姿態將一些微服務架構的成熟產品與框架揉為一體,並繼承了 Spring Boot 簡單配置、快速開發、輕松部署的特點,讓原本復雜的架構工作變得相對容易上手一些(如果您讀過我之前關於 Spring Cloud 的一些核心組件使用的文章,應該能體會這些讓人興奮而激動的特性,傳送門)。所以,如果選擇 Dubbo 請務必在各個環節做好整套解決方案的准備,不然很可能隨着服務數量的增長,整個團隊都將疲於應付各種架構上不足引起的困難。而如果選擇 Spring Cloud,相對來說每個環節都已經有了對應的組件支持,可能有些也不一定能滿足你所有的需求,但是其活躍的社區與高速的迭代進度也會是你可以依靠的強大后盾。
Round 4:文檔質量
Dubbo 的文檔可以說在國內開源框架中算是一流的,非常全,並且講解的也非常深入,由於版本已經穩定不再更新,所以也不太會出現不一致的情況,另外提供了中文與英文兩種版本,對於國內開發者來說,閱讀起來更加容易上手,這也是 Dubbo 在國內更火一些的原因吧。
Spring Cloud 由於整合了大量組件,文檔在體量上自然要比 dubbo 多很多,文檔內容上還算簡潔清楚,但是更多的是偏向整合,更深入的使用方法還是需要查看其整合組件的詳細文檔。另外由於 Spring Cloud 基於 Spring Boot,很多例子相較於傳統 Spring 應用要簡單很多(因為自動化配置,很多內容都成了約定的默認配置),這對於剛接觸的開發者可能會有些不適應,比較建議了解和學習 Spring Boot 之后再使用 Spring Cloud,不然可能會出現很多一知半解的情況。
小結:雖然 Spring Cloud 的文檔量大,但是如果使用 Dubbo 去整合其他第三方組件,實際也是要去閱讀大量第三方組件文檔的,所以在文檔量上,我覺得區別不大。對於文檔質量,由於 Spring Cloud 的迭代很快,難免會出現不一致的情況,所以在質量上我認為 Dubbo 更好一些。而對於文檔語言上,Dubbo 自然對國內開發團隊來說更有優勢。
總結
通過上面再幾個環節上的分析,相信大家對 Dubbo 和 Spring Cloud 有了一個初步的了解。就我個人對這兩個框架的使用經驗和理解,打個不恰當的比喻:使用 Dubbo 構建的微服務架構就像組裝電腦,各環節我們的選擇自由度很高,但是最終結果很有可能因為一條內存質量不行就點不亮了,總是讓人不怎么放心,但是如果你是一名高手,那這些都不是問題;而 Spring Cloud 就像品牌機,在 Spring Source 的整合下,做了大量的兼容性測試,保證了機器擁有更高的穩定性,但是如果要在使用非原裝組件外的東西,就需要對其基礎有足夠的了解。
從目前 Spring Cloud 的被關注度和活躍度上來看,很有可能將來會成為微服務架構的標准框架。所以,Spring Cloud 的系列文章,我會繼續寫下去。也歡迎各位朋友一起交流,共同進步。
原文鏈接: 微服務架構的基礎框架選擇:SpringCloud還是Dubbo
如何將一個系統拆分成SCS(自包含系統)
在進行領域驅動設計(DDD)時,為了盡可能降低SCS之間的耦合,每個SCS應該實現一個 邊界上下文 。每個系統不只擁有一個領域模型,事實上,一個系統可以包含多個不同的領域模型。每一個模型都有一個邊界上下文。例如,在電子商務系統里搜索產品的當前價格時,產品的描述和數量是很重要的。而如果要向客戶發貨,則還需要其他的信息:產品的重量和客戶的收貨地址。將系統拆分成邊界上下文是構建自包含系統最為有效的方式。
可以通過對用戶故事進行分組來定義邊界上下文。假設我們通過全文檢索來搜索產品,那么通過分類和推薦來搜索也應該屬於相同的邊界上下文。當然,有時候拆分並不會有非常清楚的界線,這要取決於搜索的復雜性。
在將系統拆分成SCS時也需要考慮到 用戶體驗 。用戶體驗描述了客戶與系統之間的交互步驟,比如搜索產品、結賬或注冊。每一個步驟都可能成為一個SCS。這些步驟之間一般只有很少的依賴。這些步驟之間有承上啟下的關系:購物車在結賬時就變成了一個訂單,然后完成支付。
SCS不只處理某種特定的領域對象。例如,使用一個SCS來處理所有的客戶數據就沒有多大意義:很多不同的邊界上下文都會用到客戶數據。所以,為客戶單獨創建模型並在一個單獨的SCS里實現是不可能的事情。如果真的這樣子做了,那么每個需要用到客戶數據的系統都會依賴它。這也就是為什么在將系統拆分成SCS時需要通過用戶故事、邊界上下文或用戶體驗來驅動,這種自上而下的方法會帶來低耦合的系統。
雖然在后續有必要識別出公共部分,但這不應該成為關鍵點。公共邏輯可以被抽取到另一個系統里,但這意味着SCS會對這個系統產生依賴,它們之間就產生了耦合。
在微服務集成——《微服務設計》讀書筆記文章中,我們說過服務間的消息傳遞有幾種方式,一種是請求/響應技術,另一種是基於事件的機制。
RPC(遠程過程調用)
RPC是Remote Procedure Call的簡稱。
這是請求/響應技術的一種,它使用本地調用的方式和遠程進行交互,如SOAP、Thrift等,比如我們常使用的WebService和Java RMI,就是這種類型。它先在本地生成樁代碼,然后通過樁代碼進行遠程調用。
RPC會帶來一些問題,如Java RMI,其耦合性較緊,同時RPC會對調用進行大量的封裝和解封裝,同時修改接口時會造成服務的提供方和調用方都要修改。
REST
REST是受Web啟發而產生的一種架構風格,REST風格包含的內容很多,Richardson的成熟度模型(http://martinfowler.com/articles/richardsonMaturityModel.html),其中有對REST不同風格的比較。
REST本身並沒有提到底層應該使用什么協議,最常用的是HTTP,HTTP本身提供了很多功能,這些功能對於實現REST風格非常有用,比如HTTP的動詞(GET、POST、PUT等)就能很好地和資源一起使用。
在使用REST時,傳輸的數據格式是XML還是JSON,這個沒有一個定論。
基於HTTP的REST也有缺點:
1.它無法幫你生成樁代碼(封裝rest請求參數時需要),
2.在要求低延遲的場景下,每個HTTP請求的封裝開銷可能是個問題,使用TCP、UDP可能更合適。
基於事件的異步協作
這種方式主要有兩個部分需要考慮:微服務發布事件消費者接收事件機制。
消息隊列(如RabbitMQ)可以同進處理上述兩方法的問題。生產者使用API向代理發布事件,代理可以向消費者提供訂閱服務,並且在事件發生時通知消費者。這種代理甚至可以跟蹤消費者的狀態,如標記哪些消息是該消費者已經消費過的。這種系統通常具有較好的可伸縮性和彈性,但這么做會增加開發流程的復雜度,因為你需要一個額外的系統(即消息代理)才能開發及測試服務。
另一種方式是使用HTTP來傳播事件,ATOM是一個符合REST規范的協議,可以通過它提供資源聚合的發布服務,當服務提供方發生改變時,只需要簡單地向該聚合發布一個事件即可,消費者會輪詢該聚合以查看變化。它的缺點是:HTTP不擅長處理低延遲的場景,而且使用ATOM的話,用戶還需要自己追蹤消息是否送達及管理輪詢等工作。
異步架構有其復雜性,比如,消息丟失了怎么辦?消息重試失敗了怎么辦?消息重發了怎么辦?消息請求崩潰了怎么辦?我們可以通過設置最大重試、黑名單、白名單等措施來解決這些問題。但這也意味着復雜性的增加。
參考
《微服務設計》(Sam Newman 著 / 崔力強 張駿 譯)
http://www.cnblogs.com/gudi/p/6624917.html