攻克微服務中的最大難點:用戶數據

今天 數人云與你們分享的文章將探討微服務架構的建立與開發工做當中最爲困難的部分——用戶數據。數據庫

只有咱們擺脫本身的依賴時微服務才能起做用,換言之,存在於單一數據庫上的多任務進程並非真正的微服務。使用Spring Boot/Dropwizard/Docker並不表明你們所構建的就是微服務。再次強調,你們須要着眼於所處業務領域,而咱們的數據纔是實現微服務的關鍵所在。後端

考慮到咱們正在着手嘗試創建微服務架構,所以其中最重要的目標就是保證團隊有能力以影響最低的方式經過不一樣速度對系統中的不一樣組件進行分別處理。所以,咱們但願整個團隊擁有自主性,可以找到最理想的實施手段並運行本身的服務,同時自由地根據業務須要作出快速變動。若是咱們的團隊可以知足這些既定要求,那麼系統架構也將對應發生變化,即真正向微服務時代邁進。緩存

爲了實現這種自主性,咱們須要「擺脫依賴性」,但這項目標顯然是說着容易作起來難。我曾見證過無數從業者將微服務概念簡單理解爲「每項微服務都應該擁有並控制本身的數據庫,且任意兩項服務不該共享同一套數據庫」。這樣的理解很是合理,由於只要嚴格遵循上述要求,咱們就不會遭遇由跨服務共享數據庫所帶來的讀/寫模式、數據模型衝突以及協調挑戰等等。然而,單一數據庫的實現方式也確實擁有諸多安全與便利性優點:ACID事務、着眼點單1、易於理解、單點管理等等。那麼在構建微服務時,咱們該如何順利將單一數據庫拆分紅多套規模較小的數據庫?安全

下面讓咱們共同找出答案。首先,對於一家有意構建微服務的「企業」,咱們須要明確如下幾個問題:網絡

  • 立足怎樣的業務領域?現實狀況又是如何?架構

  • 事務邊界在哪裏?併發

  • 微服務應當如何在不一樣邊界之間實現通訊?負載均衡

  • 咱們將數據庫剝離出來會形成怎樣的影響?框架

立足怎樣的業務領域?

這個問題每每受到人們的忽視,但必須認可微服務實踐在互聯網企業與傳統企業當中每每存在着巨大差別(而這種忽視也是形成項目失敗的主要緣由之一)。異步

在構建微服務架構以及肯定相關數據使用理由(例如用於生產/消費等)以前,咱們首先要對數據的含義擁有清晰而正確的認識。舉例一個團購網站,在咱們將信息存儲在與「預訂」相關的數據庫並把其進一步遷移至微服務以前,咱們先要理解「預訂是什麼」。就像在HR領域同樣,你們可能還須要瞭解「賬戶」、「員工」以及「索賠」等相關定義。

爲了實現這一目標,咱們須要立足於現實回答更多關於理解的問題。例如,「書是什麼?」雖然這個例子很是簡單,但卻可以很好地反映實際工做中的概念理解過程。所以,思考書的定義,然後爲其設計合適的數據模型。

書是由不一樣分頁所構成嗎?報紙算不算書籍?由於其一樣擁有分頁結構。那麼書籍是否擁有硬質封面?或者表明的是天天按期出版的各類刊物?若是我本人撰寫了一部書籍,出版商可能只會在做者名下列出單一條目。但若是某家書店在出售五本由我撰寫的書籍,那麼這些書是彼此不一樣仍是同一本書的多份副本?咱們該如何表示這種關係?若是書籍內容過長,被拆分紅了多個分卷又該如何處理?每部分是否也應算是獨立的書籍?又或者所有分卷加起來纔算一本書?衆多細小部分組合起來應該如何處理?由此組成的總體算不算是書籍?又或者每一個部分都屬於書籍?最後再提出一種比較複雜的假設,我出版了一本書,書店中擺着大量一樣內容的印本,而每種印本又分爲多個分卷。在這種狀況下,咱們該如何定義「書」的概念?

現實狀況是,並不存在這樣確切的定義。「書是什麼」這個問題沒有一個絕對的概念,所以咱們在回答以前須要瞭解「誰在提問以及上下文背景是什麼。」換言之,即現在的上下文爲王概念。咱們人類可以快速(甚至是無心識地)解決這種認識模糊性問題,由於咱們頭腦中存在着上下文背景,其中涵蓋環境與問題自己。但計算機顯然缺少這種認知基礎。咱們須要在構建軟件與創建數據模型時,爲其指定相關上下文。用書的概念可讓這樣的理解過程變得更爲明確。咱們所處的業務領域(或者說企業指向)擁有其賬戶、客戶、預訂、索賠等事務,其概念顯示更加複雜且易產生衝突/模糊性。所以,咱們須要爲其設定明確的邊界。

那麼新的問題來了,咱們該如何繪製這些邊界?爲了實現對所在領域的建模,咱們須要對實體、值對象以及聚合等概念設定上下文。換言之,咱們藉此創建並完善整套表明所在業務領域的模型,該模型包含上下文中關於邊界的各項定義。這種明確的邊界最終將成爲咱們的微服務,或者做爲微服務的邊界內組件,抑或兩者兼有。不管如何,微服務的核心在於邊界,DDD(即業務驅動型設計)亦是如此。

圖片描述
咱們的數據模型(即但願如何表現物理數據存儲中的概念——注意其中的明確區別)受到業務領域模型的驅動,而非前者驅動後者。在設定了邊界以後,咱們將可以識別模型當中「正確」與不正確的部分。這些邊界同時也可以實現必定程度的自主性。爲上下文「A」指定一個與上下文「B」不一樣的「書籍」定義(例如將上下文‘A’設定爲一項搜索服務,負責搜索標題被指定爲‘書’的條目; 而上下文‘B’則爲檢查服務,負責根據書籍總量(即標題加副本)處理事務)。

在這裏,你們可能會提出質疑:「等等,Netflix、Twitter或者領英並無給出過任何關於業務驅動設計的說明。爲何我要遵循所謂DDD原則?」下面一塊兒來看緣由:

「人們都但願複製Netflix,但卻只能複製到其表象。他們複製到的只是結果,而並不是過程」——Netflix公司前任首席雲架構師Adrian Cockcroft

邁向微服務的旅程就是這麼一段……旅程。每一家企業都擁有本身須要克服的坎坷與艱辛。其中並不存在任何固定或者便捷的規則,而只有權衡與取捨。複製某家企業的成功做法並不足以幫助咱們一樣順利地完成這一流程/旅程,其甚至可能根本沒法奏效。另外,也正是這樣的思路才讓咱們的企業沒法成爲Netflix。事實上,我一直認爲不管Netflix的領域複雜性有多麼可怕,其仍然沒法與傳統企業面臨的難題相提並論。搜索並顯示影片、發佈推文、更新領英動態等等絕對要比保障索賠處理系統簡單得多。這些互聯網企業之因此可以快速迎接微服務,是由於其須要出色的產品上市速度並應對批量/規模化負載(向Twitter發佈一條推文很是簡單,但面向5億用戶發佈推文並顯示內容則很是複雜)。現在的企業將不得不一樣時面臨來自領域與規模兩個層面的複雜性挑戰。所以,咱們在這段轉型旅程當中必須在領域、規模以及組織變動之間作出權衡。每家企業都面臨着不一樣的難題,而這也必須成爲咱們的關注重點。

事務邊界是什麼?

從新回到咱們的主要議題上來。咱們須要利用業務驅動設計等手段幫助自身理解用於實現系統的模型,同時在單一上下文當中圍繞這些模型繪製邊界。所以,咱們須要將客戶、賬戶、預訂等事務視爲被綁定至不一樣上下文的不一樣事物,但最終亦須要立足於同一架構對這些相關概念進行分發,並在發生變動時調和這些模型所受到的具體影響。咱們須要將這些狀況所有歸入考量,但首先確立事務邊界無疑是最爲重要的前提。

遺憾的是,做爲開發者的咱們彷佛仍然在以徹底錯誤的方式進行分佈式系統構建:咱們仍然遵循着單1、孤立的關係型ACID數據庫思路做爲指導方針。咱們還忽略了異步且可靠性較低的網絡環境可能帶來的風險。要知道,編寫框架這類工做並不要求從業者對網絡(包括RPC框架——數據庫抽象方案亦嚴重忽略了網絡因素)有多麼瞭解,這意味着多數人都傾向於或者只知道如何利用點到點同步調用一切(包括REST、SOAP以及對象序列化RPC庫等其它CORBA)。咱們構建的系統沒有考慮到受權與自主間的平衡,並且最終每每只能依靠覆蓋衆多獨立服務的兩段式提交方式解決數據分發問題。或者,咱們完全忽略了以上各項要求。由此創建起的系統擴展性極差且至關脆弱——即便咱們非要將其稱爲SOA、微服務乃至迷你服務等等,其仍然不具有此類架構真正須要擁有的特性與優點。

那麼咱們到底該如何理解事務邊界?我將其理解爲業務常量當中最小的基本單位。不管你們是利用數據庫的ACID屬性以實現這種基礎性,抑或是選擇二段式提交,其實都可有可無。真正的重點在於,咱們但願儘量縮小這些事務邊界(在理想狀態下,應該是單一事務對應單一對象),從而確保其可擴展能力。當咱們創建本身的業務領域模型時,使用DDD技術以明確實體、值對象與聚合。在此上下文當中,聚合表明的是那些包含其它實體/值對象的對象,負責執行不變量(單一有界上下文當中可包含多組聚合)。

舉例來講,讓咱們設定如下幾種用例:

  • 「容許客戶搜索航班信息。」

  • 「容許客戶在特定航班中選擇座位。」

  • 「容許客戶預訂航班。」

咱們在這裏能夠設置三種有界上下文:搜索、預訂與票務(固然,咱們也能夠設置更多相關上下文,包括支付、忠誠度、待機、升艙等,但這裏咱們就只強調這三種)。搜索負責顯示特定路線以及與給定時間相符的航班選項。預訂則負責利用客戶信息創建預訂流程(包括姓名、地址、會員編號等)、座位偏好以及支付信息。票務將負責與航空公司溝通並完成機票簽發。在各有界上下文當中,咱們都須要肯定事務邊界以執行約束/恆量。咱們不須要在有界上下文當中考慮基本事務(亦稱爲原子事務,咱們將在下一章節中對此進行詳盡說明)。

那麼咱們要如何在建模時容許考慮最小事務邊界(在本示例中,其應被簡化爲航班預訂流程)?也許設置一套「航班」聚合是不錯的做法,其中能夠囊括時間、日期、路線以及客戶、機型以及預訂等實體條目?聽起來頗有搞頭,其中單一航班擁有具體的機型、座位、客戶與預訂條目。航班聚合負責在預訂建立過程當中追蹤機型及座位等信息。在這種狀況下,咱們能夠選擇在數據庫當中創建一套數據模型(這是一種標準的關係模型,其中包含約束與外鍵等),或者在源代碼中生成一套對象模型(繼承/組合),下面咱們看看具體做法會帶來怎樣的變數。

圖片描述
在利用預訂、機型以及航班等信息建立一條預訂時,其中是否真的存在恆量?換言之,若是咱們向航班聚合當中新增一種機型信息,那麼咱們是否真的應當將客戶與預訂包含在該事務當中?答案恐怕是否認的。咱們在這裏須要考慮的是如何利用可組合數據模型創建聚合。然而,這樣的事務邊界太過龐大。若是咱們面對着大量航班、座位與預訂信息的變動,則可能出現衆多事務衝突。並且這種做法顯然不具有可擴展性(並且一次航班計劃變動就會帶來大量訂單失敗,從而形成糟糕的客戶體驗)。

那麼咱們顯然須要把事務邊界設定得更小一點。

也許預訂、可選座位以及航班三者自有擁有本身的獨立聚合。一條預訂當中可包含客戶信息、偏好以及可能的支付信息。可用座位聚合則包含有機型與內部配置。航班聚合由日程安排與路線構成,等咱們可以在不影響航班規劃以及機型/可用座位等事務的前提下,隨時調整預訂內容。從領域的角度來看,咱們但願實現這樣的效果。咱們不須要在機型/航班/預訂之間實現100%的嚴格一致,而是但願確保管理員可以正確記錄航班規劃變動、供應商正確提供機型配置並由客戶正確完成預訂。那麼咱們該如何完成在航班中「選擇特定座位」這樣的操做?

在預訂過程當中,咱們可能須要調用可用座位聚合並要求其在飛機上保留一個座位。這一座位保留操做可能做爲單一事務(例如保留座位23A)實現,同時返回一個保留ID。咱們能夠將該保留ID分配給對應預訂,然後將該分配有座位的預訂提交爲「保留」點。其中的每一個環節(包括保留座位與接受預訂)都做爲獨立事務存在,且可在無需任何二段式提交或者二段式鎖定的前提下獨立進行。

須要注意的是,這裏使用「保留」是一種業務要求。咱們在這裏並不進行實際座位分配,而只是保留該座位。此要求可能須要經過模型迭代對其進行約束,這是由於該用例的初始做用描述可能只是「容許客戶選擇某個座位」。開發人員可能會對其作出不一樣理解,例如「從剩餘的座位中挑選並分配給客戶,然後將該座位從清單中移除,避免其被重複出售」。這將會給咱們的事務模型增長額外的負擔,由於其中的業務並無被真正做爲恆定量處理。此業務在處理預訂操做時表現良好,但卻沒法解決座位分配與航班超額銷售等問題。

圖片描述

經過以上實例,你們應該已經清楚了爲何咱們須要爲單一聚合提供儘量小、儘量簡單且最爲基本的事務邊界。不過事情到這裏還沒結束,由於咱們如今須要解決新的問題,即如何將這些高度獨立的事務整合起來。其中涉及多種不一樣數據成分(即咱們建立了一條預訂與座位保留設置,但其還沒有被真正歸入登機牌/出票流程)。

微服務該如何在不一樣邊界間完成通訊?

咱們但願保有真正的恆定業務量。在DDD的幫助下,咱們能夠選擇將這些恆定量建模爲聚合,並強制要求每一聚合對應一項事務。有時候咱們可能須要在單一事務當中更新多種聚合(中專單一或者多套數據庫),但此類場景算是意外狀況。咱們還須要在不一樣聚合之間保持必定程度的一致性(並最終在不一樣邊界上下文間保持這種一致性),那麼這一點要如何實現?

首先須要強調的是,分佈式系統很是難以打理。事實上,幾乎沒人可以在毫無挫折的前提下在有限的時間內創建起一套分佈式系統(可能出現的問題包括故障、不肯定緣由形成的性能緩慢或者服務停機、系統中出現非同步時間邊界等),那麼咱們爲何非要使用這樣一種系統?咱們可否將其整合在跨越自身領域的一致性模型當中?在必要事務邊界之間使用不一樣數據與領域組成部分,並在以後的特定時間點上從新實現一致性——這樣的思路又是否可行?

正如以前所提到,咱們一直在強調微服務的自主權價值。咱們須要有能力對其它系統進行獨立變動(具體包括可用性、協議與格式等形式)。這將把時間元素解耦出來,確保任意服務之間的對象在任意時間段內皆可實現這種自主性優點。

如前所述,在不一樣事務邊界與有界上下文之間使用事件以完成一致性通訊。事件屬於恆定結構,用於捕捉特定時間點中應當被廣播給受衆的重要點。受衆則接收本身感興趣的事件並根據該數據制定決策、存儲該數據、存儲該數據的部分衍生信息、根據所制定決策更新部分自有數據等等。

繼續以以前提到的航班預訂爲例,在經過ACID類事務進行預訂信息存儲時,咱們要如何最終完成出票?

在這裏,以前提到的票務有界上下文要發揮做用了。預訂有界上下文將會發布一條相似於「新預訂建立」之類的事件,而票務有界上下文則消息此事件並隨後與後端(多是傳統)出票系統進行交互。其中顯然要涉及某種集成與數據轉換,咱們能夠利用Apache Camel來實現。另外,這一流程還帶來了其它幾個問題。咱們該如何向數據庫寫入並立足於基本層面向設備發佈一條隊列/消息?另外,若是咱們在不一樣事件之間進行了屢次請求,結果會如何?再有,爲每項服務指定單一配套數據庫,效果又會有什麼時候不一樣?

在理解狀況下,咱們的聚合將直接使用命令與領域事件(做爲最優做法,即所有操做皆做爲命令執行,而任何響應都做爲事件響應存在),並可以更爲明確地將內部使用的事件與有界上下文進行映射。

咱們能夠直接將事件(即新預訂建立事件)發佈至消息收發隊列,然後由監聽方從隊列中進行消費,並將其插入至數據庫當中——不使用XA/2PC事務,而是直接插入數據庫自身。咱們能夠將該事件插入至某個既做爲數據庫又做爲消息發佈-訂閱主題(在本示例中可能表明偏好航線)的特定事件。或者,咱們也能夠繼續使用ACID數據庫並將變動以數據流方式提交至數據庫,然後由Apache Kafka等持久性複製日誌系統利用某種事件處理器/流處理器進行事件處理。不管選擇哪一種方式,咱們的最終目標是經過恆定時間事件點實現不一樣邊界間的通訊。

圖片描述
這種做法可以實現如下幾大關鍵優點:

  • 避免使用成本高昂甚至可能沒法實現的跨邊界事務模型。

  • 咱們能夠對自身系統進行變動,而無需影響到系統其它組件的執行進程(包括計時與可用性)。

  • 咱們能夠決定與外界間的同步節奏,從而在不佔用過多資源的前提下保持必定程度的同步性。

  • 咱們能夠將數據存儲在自有數據庫內,同時爲本身的服務選擇最合適的配套技術方案。

  • 咱們能夠在閒暇時段對自身模式/數據庫作出變動。

  • 咱們將擁有更出色的可擴展性、容錯性以及靈活性。

  • 咱們須要高度關注CAP定理以及用於實現存儲/隊列機制的各項技術。

但必須認可,這種做法也會帶來如下弊端:

  • 其複雜程度更高。

  • 更難於調試。

  • 因爲在查看事件時可能存在延遲,所以咱們沒法假設其它系統知曉實時狀況(利用其它方案可能一樣沒法實現實時反饋,但這種做法的延遲性更爲明顯)。

  • 更加難以操做。

  • 咱們須要高度關注CAP定理以及用於實現存儲/隊列機制的各項技術。

我在優點與弊端當中都列出了「高度關注CAP……」這一條,由於儘管其對於你們而言多是一種負擔,但卻有着必須這樣作的理由!只有作到這一點,咱們纔可以時刻確保分佈式數據系統當中不一樣形式數據的一致性與併發性!單純認爲「咱們的數據庫符合ACID」已經不足以說明問題(特別是ACID數據庫極可能默認存在一致性薄弱的問題)。

這種方案中涉及的另外一項有趣概念名爲「命令查詢間隔責任」,這意味着咱們要將實際模型與寫入模型拆分紅多個獨立服務。請注意,以前提到過互聯網企業每每無需面對複雜的領域模型,這一點已經經過其編寫的簡單模型獲得了充分證實(例如能夠將一條推文插入至一份分佈式日誌當中)。

然而,他們的讀取模型卻因爲規模極大而複雜到喪心病狂。CQRS可以幫助互聯網廠商解決這些問題。在另外一方面,傳統企業的寫入模型則每每更加複雜,而讀取模型則因爲只涉及普通的選擇查詢與DTO對象而顯得比較簡單。CQRS是一種強大的拆分手段,可以在邊界設置穩當後執行評估,同時在不一樣聚合與有界上下文之間完成數據變動。

那麼,若是一項服務只具備一套數據庫且不一樣其它服務分享該數據庫,結果又會如何?在這種狀況下,咱們的受衆可能會訂閱事件流並將主聚合所使用的數據插入到一套共享數據庫當中。這種「共享式數據庫」的實際效果很是理想。請注意,具體實現方式並沒有規則,而只是作出權衡。在本示例當中,咱們可讓多項服務共同圍繞同一數據庫起效,並且因爲咱們的團隊擁有所有進程,因此無需在自主權方面作出妥協。所以,若是下次有人提到「微服務就應該擁有本身的數據庫,並且不與其它服務共享該數據庫」,你們能夠雲淡風清地迴應「差很少吧」。

若是咱們將以前章節中的概念推向邏輯極端,會形成怎樣的結果?若是咱們利用事件/流處理一切,同時又想保證事件間永遠一致,該怎麼辦?若是咱們將數據庫/緩存/索引僅僅視爲過往日誌/事件流的一種持久性物化視力,而當前狀態則屬於所有事件的順序集合,結果又會怎樣?

最後,咱們再來補充一些此種事件間通訊方案所能帶來的實際收益:

  • 如今你們可以將本身的數據庫視爲一種記錄的「當前狀態」,而非實際記錄。

  • 你們能夠引入新型應用並重讀以往事件,從而檢查其行爲「會發生哪些變化」。

  • 你們能夠完善審計記錄而無需承擔任何成本。

  • 你們能夠引入新的應用程序版本並經過重播事件的方式完成詳盡測試。

  • 你們能夠更輕鬆地在新數據庫中重播事件,從而明確數據庫版本控制/升級/模式的變動理由。

  • 你們能夠遷移至其它全新數據庫技術(即現有關係型數據庫已經沒法知足需求,這時咱們可能須要切換至其它特定數據庫/索引機制)。

圖片描述
當咱們在aa.com或者united.com網站上預訂某次航班時,就能看到各項概念的起效過程。在選定某個座位時,該座位實際並無被真正分配給咱們,而只是獲得了保留。在預訂航班時,咱們也尚未真正完成出票。使用過這類服務的朋友都知道,咱們稍後會經過郵件或者短信獲得出票確認。那麼你們有沒有經歷過航班變化而致使實際座位與預訂不符的狀況?或者是已經登機完成,卻發現因爲超量出票而不得不站到終點?這就是事務邊界、最終一致性、補償事務乃至事故帶來的真實案例。

故事帶來的啓示

這個故事帶來的啓示在於,數據、數據集成、數據邊界、企業使用模式、分佈式系統理論以及計時機制等等都屬於微服務中的重要組成部分(由於微服務實際上就是一套分佈式系統)。我發現不少朋友對於這項技術還存在着嚴重誤解(例如‘我用了Spring Boot,因此我創建的是微服務’、‘我須要解決服務發現與雲端的負載均衡問題,然後才能實現微服務’乃至‘我必須爲每項微服務提供一套對應的數據庫’等等),併爲微服務制定了一大堆無用的「規則」。別擔憂,衆多大型廠商已經介入並開始銷售各種相關產品,因此其它問題都將很快獲得解決——只留下最困難的部分,數據。

文章來源:DZone 做者:Christian Posta

相關文章
相關標籤/搜索