1 ~ 5
ch1 ~ ch5
ch.1 微服務 ch.2 演化式架構師 ch.3 如何建模服務 ch.4 集成 ch.5 分解單塊系統
能否修改&部署一個微服務,並且不影響其他服務? 不要嘗試讓所有服務都使用一種標準化的技術
架構安全性 我們的服務應該要很好的處理下游的錯誤請求
正常 & 被正確處理的 request 錯誤 & 服務有識別是錯誤,所以什麼也沒做 被訪問的服務當機,無法判斷請求是否正常
願景:充分溝通的技術願景,此願景可以滿足客戶及組織 同理心:對客戶和同事有同理心 合作:盡量多溝通,從而更好的對願景作定義、修訂、執行 適應性:在客戶、組織需要時,調整技術願景 自治性:在標準化、團隊自治之間找到平衡點 治理:確保系統按照技術願景實現
如何建構微服務
鬆耦合 盡可能地減少需要知道其他服務的事情
高內聚 把相關的行為聚集在一起
限界上下文 每個領域都包含多個限界上下文,每個限界上下文的模型可以分成兩個部分,一部分不需要與外部溝通,一部分則需要。 每個限界上下文都有明確的接口,該接口決定了它會曝露那些模型給其他上下文。
限界上下文 是找尋接縫的一個重要的工具 通過微服務將這些邊界匹配
Eric Evans <領域驅動設計>
Vaughn Vernon <實現領域驅動設計>
針對請求/響應方式 PRC Remote Procedure Call REST Repersentational State Transfer
PRC 的核心想法 : 隱藏遠程調用 其中有一些依賴接口定義(SOAP、Thrift、protocol buffers) 有些RPC實現 與 特定的網路協議 綁定 (SOAP 名義上使用HTTP)
※不同的協議可以提供不同的特性,根據使用場景決定 TCP:能夠保證送達 UDP:雖然不能保證送達,但協議開銷較小
PRC的主要賣點之一:易於使用、快速啟動。 但其存在一些問題,一開始不明顯,但帶來的代價遠遠大於一開始的方便。
著名的錯誤觀點 "網路是可靠的" 要假設隨時有惡意攻擊者隨時有可能對網路進行破壞、因此網路出錯模式也不只一種。服務端可能會返回一個錯誤信息,或是請求本身就有問題。
如何區分各種不同的錯誤、故障方式? 區分之後如何處理?
REST 受Web啟發而產生的一種架構風格 REST 配合 HTTP 的 GET POST PUT DELETE
為了避免暴露太多東西,有一個有效的模式,是先設計外部接口,等接口穩定後,再實現內部的數據持久化(?)。副作用是推遲數據儲存的部分。在這之間,先將資料儲存到硬碟的文件上。這種做法,可以保證接口是根據使用者的需求產生的。
ATOM 一個符合 REST 規範的協議,提供資源聚合(feed)的發布服務。
響應式擴展 Reactive extensions , Rx 可以把多個調用的結果組裝起來,在此基礎上做操作。
以往: 我們會獲取數據,並對於數據做操作。 現在: 對操作的結果進行觀察(?),結果會根據數據的改變進行更新。
很多Rx的實作很適合應用在分布式系統,因為調用的細節被隱藏,所以事情更容易處理。也可以直接觀察調用的結果,不用在乎它是同步 or 非同步。漂亮之處在於,可以把多個不同的調用結果組合起來,作相對應的處理。
服務及狀態機 不管用PRC 還是 REST,服務及狀態機的概念都很強大。 把關鍵領域的生命週期 顯示、視覺化 非常有用,我們不但可以在唯一的一個地方處理狀態衝突,還可以在狀態變化的基礎上封裝一些行為。
微服務世界中的DRY DRY, Don't Repeat Yourself 避免重複代碼,會比較好維護。
我們會希望微服務和 client 間避免耦合,否則微服務的任何小改動都可能導致 client 方也要改動,而共享代碼就有可能導致這種耦合。
建議: 微服務內部不要違反DRY 跨服務之間可以適當違反DRY
服務之間引入大量耦合,會比DRY造成更糟糕的問題。
客戶端庫,給Client 用的SDK (?) 建議開發 服務API 和 客戶端API 的不要是同一批人,因為同一批的話,會容易把服務端的邏輯洩漏到客戶端。
洩漏到客戶端的邏輯越多,內聚性就越差,當我們在修復服務端的問題時,也需要對Client進行修改。這樣做也會限制技術的選擇,尤其是當我們強制Client 必須使用客戶端庫的時候。
Netflix 非常強調客戶端的使用,但目的不僅僅只是為了避免代碼重複。事實上,Netflix 有另外一個同等重要的原因,保證系統的可靠性&可伸縮性。Netflix 的客戶端會處理類似服務發現、故障模式、日志等方面的工作。 不過後來還是有員工表示這樣做,產生了一定程度的耦合問題。
※如果要使用客戶端,一定要保證只處理底層傳輸,不要把與目標服務相關的邏輯放到客戶端中。並且想清楚是否允許別人使用不同的技術去呼叫API。並且確保由Client 來負責何時進行 Client的升級,這樣才能保證每個服務可以獨立於其他服務發布。
引用訪問 如何傳遞領域實體的相關資訊 是一個值得討論的話題。 微服務應該包含核心領域全生命週期的相關操作。
如果想要做跟客戶有關的改動,就必須向客戶服務發起請求。
有時發起請求,得到了一個客戶資源的本地副本。但可能在發送請求之後,有人對該資源進行了修改,持有越久,其內容失效的可能性就越高。
有時候使用本地副本是OK的,但有些場景你需要知道該副本是否已經失效,所以你要確保在持有本地副本的同時,也同時持有一個指向原始資源的引用,這樣在有需要的時候,可以進行更新。
有一種作法是 傳遞取得該資源的URI,當輪到該資源的時候,再取得該資源,並做相對應的動作。
還可以延伸 有效性時限,就可以做相對應的緩存。
版本控制
盡可能地推遲、減少破壞性修改。
Postel法則:寬進嚴出,對於自己出去的東西要嚴格,對於接收的東西要寬容。
語義化的版本管理 現在的版本,可以與那些版本相容,客戶端的版本。 如果一個Client 可以透過查看版本號,就可以知道能否和其他相容,那就太棒惹。
不同的接口共存 當我們做了所有的事情去避免接口的修改(但還是無法避免),那下一步就是避免其影響。 我們不想強迫Client 一起升級,因為希望微服務可以獨立發布。
解法1: 有一種方式,就是新、舊接口同時並存,所以在發布一個破壞性修改時,可以部屬一個同時包含新舊接口的版本。
當Client 都轉移到新接口,就可以把舊接口的相關代碼移除。
解法2: 擴展/收縮 的實例,允許我們對破壞性修改進行平滑的過度。
在內部進行轉換,讓舊接口的請求,去訪問新接口,使用這種方式,以後要刪除那些代碼也會比較清楚。
用戶介面
誰來創建用戶介面? 維護服務的往往不是服務的使用者,如果 UI 是另外一個團隊創建的,這樣要做修改時,需要多個團隊參與。
如果畫面的每個區塊,都要去 call 各自的API,可能會很吃流量,對於mobile使用者不利。
使用API入口 (gateway) 可以很好的緩解這個狀況。
建議: 為前端服務的後端, BFF, Backends For Frontends
這個後端,只為一個應用 or 一個用戶介面(desktop or mobile)做事 雖然嵌在服務端,但它是UI的組成部分。 而API認證、授權 的檢查,應該會在 UI 和 BFF 之間。 使用這種方式的風險與其他聚合層相同,就是不要包含業務邏輯。業務邏輯應該在服務中,這一層只負責實現與某種特定的UI相關的邏輯。
與第三方軟體集成
在第三方軟體的外面,再加設一層框架、介面,而這層的接口是我們設計的標準呼叫方式(比如 HTTP REST),而這一層則去因應各種第三方軟體,去做適應對方的呼叫,傳遞資料。
第四章結論
無論如何,避免直接去呼叫資料庫
理解 REST 和 RPC 的取捨,但總是使用 REST 作為 request/response
相比起編排,優先選擇協同
避免破壞性修改,理解 Postel 法則,使用容錯性讀取器
將用戶介面視為一個組合層
共享靜態數據的幾種方式 (國家代碼、主流瀏覽器、手機品牌..)
第一種: 為每個package 複製一份該表的內容,也就是說未來每個服務都會有一份該表的內容,當然 這會導致一個潛在的一致性問題。
第二種: 把這些共享的靜態數據放進代碼,比如放在屬性文件 or Enum 裡面。 數據一致性的問題依然存在,但是從經驗上看,修改配置文件,比修改線上資料庫來得簡單。
第三種: 極端一點,將這些靜態數據抽出來,放在一個單獨的服務中。 在某些場景下,數據量 & 重要性 值得我們這樣做,但如果只是一些少量、簡單的,就不必了。
共享動態數據 有時領域的概念不是在服務,而是在資料庫 這時新增加一個服務,去專門存取這個領域
共享table、重構資料庫 A服務和 B服務都會對 C表做操作 為了劃分清楚,拆成兩個表
建議: 當我們有一個單塊系統,我們拆成了兩個服務,資料庫的結構也有所異動,不建議在一次發布中,同時做這兩件事情。 會建議先把資料庫的異動先做發布,暫時先不對服務進行分離。 因為表的結構分離之後,可能會造成連線次數變多、破壞事務的完整性,對於應用程式造成影響。只先做一半,可以讓我先觀察狀況,看要繼續做下去,還是退回去原本的。
事務邊界
類似Transact 的概念 在單塊的時候,可以用Transact 去達到完整的一致性。 但是分離資料庫之後,這種好處就沒有了。
最終的一致性 (11章 scaling) 再試一次,將沒做成功的事情,放進佇列,之後再做一次。 對於某些操作來說,這樣是合理的(前提是重試可以成功解決問題)
終止整個操作 需要將系統重置到某種一致的狀態。假如一個失敗,要將其它的也還原、退回,則發起 補償事務 去抵銷之前的操作。 要用程式自動控制呢? 還是後台增加介面去做維護呢?
分布式事務、事務管理器 分布式事務 常用的算法是 兩階段提交,投票階段、提交操作。 但這個會使參與者暫停、導致系統中斷,如果事務管理器當機,就爆炸了。 如果真的要使用,可以用一些現成的API,例如JAVA 的 事務API,可以減少錯誤。
如果遇到的場景確實需要保持一致性,那麼盡量避免放在不同地方,如果實在不行,那麼要避免從純技術的角度去考慮。可以用不同的角度,讓他們概念上是同類型的事務,而去整合在一起。
報告
為了避免對主系統的效能造成影響,會把報告系統去指向副本資料庫,從中讀取數據。 缺點是,主系統跟報告系統會因為 table 而產生耦合。
一種方式:報告系統去讀取各個服務的API,去組合出自己需要的資料,但有很大的可能,是服務並沒有提供完全切合的API,那就要呼叫各種不同的API,再從中挑選、組合。這要效率會比較差。
一種是:主動把數據丟到報告系統。寫一支AP,專門去各服務將需要的資料丟到報告資料庫。(少數可以直接去耦合資料庫的例外。因為它讓報告變得很簡單,可以抵銷耦合帶來的缺點) 雖然 table 的耦合依然存在,但我們可以把它當作是一種公共API 這種比較穩定的東西。另外,我們可以用 view 去創建一個給指定報告所使用的結構。
事件數據導出 因為每個微服務 可以在自己管理的實體 狀態改變時,發送一些事件。 對於這些暴露事件聚合(feed) 的微服務來說,可以編寫自己的事件訂閱器,把數據丟到報告的資料庫去。
使用這種方式,可以將微服務和底層資料庫的耦合消除。我們只要綁定服務所發送的事件即可,而這些事件本來就是暴露給Client的。
因為是在事件發生時就給報告系統發送數據,所以資料可以更快的更新到資料庫。 如果我們還有紀錄那些事件被處理過,可以讓我們只對新事件進行處理,這意味著 Insert 會更有效率。
因為 事件數據導出 的方式與服務的內部耦合很小,所以可以將這個部分交給另外一個獨立的團隊來維護。
缺點: 所有的信息都用事件來廣播,所以數據量大時,不容易在資料庫的層級做擴展。 但不論如何,如果已經有適合的事件,還是建議用這種方式,它帶來的低耦合 & 數據時效性,是更好的。
CRC , Class-Responsibility Collaboration 是一種收集並整理卡片的開發方式。
一個CRC卡片,由類別名稱、職責、協作者。
類別名稱:一組相似的對象,人 事 地 物 都可以~ 名稱盡量簡單,通常是一個名詞或一個名詞詞句。
職責:明確此類別應該知道或該做的事情 類可以對自己所知道的東西改值,而不能對其他類改值。
協作者:需要的信息、需要的動作。(需要相互作用的其他類。)
如何構建一個CRC模型呢? 只要循環以下步驟
找出類
進行分析任務的基礎工作。可分為實體類(Entity Class) 和 UI類(User Interface 介面)。 有時候某個角色,很難和實體類別對應時,也會用卡片去代表角色。
找出職責
除了應該問自己一個類應該做什麼,還應該問自己需要保留那些信息。 也經常會標記出與其他類協作的職責。
界定協作者
類經常會在執行其職責時,發現沒有足夠的信息支持。 因此它通常會配合(主導) 其他類 以便能完成工作。 協作應該是以下兩者之一:一個信息請求 or 一個任務執行請求。
移動卡片
為了讓所有人理解這個系統,所有的卡片應該使用容易理解的方式去做擺放,例如盡量把有協作關係的卡片放在一起,彼此沒有協作關係的盡量遠離。
參考資料:(https://blog.csdn.net/jgw2008/article/details/52512276)
Last updated