在 《Single Responsibility Principle》中,Robert C. Martin 所闡述的單一責任原則特別強化這個論點,『將那些因為相同理由而改變的東西集合起來,將那些因為不同理由而改變的東西分離開來』。微服務採取相同的作法,達成它的獨立性。 p. 2
當論及多小才算夠小時,我喜歡從這個觀點思考:服務越小,微服務架構的好處與缺點就越被放大。 p. 3
我們盡量避免將多個服務整合到相同的機器上,… (中略) …儘管這種隔離會增加一些開銷 (overhead),但它所產生的簡單性會讓分散式系統更容易理解,而且,較新穎的技術也能夠消弭許多與這種部署形式相關的困難與挑戰。 p. 3
不同服務之間透過網路呼叫來溝通,強化了服務之間的分隔,避免緊密耦合的危險性。 p. 3
這可能意味著,選用技術無關 (technology-agnostic) 的 API,確保我們不受限於特定技術選項。 p. 3
這允許我們針對每個任務選擇合適的工具,而不必選擇更標準、更一體適用的做法,那樣往往導致不甚理想的最『小』公約數。 p. 4
因為我能夠明確地限制潛在的負面影響。許多組織發現,這項能力讓他們得以更迅速地吸納新技術,成為組織的實際優勢。 p. 4
彈性工程 (resilience engineering) 的關鍵概念是隔艙 (bulkhead),如果系統的一個元件失敗,而該失敗並未蔓延,你就可以區隔問題,系統的其餘部分就能夠繼續運作。 p. 5
不幸的是,這也意味著,變更會在不同的釋出版本之間持續累積,最後,正式上線的新版應用包含一堆變更,而且,不同釋出版本之間的差異越大,犯錯的風險就越高! p. 6
使用微服務時,我們可以針對單一服務做變更,並且獨立於系統其餘部分進行部署,讓我們能夠更快速地部署程式碼,若有問題發生,它可以乾淨俐落地被隔離在個別的服務中,讓撤銷部署 (rollback) 更容易實現,同時,這也意味著,我們可以更快速地將新功能交給客戶手上。 p. 7
微服務方法衍生自現實世界的實際運用,讓我們更清楚地理解有助於妥善建構 SOA 的系統與架構,因此,你應該把微服務想成是 SOA 的特定解法,就像 XP 或 Scrum 被視為敏捷軟體開發的特定作法那樣。 p. 9
多個團隊可以圍繞著這些程式庫組織起來,而這些程式庫本身能被重利用,然而,這也存在著一些缺點。首先,你失去了技術異質性 (heterogeneity),程式庫通常必須以相同的語言寫成,或至少執行在相同的平台上。其次,你們彼此獨立地擴展系統零件的困難度變高。再者,除非使用動態連結程式庫,否則,沒有重新部署整個行程,就無法部署新的程式庫。 p. 9
無論如何,共用程式庫確實佔有一席之地,你會發現自己正在建立不屬於特定業務領域的通用程式碼,而且,你會想要在整個組織中重利用它,那正式成為共用程式庫的明顯候選人。然而,確實必須小心,被用來在不同服務之間進行溝通的共用程式碼會變成耦合點。 p. 9
架構師顯著且直接影響系統的建構品質、團隊成員的工作情境、以及組織回應變更的能力,遠超過其他角色 p. 13
架構師必須改變想法,放棄想要打造最終完美產品的思維,改為聚焦在協助建立可從中衍生出合適系統的框架,並且隨著我們聊解更多資訊而持續壯大。 p. 15
代替針對所有可能性制定完善的計畫,我們的計畫應該著重於容許變更發生,避免想要周全考慮一切的衝動。 … (中略) …架構師有責任確保系統也能夠讓開發人員在那裡安居樂業。 p. 15
應該擔心不同服務之間發生什麼事,而放任服務裡面發生什麼事 p. 16
如果你是擘劃公司技術願景的人,這可能意味著,你必須花更多時間與組織 (或企業) 的非技術部門相處,弄清楚驅動企業的願景為何?以及它如何改變? p. 18
理想上不超過 10 個原則 — — 好讓人們能夠記住,或者放到小海報上。原則越多,相互重疊或矛盾的機會就越大。 p. 18
實務做法 (practices) 是確保原則被貫徹的手段,是一組關於執行任務的詳實指導方針,通常是技術特定的,並且夠低階,足以讓任何開發人員理解。 p. 18
當你在處理實務做法並且思考必須採取的權衡取捨時,要尋求的核心平衡之一,就是你的系統允許多少可變性 (variability)。 p. 20
知道個別服務是否健康確實有用,但往往只有在你試圖診斷更廣泛的問題,或瞭解更大趨勢的情況下才是如此。為了讓這件事情儘可能容易,我會建議讓所有服務以相同的方式留下與健康/一般監控有關的統計數據。 p. 20
務必與機器內部的技術相區隔,不要為了支援它而改變你的監控系統。在此,日誌機制也是一樣:必須將它集中一處,統一管理。 p. 20
即使不使用 HTTP,類似的顧慮也存在,我們必須能夠分辨下列幾種狀況:請求沒問題並且正確被處理、請求有問題並且因此防止服務用它來做任何事、以及請求可能沒問題但因服務器關閉而無法分辨,這是快速追蹤失敗原因與相關問題的關鍵,假如我們的服務把這些規則當作兒戲,最終只會得到更脆弱的系統。 p. 21
治理確保企業目標被達成,透過評估利害關係人之需求、條件與選項;透過排序與決策制定方向;針對約定的方向與目標監控效能、合規與進展。 — — COBIT 5 p. 24
我們想要聚焦於技術治理 (technical governacne) 的面向,我覺得這是架構師的職責。如果架構師的職責之一是確保技術願景存在,那麼,治理便涉及確保我們所建造的東西符合這個遠景.並且在必要時演進這個願景。 p. 24
架構師負責很多事情,必須確保有一組原則可以指導整個開發工作,而且這些原則需要符合組織策略,還得確認這些原則要求的實務做法不至於讓開發人員苦不堪言,他們必須跟上最新的技術,必且知道何時該做出是當的取捨。這是很大的責任。除此之外,他們還必須帶得動人 — — 亦即,確保一起合作的同事能夠理解他們的決策,必且投入心血,實現他們 p. 25
我非常贊同的模型係由架構師領導該小組 [註:治理小組],但主要的小組為每個交付團隊的技術專家 — — 至少是每個團隊的領導人。架構師負責確保小組順利運作,但由小組共同負責治理工作。 p. 25
我希望你聚焦在兩個關鍵概念上:鬆散耦合 (loose coupling) 與高度內聚 (high cohesion),貫穿全書,我們還會討論其他的觀念與實務,但假如把這兩個重點搞錯,其他努力皆屬枉然。 p.30
邊界上下文 (Bounded Context),想法是,任何特定領域 (domain) 皆由多個邊界上下文構成,每一個邊界上下文內涵無須與外界構通的東西,以及被其他邊界上下文從外部共用的東西。每一個邊界上下文都具有顯式介面 (explicit interface),他決定要跟其他上下文共享那些模型。 p. 31
老實說我不太喜歡將 context 翻成上下文
無論如何,一開始,先讓新系統保持在較為單體式的那邊;將服務邊界搞錯的代價可能很昂貴。 p. 33
過早將系統分解成微服務的代價可能很昂貴,尤其是在你對新領域不慎熟悉的情況下。基本上,將既有的程式碼基礎分解成微服務遠比從頭開始發展微服務來得簡單。 p. 34
這些功能可能需要資訊交換 — — 共用模型 — — 但我見過太多以資料的角度思考而導致貧血的 CRUD 服務,因此,請先詢問『這個上下文是做什麼的?』然後才問,『他需要什麼資料?』 p. 34
根據相同的業務概念來思考這些微服務之間的溝通也是很重要的,你的軟體塑模遵循你的業務領域,不應該侷限在邊界上下文的概念,在組織的各個單位之間共用的相同術語與想法應該被反映在你的介面上。 p. 36
把整合 (integration) 弄對視微服務最重要的技術面向。將這項工作做好,讓你的微服務保有自主性 (autonomy),你就能夠以獨立於整體的方式變更及釋出它們。 p. 39
尋找理想的整合技術
資料庫整合讓服務很容易共用資料,但完全無法共用行為。我們的內部表示 (internal representation) 被暴露給服務消費者,很難避免破壞性變更,這必然會造成我們害怕進行任何變更。避開它,不惜 (幾乎) 任何代價。 p. 42
這跟我後來不喜歡將 JPA/Hibernate 或 Jackson 的 annotation 放在 domain model 中的主要原因,這些內部呈現與外部呈現都與 domain model 的行為無關。
需要考慮的重要因素之一是,這些風格 [註:同步或非同步]有多適合用來解決通常很複雜的問題:我們如何處理跨服務邊界並且可能長期執行的流程? p. 43
orchestration 方法的缺點是,用戶服務會變得太過中央集權,成為系統的中央樞紐,以及邏輯蔓延叢生的中心點,我看過這種作法導致少數的『天神』服務指揮著一群基於 CRUD 的貧血服務。 p. 45
你必須思考網路本身。很多人都知道,分散式系統的第一個謬誤就是『網路是可靠的』,事實上,網路並不可靠,它們可能並且會失敗。 p. 47
有許多不同的 REST 風格存在,… (中略) … 強烈建議你看看 Richardson Maturity Model,在當中,不同風格的 REST 被比較。 p. 49
傳統上,RabbitMQ 之類的訊息仲介者 (message broker) 試著處理這兩種問題,生產者 (producer) 使用 API 將事件發佈到仲介者 [非同步溝通的第一個問題:讓微服務發出事件的機制],仲介者處理訂閱,讓消費者 (consumer) 在事件到達時接獲通知 [第二個問題:讓服務消費者發現這些事件已經發生的機制]。 p. 55
事件驅動架構似乎導致更為解耦合、更具擴展性的系統,然而,這些編程風格確實增加複雜度,不只是管理發布及訂閱訊息所關聯的複雜度,還有我們可能面對的其他問題,例如,在考慮長期運作的異步請求/回應時,我們必須考量到回應返回時要怎麼做,是否回到發起請求的同一個節點?如果是這樣,萬一該節點停止運作呢?如果不是,我需要將資訊儲存在某處,而能夠據以做出式的回應嗎? p. 57
我們希望避免近似 CRUD 包裹器的愚蠢貧血服務,如果關於允許那些用戶變更的決策不是出自用戶服務本身,那就表示,我們正失去內聚性。 p. 58
DRY 是開發人員經常聽到縮略詞:don’t repeat yourself。雖然其定義有時候被簡化為盡量避免程式碼重複,DRY 更準確的意思是,我們想要避免重複系統的行為與知識。… (中略) … DRY 促使我們建立可重利用的程式碼,將重複的程式碼抽取出 來 … (中略) … 然而,在微服務架構中,這種做法看似危險。… (中略) … 如果共用程式碼的使用洩漏到服務邊界之外,你已經引進某種潛在的耦合 … (中略) … 我的原則是:在微服務中,不要違反 DRY,但跨所有服務時,別太在意 DRY 是否被遵循。 p. 59
如果同一批人建立伺服器 API 與客戶端 API,那會有危險,應該存在於伺服器端的邏輯開始滲漏到客戶端 p. 60
最後,請確認確實由客戶端主導何時升級自己的客戶端程式庫:務必確保我們的服務能夠獨立於其他服務被釋出! p. 60
無論你是否決定傳遞實體曾是什麼模樣的記憶,請確認你還包含指向原始資料的參考 (reference),好讓新的狀態能夠被擷取。 p. 61
降低進行破壞性變更之衝擊的最佳方法就是從一開始就避免 p. 62
實作能夠忽略我們不在意之變更的讀取器 — — 就是 Martin Fowler 所謂的 Tolerant Reader。
客戶端盡可能有彈性地使用服務的例子說明了 Postel 法則,也稱作強健性原則 (robustness principle):『發送時要保守,接收時要開放』 (Be conservative in what you do, be liberal in what you accept from others) p. 63
語意化版本管理 (semantic versioning)是讓你達成這個目的的一種規格,使用語意化管理時,版本編號的格式為 MARJOR.MINOR.PATH
,當 MAJOR
編號遞增時,就表示非向後兼容的變更已經被產生;當 MINOR
編號遞增時,較表示向後兼容的新功能已經被增加;最後,當 PATCH
編號改變時,就表既有的功能已經完成臭蟲修復。 p. 64
我們總是想要保有獨立釋出微服務的能力,一種我已經成功運用來處理這項工作的方法是,讓新舊介面並存於同一個運行中的服務,因此,如果我們喜要發布破壞性變更,就不屬新版的服務,同時開放該端點的新舊版本。 p. 64
這事實上是擴展及收縮 (expand and contract) 模式的範例,允許我們逐步採納破壞性變更。我們擴展提供的功能,同時支援做某件事的新方法與舊方法,一旦舊的服務消費者以新的方法做事情,我們就收縮 API,移除舊功能。 p. 65
隨著時間推移,JavaScript 成為將動態行為增加到基於瀏覽器之 UI 的熱門選項,現在,有些甚至跟就是桌面客戶端一樣臃腫肥胖。p. 67
如果有用 webpack 之類的工具打包 JavaScript 就知道,打包時間越來越長…
解決方法之一是讓服務消費者在提出請求時指名要擷取那些欄位,然而,這個解法假設每個服務都支援這種互動模式 p. 68
絞殺者模式 Strangler Application Pattern,透過這模式,你捕捉及攔截指向舊系統的呼叫,決定是否將這些呼叫繞送給既有的遺舊程式碼,或是導道你可能已經撰寫的新程式碼,讓你隨著時間推移逐漸替換功能性,而不需要突然重寫一大堆程式碼。
這算是進入職場後蠻常使用的 pattern 之一了
大部分編程語言支援名稱空間的概念,讓我們將類似的程式碼組織在一起。Java 的 package 概念是相當薄弱的例子,但提供很多我們需要的東西,所有其他的主流編程語言皆內建類似的概念,但 JavaScript 基本上是一個例外。 p. 80
新標準一直在演進中,這個例外可能不會持續太久,但我個人確實沒有很喜歡 JavaScript
強烈建議你一點一滴慢慢處理它們,漸進式的做法會在過程中幫助你聊解微服務,同時也會限縮弄錯某是 (事情總會出錯!) 所造成的影響。 p. 81
這將我們帶往常常造成依賴纏繞的根源:資料庫。 p. 82
有時候,為了其他利益犧牲一點速度確實是正確的選擇,尤其是在速度綽綽有餘的情況下。 p. 85
從許多方面來看,這是所謂最終一致性 (eventual consistency) 的另一種形式,代替使用交易邊界確保系統在交易完成後楚瑜一致的狀態,我們改為接受系統會在未來某個時點讓自己變成一致的狀態,這種作法尤其適合於可能長時間執行的操作。 p. 91
分散式交易 (distributed transaction) 嘗試跨多筆交易進行確認,使用某種稱做交易管理器 (transaction manager) 的控制行程來協調由各個底層系統所進行的不同交易。 … (中略) … 分散式交易已經針對一些特定技術堆疊被實作,像是 Java 的 Transaction API,允許資料庫與訊息佇列之類的一直系統餐與同一個總體交易 (overarching transaction) p. 93
即使採用短期分支 (short-lived branch) 來管理變更,也要盡量頻繁地整合到單一主線。 p. 104
沒有驗證程式碼行為是否一如預期的 CI 算不上真正的 CI。 p. 104
我們關心的是它的運作,那麼,我們可以將心力聚集在建立及部署這些映像的自動化,這也變成實現另一個部署理念的巧妙方法 — — 不可變伺服器 (immutable server)。 p. 113
基礎設施團隊的工作負擔往往與他必須管理的主機數量有關,如果更多服務被打包到一個主機,主機管理的數量不會隨著服務數量增加而增加。… (中略) … 這模型也有一些挑戰,首先,它讓(精準)監控更困難 … (中略) … 另一個問題是,這個選項會限制我們的部署產出物,基於映像的部署被排除,不可變的伺服器也一樣,除非你將多個不同服務一起綁在單一產出物,但這是我們極力想要避免的事情。 p. 116
許多關於部署與主機管理的工作實務皆企圖以最佳化的方式處理資源稀有性。 p. 117
主機數量增加也具有潛在的不利因素,更多伺服器需要管理,而且,執行更多主機也隱含著一些成本。 p. 120
我們最不樂見的事情就是:因部署流程完全不同而造成我們在上線環境中遇到一些問題!在這個領域工作多年之後,我相信,觸發任何部署的最明智方法就是透過單一、可參數化的命令列呼叫,這可以透過指令搞觸發,由 CI 工具啟動或手動輸入。 p. 127
建立這樣的系統可謂工程浩大,需付出的心力往往集中在前期,但是對部署複雜性的管理可能是必要的。 p. 129
近來的趨勢是避免大規模的手動測試,盡可能採用自動化測試,我當然同意這種做法,如果你目前正在進行大量的手動測試,建議你,在深入微服務之前,先解決這個問題再繼續往前走,因為,假如你無法快速且有效地驗證你的軟體,就無法從微服務獲得諸多利益。 p. 132
單元測試通常測試一個函示或方法呼叫,… (中略) … 一般而言,你需要進行大量這類測試,如果處理得當,他們是非常快速的,而且,在現代化硬體上,你可以預期在不到一分鐘的時間內執行數以千計的這類測試。 p. 134
服務測試繞過 UI,直接測試服務本身,在單體式應用程式中,我們可能只測試一群提供服務給 UI 的類別,然而,對包含多個服務的系統來說,服務測試會測試個別服務的能力。我們想要測試單一服務本身的原因是,提高測試的隔絕性,以便更迅速地發係及解決問題。為了達成這種隔絕性,我們必須 stub 所有外布鞋作者,因此,僅僅服務本身落在測試範圍內。 p. 135
當較大範圍的測試 (如服務社或端到端測試) 發生失敗時,我們會試著撰寫快速的單元測試,捕捉相關的問題,依此方式,我們不斷地試著改善我們的回饋循環。 p. 136
基本原則是,每往金字塔下方移動一層,需要的測試數量可能多出一個層級,但重點是,知道你確實擁有不同類型的自動化測試,並且瞭解當前的平衡是否為你帶來問題! p. 136
變動元件越多,我們的測試可能越脆弱 (brittle),不確定性也越高。如果有一些測試偶爾失敗,而大家只是重新執行它們,因為稍後可能又會通過,這些就叫作詭異測試 (flaky test) … (中略) … 詭異測試是恐怖的敵人,在發生失敗時,它們並未提供我們很多資訊,我們重新執行 CI 建置,希望它們稍後不再出現,只是為了看到我們順利且持續地簽入程式碼,突然間,我們發現自己已經累積一大堆有問題的功能性。 p. 140
包含詭異測試的測試組會變成 Diane Vaughan 所謂的異常正常化 (normalization of deviance) 的受害者 — — 隨著時間推移,我們會對錯誤的事情習以為常,以致於開始接受它,視之如無物。 p. 141
Martin Fowler 主張下列作法。如果你有一些詭異測試,你應該記錄並追蹤它們,如果無法立刻解決,就設法將它們從測試組移除。看看你是否能夠改寫它們,避免以多執行緒的方式執行測試程式碼。看看你是否能夠讓底層環境更趨穩定。更好的是,看看你是否可以使用比較不會產生問題的較小範圍測試替換詭異測試。在某些情況下,改變受測軟體,讓它更容易測試,也是繼續向前行的好辦法。 p. 141
我很少看到團隊確確實實地妥善規劃端到端測試,以便減少測試涵蓋度的重疊,或是花費足夠的時間想辦法讓它們變快。 p. 142
這樣的緩慢,加上詭異測試可能經常存在的事實,會形成重大的問題。耗費一整天執行,並且經常包含與毀損功能無關之問題的測試組,絕對是一場災難,當功能真的毀損時,你可能得花好幾個小時才能找到問題。 p. 142
使用藍 / 綠部署 (blue/green deployment) 時,我們同時部署兩份軟體,但只有一個版本接受真實的請求。 p. 143
使用金絲雀釋出時,我們透過導入一部分的上限交通流量,看看新部署的軟體是否如預期般執行,『如預期般執行』可以涵蓋很多事情,包含功能性及非功能性的事項。 p. 150
考慮金絲雀釋出時,你必須決定是否將一部分上線請求直接導到金絲雀,或者只是複製上線負載。有些團隊能夠複製 (shadow) 上線流量,並且將它導到金絲雀,透過這種方式,既有的上線版本與金絲雀版本可以看到完全相同的請求,但是,只有上線版本處理的請求會被外部看到,這樣可以讓你對召集比較,同時去除被客戶看到金絲雀版本發生失敗的可能性,然而,複製上線流量的工作可能很複雜,尤其是在被重播的事件 / 請求並非等冪 (idempotent) 的情況下。 p. 150
有時候,投入相同的心力,建立更好的釋出補救機制,可能遠比增加更多自動化功能測試更有助益 [註:end-to-end 的自動化測試],在 Web 世界哩,這通常被稱作在最佳化平均故障間隔 (mean time between failures, MTBF) 與平均修復時間 (mean time to repair, MTTR) 之間的權衡取捨。 p. 151
當系統被分解成較小的微服務時,我們增加跨網路邊界的呼叫數量,原本可能涉及一個資料庫呼叫的操作,現在能需要對其他服務發出三、四個跨網路邊界的呼叫,以及相當數量的資料庫呼叫,這一切都可能降低系統的運作速度。因此,追蹤延遲來源尤其重要。 p. 152
執行的主機數量成為一項挑戰,以 SSH 多工 (SSH-multiplexing) 的方式擷取日誌可能行不同,而且沒有任何螢幕大得足以容納所有主機的終端視窗 — — 相反地,我們指望使用專門的子系統來捕捉日誌,並且將它們集中起來。 p. 158
首先,有一句古老的格言,80% 的軟體功能從未被使用。
話雖如此,但更常聽到 PM / PO 一邊想像一邊說:這功能超棒超有用,一定會大受歡迎… 有人說,透過數據統計可以知道哪些功能沒人用,但已經投入的成本 (人力成本、時間成本和錯失市場的機會成本) 都已經無法挽回了。
一種有用的做法是運用相關性 ID (correlation ID),當最初的呼叫被產生時,你為該呼叫生成 GUID,接著,他被往下傳遞給所有的後續呼叫。並且能夠以結構化的方式被放進你的日誌哩,就像日誌級別或日期那樣。藉由合適的日誌工具,你可以一路穿過你的系統,追蹤這個事件。 p. 163
相關性 ID 的真正問題之一是,你經常不知道你需要它,直到你碰到問題之後, … (中略) … 雖然這可能看起來像是額外的工作,但我強烈建議你盡早將它們納入考慮,尤其是當你的系統即將運用事件驅動的架構模式時,那可能導致某種奇怪的緊急行為。 p. 164
目前想到可能較簡單的做法是將 task ID 當作 correlation ID,並將 task 當成參數傳遞,不管是否跨節點,每個處理過這個 task 的程式都可以輸出 task ID 到日誌中。
監控不同系統之間的整合點是關鍵,每個服務實例都應該追蹤並且揭露其下游依賴物的健康狀況,從資料庫到其他協作服務都是。 p. 164
就標準化而言,監控是一個至關重要的區域,透過多個介面,使用以各種方式提供功能給使用者的協作服務時,你必須以整體觀點看待系統。 p. 165
如果你要建立服務帳號 (service account),請盡量保持用途單純,因此,考慮讓每個微服務擁有自己的一組憑證,如果憑證被盜用,這樣會讓撤銷 / 改變存取變得比較容易,因為你只需要撤銷受影響的那組憑證。 p. 175
(客戶端) 憑證管理的操作挑戰甚至比使用伺服器憑證更艱鉅,不只是因為建立及管理較大量憑證的一些基本議題;更且,因為憑證本身的複雜性,你預計會花很多時間試圖診斷出服務為什麼不接受你認為完全有效的客戶端憑證,然後,我們還得考慮撤銷即在核發憑證的困難性。使用萬用憑證 (wildcard certificate) 會有幫助,但無法解決所有的問題。 p. 175
Twitter、Google、Flicker 與 AWS 等服務的所有公開 API 都利用 API 金鑰,API 金鑰讓服務能夠識別誰產生呼叫,並且限制他們可以做什麼事情,通常,這些限制不僅止於資源的存取,還能夠擴展到特定呼叫者的速率限制 (rate-limiting),以保證其他人的服務品質。 p. 177
有一種安全漏洞被稱作混淆代理人問題 (confused deputy problem),在此情況下,惡意的第三方欺騙代理人服務 (deputy service),讓它對下游服務發出呼叫。 p. 179
加密 (encruption) 依賴用來編密資料的演算法與金鑰,然後產生編密過的資料,那麼,你的金鑰要存放在哪裡? … (中略) … 一個解法是使用獨立的資安設備 (security appliance) 來加解密資料,另一個解法是使用你的服務在需要金鑰時能夠存取的獨立金鑰保存庫 (key vault),金鑰的生命週期管理是很重要的操作,這些系統能夠為你處理這項工作。 p. 181
我們擔心且想要加密的資料通常也需要備份!因此看起來可能很明顯,但我們必須確認我們的備份也被加密過,這也表示,我們必須知道處理那些版本的資料需要用到那些金鑰,尤其是,如果金鑰需要改變,擁有明確的金鑰管理機制變得相當重要。 p. 182
入侵偵測系統 (IDS, intrusion detection systems) 可以監控網路或主機上的可疑行為,在看到它們時報告問題。入侵預防系統 (IPS, intrusion prevention systems),除了監控可疑活動,還可進一步介入,阻止它們發生。 p. 183
以權限盡量低的 OS 使用者執行服務,確保萬一帳號被破解,造成的損傷會最小。 p. 183
如果我們讓生活過得更簡單些呢?何不盡量『淨化』可用來識別個資的資料,並盡早做到呢? … (中略) … 這裡的優點是多重的。首先,如果你不儲存的話,就沒有人能夠竊取它。其次,如果你不儲存,就沒有人 (例如,政府機構) 可以請求它!德語 Datensparsamkeit 代表這個概念,源自德國隱私法,基本概念是只能儲存對商業營運絕對必要,或者滿足本地法律的資訊。 p. 187
負責設計系統 (這裡採取更廣泛的定義,不限於 IT 系統) 之任何組織所產生的『設計結構』將無可避免地複製該組織的『溝通結構』。 p. 191
當協調變更的成本增加時,有兩種可能會發生,不是找到辦法減少協調 / 溝通成本,就是停止變更協調,後者最終導致難以維護的龐大程式碼基礎。 p. 194
如果建立系統的組織比較鬆散耦合,這個組織所建構的系統傾向於更為模組化,因此,可能比較不耦合。擁有多個服務的單一團隊請向隅發展出比較緊密耦合的整合,這種趨勢在較分散的組織中很難維持的。 p. 194
服務所有權 (service ownership)是什麼意思?一般情況下,這表示,擁有服務的團隊負責變更該服務,該團隊應該可以在必要時重新組織程式碼,只要該變更不破壞消費端服務。對許多團隊來說,所有權擴及服務的各個面向,從收集需求,到建置、部署和維護應用程式。 p. 194
在不能把燙手山竽交接給別人的情況下,自然就不會弄出什麼燙手山竽啦! p. 194
賦予團隊更多權力與自主性,同時讓它為其工作負起全責。我看過太多開發人員再把他們的系統送進測試或部署階段,就認為他們的任務已告結束。 p. 195
讓我們再次思考一下微服務是什麼:根據業務領域塑模的服務,而非技術領域,如果擁有特定服務的團隊大致與業務領域一致,很可能,團隊就可以維持用戶焦點,並且更能夠看清楚功能開發,因為它對於服務相關的所有技術都掌握著全面性的理解與所有權。 p. 196
萬一我們已經盡力,但就是無法找到辦法避開幾個共用服務呢?在此情況下,適度擁抱內部開源碼 (internal open source) 模型可能很合理。 p. 197
大多數開源碼專案傾向於不接受較大群不授信認知提交者的變更提交,直到第一個版本的核心功能完成。 p. 198
不管乍看如何,問題總是在人 — — Gerry Weinberg, The Second Law of Consulting
切記,若未考慮相關人員的感受,或者他們所具備的能力,逕自擘劃事情應該如何完成的願景,很可能將大火帶到錯誤的境地。 p. 202
你必須明確指出你的人員在微服務世界裡的責任,並且說清楚那些責任微和對你很重要,這樣可以幫助你看清楚可能存在那些技能缺口 (skill gap),必且思考如何消弭這些間隙。對很多人來說,這將是相當駭人的旅程,但只要記住,倘若無人承擔大任,任何你想要進行的變更可能從一開始就注定失敗。 p. 202
在一定規模下,失敗變成統計上的必然。 p. 205
即使對我們當中不考慮極端規模的人來說,如果能夠接受失敗的可能性,事情會變得更好,例如,如果我們能夠優雅地處理服務的失敗,那麼,接下來,我們還可以就地升級服務,因為計畫下的運作中斷遠比意外的失敗更容易處理。 p. 205
我們也可以少花一點時間阻止無可避免的事情,多花一點時間設法以優雅的方式處理失敗,我很驚訝,許多組織設置大量流程與控制機制,試圖阻止失敗發生,卻很少或幾乎沒有付出心力,設法讓系統更容易從失敗中回復 (recover)。 p. 205
建構具回復力之彈性系統的重要關鍵是安全降級功能性的能力,尤其是在你的功能性分散於諸多或開啟或關閉的服務時。 p. 207
使用一個單體式應用程式時,我們不需要做很多決策,系統健康非黑即白,但使用微服務架構時,我們需要考慮更微妙的情況,在任何情況下都要做的往往不是技術決策,我們可能知道當購物車失效時技術上該如何處理,但除非了解業務背景,否則無法明白應該採取什麼行動。 p. 208
回應非常緩慢是你會碰到的最差勁失敗模式之一,如果系統壓根不存在,你很快就能夠找出答案,然而,當它只是緩慢時,在放棄之前,你最後會等待一段時間,但無論失敗原因為何,我們建立的系統確實脆弱,很容易發生連鎖失敗,下游服務 (我們對它幾乎沒什麼控制力) 會破壞我們的整個系統。 p. 209
說到底,我們發現,這些系統與其歹戲拖棚,倒不如快速失敗,那可能會比較容易處理一些。在分散式系統中,緩慢延遲實在是一個很棘手的問題。 p. 210
我們十座三種修補,避免這種情況 (緩慢服務造成的影響) 再發生:把逾期時間設對;實作隔艙 (bulkhead),劃分出不同的連接池;並且實作斷路器 (circuit breaker),從第一時間就避免傳送呼叫給不健康的系統。 p. 210
使用斷路器時,在一定數量的下游資源請求失敗之後,斷路器被熔斷,在此狀態下,所有後續請求快速失敗,特定時間之後,客戶端發送幾個請求,看看下游服務是否已經恢復,並且,如果得到足夠的健康回應,就重設斷路器。 p. 212
把設定弄至卻可能有點棘手,你不想要斷路器太容易熔斷,也不想要花太長時間才能熔斷它。 p. 212
如果有這樣的機制 (就像家裡的斷路器),我們可以手動操作它們,讓相關作業更安全。例如,如果我們想要在例行維護工作中卸除某個微服務,我們可以手動熔斷各個依賴系統的斷路器,讓它們在微服務離線時快速失敗,一旦該微服務重新上線,我們就可以重設這些斷路器,一切就會回歸正常。 p. 212
關注點分離 (separatin of concerns) 也是一種時做隔艙機制的方法,透過將功能拆解成多個獨立的微服務,減少一個區域的中斷影響另一個區域的可能性。 p. 214
從許多方面來看,隔艙是這三種模式中最重要的,逾期與斷路器幫助你在資源負荷超載或失效時釋放它們,但隔艙能夠在一開始就確保它們不變成負荷太重或失效。 p. 215
在等冪操作中,執行結果在連續呼叫多次之後並不會改變,如果操作式等冪的,我們可以重複呼叫多次,而不會產生不良的影響。 p. 215
一些 HTTP 動詞,如 GET
與 PUT
,在 HTTP 規格中被定義為等冪的,然而,這樣的話,它們依賴你的服務乙等密的方式來處理這些呼叫,如果你開始讓這些動詞變成非等冪的,但呼叫者以為他們可以安全地重複執行這些操作,你可能會讓自己陷入一團混亂。 p. 216
務必小心,別在太多地方快取! p. 230
有一個快取是你幾乎無法控制的:使用者瀏覽器裡頭的快取。 p. 230
特別是目前主流的瀏覽器像是 Chrome,之前開發時就因為快取,測試人員的瀏覽器總是用舊的 JavaScript 造成許多奇怪現象…
我們甚至有數學證明可以靠訴我們確實不行。你可能聽說過 CAP 定理,特別是在討論各種資料儲存機制的優缺點時,基本上,這個定理告訴我們,在分散式系統中,有三件事相互抗衡:一致性 (consistency)、可用性 (availability)、與分區容錯性 (partition tolerance)。具體來說,這個定理告訴我們,我們只能夠滿足三項當豬的二項,而無法滿足全部三項。 p. 232
跨多個節點維持一致性真的很難 p. 234
我們的系統不需要整體都是 AP (犧牲一致性) 或 CP (犧牲可用性) … (中略) … 然而,個別的服務甚至不必為 CP 或 AP。 p. 235
微服務的原則
這本書大概花了一個禮拜的零碎時間看完,其實收穫很多,很多原則不僅僅適用於微服務,也適用在單體式應用被部署在很多節點上,加上跟過去的經驗比較,更能體會到書中的觀點,接下來,要開始 stop starting, start finishing,將書架上很多看到一半的書,集中火力把它們看完吧!
Jul 19, 2020