您好,登錄后才能下訂單哦!
微服務設計的一個關鍵是數據庫設計,基本原則是每個服務都有自己單獨的數據庫,而且只有微服務本身可以訪問這個數據庫。它是基于下面三個原因。
理想的設計是你的數據庫只有你的服務能訪問,你也只調用自己數據庫中的數據,所有對別的微服務的訪問都通過服務調用來實現。當然,在實際應用中,單純的服務調用可能不能滿足性能或其他要求,不同的微服務都多少需要共享一些數據。
微服務之間的數據共享可以有下四種方式。
有一些靜態的數據庫表,例如國家,可能會被很多程序用到,而且程序內部需要對國家這個表做連接(join)生成最終用戶展示數據,這樣用微服務調用的方式就效率不高,影響性能。一個辦法是在每個微服務中配置一個這樣的表,它是只讀的,這樣就可以做數據庫連接了。當然你需要保證數據同步。這個方案在多數情況下都是可以接受的,因為以下兩點:
如果你需要讀取別的數據庫里的動態業務數據, 理想的方式是服務調用。如果你只是調用其他微服務做一些計算,一般情況下性能都是可以接受的。如果你需要做數據的連接,那么你可以用程序代碼來做,而不是用SQL語句。如果測試之后性能不能滿足要求,那你可以考慮在自己的數據庫里建一套只讀數據表。數據同步方式大致有兩種。如果是事件驅動方式,就用發消息的方式進行同步,如果是RPC方式,就用數據庫本身提供的同步方式或者第三方同步軟件。
通常情況下,你可能只需要其他數據庫的幾張表,每張表只需要幾個字段。這時,其他數據庫是數據的最終來源,控制所有寫操作以及相應的業務驗證邏輯,我們叫它主表。你的只讀庫可以叫從表。 當一條數據寫入主表后,會發一條廣播消息,所有擁有從表的微服務監聽消息并更新只讀表中的數據。但這時你要特別小心,因為它的危險性要比靜態表大得多。第一它的表結構變更會更頻繁,而且它的變更完全不受你控制。第二業務數據不像靜態表,它是經常更新的,這樣對數據同步的要求就比較高。要根據具體的業務需求來決定多大的延遲是可以接受的。
另外它還有兩個問題:
除非你能用服務調用(沒有本地只讀數據庫)的方式完成所有功能,不然不管你是用RPC方式還是事件驅動方式進行微服務集成,上面提到的問題都是不可避免的。但是你可以通過合理規劃數據庫更改,來減少上面問題帶來的影響,下面將會詳細講解。
這是最復雜的一種情況。一般情況下,你有一個表是主表,而其他表是從表。主表包含主要信息,而且這些主要信息被復制到從表,但微服務會有額外字段需要寫入從表。這樣本地微服務對從表就既有讀也有寫的操作。而且主表和從表有一個先后次序的關系。從表的主鍵來源于主表,因此一定先有主表,再有從表。
上圖是例子。假設我們有兩個與電影有關的微服務,一個是電影論壇,用戶可以發表對電影的評論。另一個是電影商店。“movie”是共享表,左邊的一個是電影論壇庫,它的“movie”表是主表。右邊的是電影商店庫,它的“movie”表是從表。它們共享“id”字段(主鍵)。主表是數據的主要來源,但從表里的“quantity”和“price”字段主表里面沒有。主表插入數據后,發消息,從表接到消息,插入一條數據到本地“movie”表。并且從表還會修改表里的“quantity”和“price”字段。在這種情況下,要給每一個字段分配一個唯一源頭(微服務),只有源頭才有權利主動更改字段,其他微服務只能被動更改(接收源頭發出的更改消息之后再改)。在本例子中, “quantity”和“price”字段的源頭是右邊的表,其他的字段的源頭都是左邊的表。本例子中“quantity”和“price”只在從表中存在,因此數據寫入是單向的,方向是主表到從表。如果主表也需要這些字段,那么它們還要被回寫,那數據寫入就變成雙向的。
這種方式是要絕對禁止的。生產環境中的許多程序錯誤和性能問題都是由這種方式產生的。上面的三種方式由于是另外新建了本地只讀數據庫表,產生了數據庫的物理隔離,這樣一個數據庫的性能問題不會影響到另一個。另外,當主庫中的表結構更改時,你可以暫時保持從庫中的表不變,這樣程序還可以運行。如果直接訪問別人的庫,主庫一修改,別的微服務程序馬上就會報錯。
從上面的論述可以看出,數據庫表結構的修改是一個影響范圍很廣的事情。在微服務架構中,共享的表在別的服務中也會有一個只讀的拷貝。現在當你要更改表結構時,還需要考慮到對別的微服務的影響。當在單體(Monolithic)架構中,為了保證程序部署能夠回滾,數據庫的更新是向后兼容的。需要兼容性的另一個原因是支持藍綠發布(Blue-Green Deployment)。在這種部署方式中,你同時擁有新舊版本的代碼,由負載均衡來決定每一個請求指向那個版本。它們可以共享一個數據庫(這就要求數據庫是向后兼容的),也可以使用不同的數據。數據庫的更新簡單來講有以下幾種類型:
向后兼容的數據庫更新的好處是,當程序部署出現問題時,如需進行回滾。只要回滾程序就行了,而不必回滾數據庫。回滾時一般只回滾一個版本。凡是需要刪除的表或字段在本次部署時都不做修改,等到一個或幾個版本之后,確認沒有問題了再刪除。它的另一個好處就是不會對其他微服務中的共享表產生立刻的直接影響。當本微服務升級后,其他微服務可以評估這些數據庫更新帶來的影響再決定是否需要做相應的程序或數據庫修改。
微服務的一個難點是如何實現跨服務的事物支持。兩階段提交(Two-Phase Commit)已被證明性能上不能滿足需求,現在基本上沒有人用。被一致認可的方法叫Saga。它的原理是為事物中的每個操作寫一個補償操作(Compensating Transaction),然后在回滾階段挨個執行每一個補償操作。示例如下圖,在一個事物中共有3個操作T1,T2,T3。每一個操作要定義一個補償操作,C1,C2,C3。事物執行時是按照正向順序先執行T1,當回滾時是按照反向順序先執行C3。 事物中的每一個操作(正向操作和補償操作)都被包裝成一個命令(Command),Saga執行協調器(Saga Execution Coordinator (SEC))負責執行所有命令。在執行之前,所有的命令都會按順序被存入日志中,然后Saga執行協調器從日志中取出命令,依次執行。當某個執行出現錯誤時,這個錯誤也被寫入日志,并且所有正在執行的命令被停止,開始回滾操作。
Saga放松了對一致性(Consistency)的要求,它能保證的是最終一致性(Eventual Consistency),因此在事物執行過程中數據是不一致的,并且這種不一致會被別的進程看到。在生活中,大多數情況下,我們對一致性的要求并沒有那么高,短暫的不一致性是可以接收的。例如銀行的轉賬操作,它們在執行過程中都不是在一個數據庫事物里執行的,而是用記賬的方式分成兩個動作來執行,保證的也是最終一致性。
Saga的原理看起來很簡單,但要想正確的實施還是有一定難度的。它的核心問題在于對錯誤的處理,要把它完全講明白需要另寫一遍文章,我現在只講一下要點。網絡環境是不可靠的,正在執行的命令可能很長時間都沒有返回結果,這時,第一,你要設定一個超時。第二,因為你不知道沒有返回值的原因是,已經完成了命令但網絡出了問題,還是沒完成就犧牲了,因此不知道是否要執行補償操作。這時正確的做法是重試原命令,直到得到完成確認,然后再執行補償操作。但這對命令有一個要求,那就是這個操作必須是冪等的(Idempotent),也就是說它可以執行多次,但最終結果還是一樣的。
另外,有些操作的補償操作比較容易生成,例如付款操作,你只要把錢款退回就可以了。但有些操作,像發郵件,完成之后就沒有辦法回到之前的狀態了,這時就只能再發一個郵件更正以前的信息。因此補償操作不一定非要返回到原來的狀態,而是抵消掉原來操作產生的效果。
我們原來的程序大多數都是單體程序,但現在要把它拆分成微服務,應該怎樣做才能降低對現有應用的影響呢?
我們用上面的圖來做例子。它共有兩個程序,一個是“Styling app”,另一個是“Warehouse app”,它們共享圖中下面的數據庫,庫里有三張表,“core client”,“core sku”,“core item”。
假設我們要拆分出來一個微服務叫“client-service”,它需要訪問“core client”表。第一步,我們先把程序從原來的代碼里拆分出來,變成一個服務. 數據庫不動,這個服務仍然指向原來的數據庫。其他程序不再直接訪問這個服務管理的表,而是通過服務調用或另建共享表來獲取數據。
第二步,再把服務的數據庫表拆分出來,這時微服務就擁有它自己的數據庫了,而不再需要原來的共享數據庫了。這時就成了一個真正意義上的的微服務。
上面只講了拆分一個微服務,如果有多個需要拆分,則需一個一個按照上面講的方法依次進行。
另外,Martin Fowler有一個很好的建議。那就是,當你把服務從單體程序里拆分時,不要只想著把代碼拆分出來。因為現在的需求可能已經跟原來有所不同,原先的設計可能也不太適用了。而且,技術也已更新,代碼也要作相應的改造。更好的辦法是重寫原來的功能(而不是重寫原來的代碼),把重點放在拆分業務功能上,而不是拆分代碼上,用新的設計和技術來實現這個業務功能。
數據庫設計是微服務設計的一個關鍵點,基本原則是每個微服務都有自己單獨的數據庫,而且只有微服務本身可以訪問這個數據庫。微服務之間的數據共享可以通過服務調用,或者主、從表的方式實現。在共享數據時,要找到合適的同步方式。在微服務架構中,數據庫的修改影響廣泛,需要保證這種修改是向后兼容的。實現跨服務事物的標準方法是Saga。當把單體程序拆分成微服務時,可以分步進行,以減少對現有程序的影響。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。