當前位置:網站首頁>微服務最强理論基礎,堪稱絕妙心法

微服務最强理論基礎,堪稱絕妙心法

2022-05-14 22:04:12Java愛好狂

前言

Building Microservices: Designing Fine Grained Systems 讀書筆記。

本書偏理論而非實現,可作為內功心法,適合架構師或有經驗的系統工程師。

常讀常新。


前言

微服務是分布式系統提高細粒度服務(use of finely grained services)使用的一種 方式,在這種模式中,每個服務都有自己獨立的生命周期,所有服務共同合作完成整體 的功能。

微服務主要是針對業務領域建模的(modeled around business domains),因此可以 避免傳統分層架構(tiered architecture)的一些缺點。

微服務價格提供了越來越多的自治性(increased autonomy)。

1 微服務

Domain Driven Design:如何對系統建模。

領域驅動設計(DDD)、持續交付(CD)、按需虛擬化(On-demand virtualization)、基 礎設施自動化(Infrastructure automation)、小自治團隊(Small autonomous teams) 、大規模系統(Systems at scale):這些都是微服務產生的前提。

微服務並不是憑空設計的,而是真實需求催生的。

什麼是微服務?

微服務:小的、自治的、一起工作的服務。

  • 小:專注、只做好一件事情很難確定多小才算小,但是比較容易確定多大就算大:如果你覺得一個系統該拆分了, 那它就是太大了
  • 自治判斷標准:對一個服務進行改動昇級,不影響其他服務

主要好處

  • 技術异質性(Technology Heterogeneity)
    • 不同組件可以采用不同語言、框架、數據庫類型等等。綜合考慮功能、性能、成本等 ,選擇最優的方案
    • 新技術落地更方便
  • 容錯性(Resilience)
    • 容錯工程的一個核心概念:bulkhead(防水壁)。一個組件出差,錯誤不應該瀑 布式傳遞給其他系統(cascading),做到錯誤隔離
    • 但也應該認識到,微服務(分布式系統)跟單體應用相比,會引入新的故障源( sources of failure),例如網絡故障、機器故障
  • 擴展性(Scaling)
  • 易於部署(Ease of Deployment)
  • 架構和組織對齊(Organizational Alignment)
    • 多個小團隊維護獨立的較小的代碼庫,而不是大家一起維護一個很大的代碼庫
  • 可組合性(Composability)
    • 單個服務可以同時被不同平臺使用,例如一個後端同時服務 PC、Mobile、Tablet 的訪 問
  • 易於替換組件(Optimizing for Replaceability)

SOA 與微服務

面向服務的架構(Service-oriented architecture,SOA)是一種多個服務協同工作來提供 最終功能集合(end set of capabilities)的設計方式。這裏的服務通常是操作系統中 完全獨立的進程。服務間的調用是跨網絡的,而不是進程內的函數調用。

SOA 的出現是為了應對龐大的單體應用(large monolithic applications)帶來的挑戰。它的目的是提高軟件的重用性(reusability of software),例如多個終端用戶應用 使用同一個後端服務。SOA 致力於使軟件更易維護和開發,只要保持服務的語義不變, 理論上換掉一個服務其他服務都感知不到。

SOA 的思想是好的,但是,關於如何做好 SOA,業界並沒有達成共識。在我看來,很 多廠商鼓吹 SOA 只是為了兜售他們的產品,而對業界大部分人對 SOA 本身還缺少全面和 深入的思考。

SOA 門前的問題包括:通信協議(e.g. SOAP)、廠商中間件、對服務粒度缺乏指導、對在 哪切分單體應用的錯誤指導等等。憤青(cynic)可能會覺得,廠商參與 SOA 只是為他們賣 自家產品鋪路,而這些大同小异的(selfsame)產品反而會削弱(undermine) SOA 的目標。

SOA 的常規實踐經驗(conventional wisdom)並不能幫助你確定如何對一個大應用進行拆分。例如,它不會討論多大算大,不會討論實際項目中如何避免服務間的過耦合。而這些沒有討論的東西都是 SOA 真正潜在的坑。

微服務源自真實世界的使用(real-world use),因此它對系統和架構的考慮比 SOA 要更 多。可以做如下類比:微服務之與 SOA 就像極限編程(XP)之與敏捷開發(Agile )

其他拆分方式

  • 共享庫(Shared Libraries):語言、操作系統、編譯器等綁定
  • 模塊(Modules):模塊/代碼動態更新,服務不停。例如 Erlang

沒有銀彈

微服務並不是銀彈,錯誤的選擇會導致微服務變成閃著金光的錘子(a golden hammer)。微服務帶來的挑戰主要源自分布式系統自有的特質。需要在部署、測試、監控等方面下功夫 ,才能解鎖服務器帶來的好處。

總結

2 演進式架構師(The Evolutionary Architect)

軟件工程和建築工程的角色對比

計算機和軟件行業很年輕,才六七十年。“工程師”、“架構師”(architect,在英文裏和建 築師是同一個單詞)等頭銜都是從其他行業借鑒過來的。但是,同樣的頭銜在不同行業所 需承擔的職責是有很大差別的,簡單來說就是:軟件行業中的頭銜普遍虛高,且對自己工 作成果所需承擔的責任都很小。例如,建築師設計的房子倒塌的概率,要比架構師設計的 軟件崩潰的概率小得多。

另一方面,軟件工程設計和建築工程設計也確實有不同。例如橋梁,設計建好之後基本橋就 不動了,而軟件面向的是一直在變化的用戶需求,架構要有比較好的可演進性。

建築設計師更多的會考慮物理定律和建材特性,而軟件架構師容易飄飄然,與實現脫節,最 後變成紙上談兵,設計出灾難性的架構。

架構師應具備的演進式願景

客戶的需求變化總是比架構師想象中來的更快,軟件行業的技術和工具迭代速度也比傳統行 業快得多。架構師不應該執著於設計出完美的終極架構,而更應該著眼於可演進的架構。

軟件架構師的角色與遊戲《模擬城市》(SimCity)裏鎮長(town planner)的角色 非常相似,做出每個决策時都需要考慮到未來。

人們經常忽視的一個事實是:軟件系統並不僅僅是給用戶使用的,開發和運維工程師也 要圍繞它工作,因此系統設計的也要對開發和運維友好

Architects have a duty to ensure that a system is habitable for developers too.

總結起來一句話:設計一個讓用戶開發者都喜歡的系統。

那麼,如何才能設計出這樣的系統呢?

Zoning(服務或服務組邊界)

架構師應該更多地關心服務間發生的事情,而不是服務內發生的事情。

Be worried about what happens between boxs, and be liberal in what happens inside.

每個服務可以靈活選擇自己的技術棧,但如果綜合起來技術棧太過分散龐雜,那成本也會非 常高,並且規模很難做大。需要在技術棧選擇的靈活性和整體開發運維成本之間取得一個 平衡。舉例,Netflix 大部分服務都是使用 Cassandra。

參與寫代碼的架構師(The coding architect)

架構師花一部分時間參與到寫代碼,對項目的推進會比只是畫圖、開會、code review 要 有效得多。

A Principled Approach

架構設計就是一個不斷做出選擇(折中)的過程(all about trade-offs),微服務架構給 我們的選擇尤其多。

原則化(Framing):Strategic Goals -> Principles -> Practices.

A great way to help frame our decision making is to define a set of principles and practices that guide it, based on goals that we are trying to achieve.

圖 2-1 一個真實世界的原則和實踐(principles and practices)的例子

最好提供文檔示例代碼,甚至是額外的工具,來解釋這些原則和實踐標准。

The Required Standard

一個好的微服務應該長什麼樣:

It needs to be a cohesive system made of many small parts with autonomous life cycles but all coming together.

監控

建議所有的服務都對外暴露健康和監控信息。

  • Push 模型:主動向外發送信息,例如 telegraf
  • Pull 模型:暴露端口,被動地被其他組件收集,例如 prometheus

接口(API)

API 有多種可選的實現方式。從粗的維度包括 HTTP/REST、RPC 等等,細的維度還包括它們各自內部的各自標准。例如, HTTP/REST API 裏用動詞還是名詞、如何處理分頁、如何處理不同 API 版本等等。

盡量保持在兩種以內。

架構安全性(Architectural Safety)

不能因為一個服務掛掉,導致整個系統崩潰。每個服務都應該在設計時就考慮到依 賴的組件崩潰的情况。這包括:

  1. 線程池的連接數量
  2. 熔斷(circut breaker)
  3. 快速失敗(fast fail)
  4. 統一的錯誤碼(例如 HTTP Code)

確保代碼符合規範(Governance Through Code)

兩項有效的方式:

  • exemplars
  • service templates

Exemplars

Tailored Service Templates

將同樣的東西統一化,可以庫、代碼模板或其他形式:

  • 健康檢查
  • HTTP Handler
  • 輸出監控信息的方式
  • 熔斷器/方式
  • 容錯處理

但注意不要喧賓奪主,過於龐大的模板和庫也是一種灾難。

技術債(Technical Debt)

技術債不一定都是由拙劣的設計導致的。例如,如果後期的發展方向偏離了最初的設計 目標,那部分(甚至大部分)系統也會成為技術債。

技術債也並不是一經發現就要投入精力消除。架構師應該從更高的層面審視這個問題,在”立即還債”和”繼續忍耐”之間取得一個平衡。

維護一個技術債列錶,定期 review。

例外處理

如果你所在的公司對開發者的限制非常多,那微服務並不是適合你們。

總結

演進式架構師的核心職責:

  • 技術願景(Vision):對系統有清晰的技術願景,並且與團隊充分溝通,系統滿足客戶和 公司的需求
  • 深入實際(Empathy,理解):理解你的决定對客戶和同事產生的影響
  • 團結合作(Collaboration):與同事緊密合作來定義、優化和執行技術願景
  • 擁抱變化(Adaptability,自適應性):技術願景能隨著客戶需求的變化而變化
  • 服務自治(Autonomy,自治性):在標准化和允許團隊自治之間取得平衡
  • 落地把控(Governance):確保系統是按照技術願景實現的

這是一個長期的尋求平衡的過程,需要經驗的不斷積累。

3 如何對服務建模

劃分微服務的邊界。

良好的微服務的標准

標准:

  • 低耦合(loose coupling)
  • 高內聚(high cohesive)

這兩個術語在很多場合,尤其是面向對象系統(object oriented systems)中已經被用爛 了,但我們還是要解釋它們在微服務領域裏錶示什麼。

低耦合

哪些事情會導致高耦合?一種典型的場景是錯誤的系統對接(integration)方式,導致依賴其他服務。

低耦合的系統對其他系統知道的越少越好,這意味著,我們也許應該减少服務間通信的種類 。囉嗦(chatty)的通信除了性能問題之外,還會導致高耦合。

高內聚

相關的邏輯集中到一起,改動昇級時便只涉及一個組件。因此,核心問題轉變成:確 定問題域的邊界

有界上下文(The Bounded Context)

《領域驅動設計》:對真實世界域(real-world domains)進行建模來設計系統。其中一個重要概念:bounded context

Bounded Context: Any given domain consists of multiple bounded contexts, and residing within each are things that do not need to communicate outside as well as things that are shared externally with other bounded contexts. Each bounded context has an explicit interface, where it decides what models to share with other contexts.

Bounded context 的另一種定義:由顯式的邊界定義的具體的責任(a specific responsibility enforced by explicit boundarries)。類比:細胞膜(membrance),細 胞之間的邊界,决定了哪些可以通過,哪些需要保持在細胞內部。

Shared and Hidden Model

一般來說,如果一個 model 需要對外暴露,那對外的和內部使用的 model 也應該是不同的 ,因為很多細節是只有內部才需要的,沒有必要暴露給外部,因此會分為:

  • shared models:bounded context 對外暴露的 models
  • hidden models:bounded context 內部使用的 models

舉例:訂單的模型,

  • Hidden model:在數據庫中的錶示
  • Shared model:在 REST API 中的錶示

Modules and Services

Shared model 和 hidden model 使得服務間不依賴內部細節,實現了解耦。

確保 bounded context 實現成一個代碼模塊(module),以實現高內聚。這些模塊化的邊 界,就是微服務的理想分割點。

如果服務的邊界和問題域的 bounded context 邊界是對齊的,而且我們的微服務能够 錶示(represent)這些 bounded context,那麼我們就走在了低耦合和高內聚的正確道路 上。

過早拆分(Premature Decomposition)

項目早期,邊界一直在變化,不適合拆分成微服務。應該等邊界比較穩定之後再開始。

業務功能(Business Capabilities)

設計一個 bounded context 首先應該考慮的不是共享什麼數據,而是這個 bounded context 能為域內的其他服務提供什麼功能(capability)。如果一上來就考慮數據模 型,很容易設計出缺乏活力的(anemic)、基於 CRUD (增删查改)的服務。

Turtles All the Way Down

圖 3-2 Microservices representing nested bounded contexts

圖 3-3 The bounded contexts being popped up into their own top-level contexts

選擇哪種需要視組織結構:如果上面三個服務是同一個團隊負責的,那 3-2 比較合適;如 果是三個不同團隊負責的,那 3-3 比較合適。康威定律。

另外,測試的難易程度也會有差异。

Communication in Terms of Business Concepts

The Technical Boundary

避免:洋葱架構(onion architecture)。軟件層級非常多,從上往下切的時候,會讓 人忍不住掉眼淚。

Summary

本章學習了什麼是一個好的服務,如何找出問題域的邊界,以及由此帶來的兩個好處:低耦 合和高內聚。Bounded context 是幫助我們完成這一目的的利器。

《領域驅動設計》描述了如何找出恰當的邊界,這本書非常經典,本章只是涉及了它的一點 皮毛(scratched the surface)。另外,推薦《領域驅動設計實現》(Implementing Domain-Driven Design),以幫助更好的理解 DDD 的實踐。

4 集成

我個人認為,集成(integration)是微服務中最重要的一方面。集成方案設計的好, 萬事 OK;設計的不好,坑(pitfall)會一個接著一個。

確定最佳的集成技術

SOAP、XML-RPC、REST、ProtoBuf 等等。

避免不兼容改動(breaking changes)

盡最大努力。

保持 API 技術無感知(technology-agnostic)

如果你在 IT 行業已經混了 15 分鐘以上,那就不用我提醒你這個行業變化有多快了。

If you have been in the IT industry for more than 15 minutes, you don’t need me to tell you that we work in a space that is changing rapidly.

新的技術、平臺、工具不斷湧現,其中一些用好了可以極大提高效率,因此 API 不應該綁 死到一種技術棧。

使服務對客戶盡量簡單

從選擇的角度講,應該允許客戶使用任何技術來訪問服務。

從方便的角度講,給客戶提供一個客戶端庫會大大方便他們的使用。但也也會造成和服務端 的耦合,需要權衡。

避免暴露內部實現

暴露內部實現會增加耦合。任何會導致暴露內部實現的技術,都應該避免使用。

共享數據庫

這也是最簡單、最常用的集成方式是:數據庫集成(DB integration,使用同一個數據庫)。

img

圖 4-1 數據庫集成

缺點:

  1. 允許外部組件直接查看和綁定內部實現細節
    1. 修改數據庫的字段會影響所有相關服務
    2. 回歸測試麻煩
  2. 外部組件被迫綁定到特定技術(數據庫實現)
    1. 如果要從關系數據庫切換到非關系數據庫,外部組件也得跟著改
    2. 高耦合
  3. 外部組件包含相同邏輯,例如查詢,修改數據庫
    1. 要修一個 bug 或加一個 feature,得改每一個組件
    2. 低內聚

微服務的兩個標准:低耦合和高內聚,被破壞殆盡。

Database integration makes it easy for services to share data, but does nothing about sharing behavior.

异步還同步

同步和异步會導致不同的協助模式:

  • 同步:請求/響應式(request/response)
    • 客戶端主動發起請求,然後等待結果
    • 同步請求 + 回調函數的方式也屬於請求/響應模式
  • 异步:事件驅動式(event-based)
    • 服務端主動通知客戶端發生了某事件
    • 從本質上(by nature)就是异步的
    • 處理邏輯更分散,而不是集中到一個系統
    • 低耦合,服務只負責發事件通知,誰會對此事件作出反應,它並不知道,也不關心
    • 添加新的訂閱者時,客戶端無感知

選擇哪種模式?重要標准:哪個更適合解决常見的複雜場景問題,例如跨多個服務的請 求調用。

Orchestration Versus Choreography(管弦樂編排 vs 舞蹈編排)

  • 管弦樂編排:有一個中心的指揮家(conductor),指示每個樂隊成員該做什麼
  • 舞蹈編排:沒有指揮家,每個舞蹈演員各司其職

以創建一個新用戶的流程為例:

圖 4-2 The process for creating a new customer

管弦樂編排(同步)模式

圖 4-3 Handling customer creation via orchestration

優點:

  1. 很容易將流程圖轉變成代碼實現,甚至有工具做這種事情,例如合適的規則引擎(rule engine)。另外還有很多商業軟件專門做這種事情(business process modeling software)
  2. 如果使用同步方式,編排器(大腦)還能知道每個階段的調用是否成功

缺點:

  1. 編排器成為核心,很大一部分邏輯都實現在這裏
  2. 單點及性能問題

舞蹈編排(异步)模式

圖 4-4 Handling customer creation via choreography

優點:

  1. 耦合更低、更靈活、更易擴展
  2. 添加新訂閱者方便,不需要改編排器代碼

缺點:

  1. 架構更松散,只能隱式地反映流程圖
  2. 需要更好的監控和跟踪系統,才能高效排障

總體來說優先推薦舞蹈編排模式。也可以兩者結合使用。

請求/響應式設計時兩者常見的通信方式:RPC 和 REST。

RPC

存在的幾個問題:

  1. 遠程過程調用和本地(函數)調用看起來一模一樣,但實際上不一樣,性能差很多
  2. 契約字段基本上只增不减(expand only),否則會破壞老版本兼容性,最後導致大量不 用的字段留在協議裏

Compared to database integration, RPC is certainly an improvement when we think about options for request/response collaboration.

RPC 的性能一般更好,因為它們可以采用二進制格式:

  1. 消息體更小
  2. 延遲更低

REST

資源對外的錶現形式(JSON、XML 等)和它們在服務內的存儲形式是完全分開的。

REST 本身並沒有限定底層協議,但事實上用的最多的還是 HTTP 一種。

雖然性能沒有 RPC 好,但很多情况下, REST/HTTP 仍然是服務間通信的首選。

基於事件的异步協作實現

技術選擇(Technology Choices)

需要消息隊列這樣的中間件。

中間件應該聚焦其功能本身,其他邏輯都實現在服務中:

Make sure you know what you’re getting: keep your middleware dumb, and keep the smarts in the endpoints.

异步架構的複雜之處

建議在上异步架構之前,做好監控和追踪方案(例如生成關聯 ID,在不同服務 間跟踪請求)。

强烈建議 Enterprise Integration Pattern 一書。

服務即狀態機(Services as State Machines)

每個服務都限定在一個 bounded context 內,所有與此 context 相關的邏輯都封裝在其內 部。服務控制著 context 內對象的整個生命周期。

DRY 和微服務裏的代碼重用

DRY:Don’t Repeat Yourself.

DRY 一般已經簡化為避免代碼重複,但實際上更嚴格地說,它指的是避免系統行為和 知識的重複。

DRY 落實到實現層面就是將公用的部分抽象成庫,但注意,這在微服務裏可能會導致問題。例如,如果所有服務都依賴一個公用庫,那這些服務也就形成了耦合。當其中一個服務想( 不兼容)更新這個庫的時候,其他服務都得跟著昇級,導致服務間獨立昇級的假設被打破。

Rule of thumb: don’t violate DRY within a microservice, but be relaxed about violating DRY across all services.

耦合的代價比代碼重複的代價高的多。

典型的例子:客戶端程序。

寫服務端的團隊最好不要同時提供標配客戶端,否則服務端實現細節會不知不覺地泄漏 到客戶端程序,導致耦合。

if the same people create both the server API and the client API, there is the danger that logic that should exist on the server starts leaking into the client.

這方面做的比較好的:AWS。AWS API 通過 SDK 的形式訪問,而這些 SDK 要麼是有社區自 發開發的,要麼是 AWS API 以外的團隊開發的。

make sure that the clients are in charge of when to upgrade their client libraries: we need to ensure we maintain the ability to release our services independently of each other!

Access by Reference

當一個訂單確定之後,需要以事件的方式通知郵件系統給用戶發一封郵件。兩種選擇:

  1. 將訂單信息放到消息體裏,郵件系統收到消息後就發送郵件
  2. 將訂單索引放到消息體裏,郵件系統收到消息後先去另外一個系統去獲取訂單詳情,再 發送郵件

在基於事件的方式中,我們經常說這個事件發生了this happened),但我們需要 知道的是:發生了什麼what happened)。

第三種選擇:同時帶上訂單信息和索引,這樣事件發生時,郵件系統既能及時得到最新通知 ,在未來一段時間又能主動根據索引去查詢當前詳情。

Versioning

Defer It as Long as Possible

客戶端對消息的解析要有足够的兼容性,老版本客戶端收到新字段時不做處理,稱為 Tolerant Reader 模式。

健壯性定理(the robustness principle):

Be conservative in what you do, be liberal in what you accept from others.

Catch Breaking Changes Early

消費者驅動型合約(consumer-driven contracts)。

Use Semantic Versioning

版本號格式:<major>.<minor>.<patch>,含義:

  1. <major>:大版本號,遞增時錶示有不兼容式(incompatible)更新
  2. <minor>:小版本號,遞增時錶示有新的特性,兼容以前的版本
  3. <patch>:補丁號,遞增時錶示修複 bug

Coexist Different Endpoints(同時支持不同版本的 API)

引入不兼容 API 時,同時支持新老版本:

圖 4-5

這是擴展與合約模式(expand and contract pattern)的一個例子,用於分階段引入 不兼容更新(phase breaking changes in)。首先擴展(expand)我們提供的功能,同時 提供新老方式;當老用戶遷移到新方式後,按照 API 合約(contract),删除老功能。

Integration with Third-Party Software

The Strangler Pattern(阻氣門模式)

在老系統前面加一層服務專門做代理,屏蔽背後的系統。這樣後面的系統不論是昇級、改造 甚至完全換掉,對其他系統都是無感知的。

with a strangler you capture and intercept calls to the old system. This allows you to decide if you route these calls to existing, legacy code, or direct them to new code you may have written. This allows you to replace functionality over time without requiring a big bang rewrite.

總結

保持系統解耦的建議:

  1. 不要通過數據庫集成
  2. 理解 REST 和 RPC 的區別,推薦先從基於 REST 的 request/response 模式開始做起
  3. 優先考慮舞蹈編排模式(Prefer choreography over orchestration)
  4. 避免不兼容更新,理解版本化的必要,理解健壯性定理、tolerant reader 模式

5 拆分單體應用

It’s All About Seams

Working Effictively with Legacy Code(Prentice Hall)一書中定義了 Seam 的 概念:隔離的、獨立的代碼塊。

Bounded contexts make excellent seams.

一些語言提供了 namespace,可以隔離代碼。

將不同部分實現為不同模塊(module 或 package)。

應該采用漸進式拆分。

拆分單體應用的原因

1. 局部頻繁變化(Pace of Change)

預見到某一部分接下來會頻繁變化,將其單獨抽離出來。更新部署會更快,單元測試也更 方便。

2. 團隊結構變化(Team Structure)

團隊拆分、合並等變化,軟件系統能跟隨組織結構變化,會提高開發效率。康威定律。

3. 新技術應用方便(Technology)

便於某一部分功能采用新技術,如新語言、新框架等等。

錯綜複雜的依賴(Tangled Dependencies)

有向無環圖(DAG)可以幫助分析依賴。

The mother of all tangled dependencies: the database.

使用一些可視化工具查看數據庫錶之間的依賴,例如 SchemaSpy 等。

例子:外鍵關聯

數據庫依賴解耦:去除不同 bounded context 之間的外鍵關聯。

例子:共享的靜態數據

例如,國家代碼,原來可能存在數據庫,所有組件都訪問。

解决方式:

  1. 每個服務都複制一份:以代碼或配置文件的方式存儲;如果數據會被修改,需要解决數據一致性問題
  2. 單獨抽象一個服務,提供靜態數據服務:適用於靜態數據很複雜的場景

例子:共享的可變數據(mutable data)

兩個服務都需要更新同一張錶。

解决方式:需要抽象出兩個服務間的公共部分,單獨一個組件,完成對錶的更新。

圖 5-5 Accessing customer data: are we missing something?

圖 5-6 Recognizing the bounded context of the customer

例子:共享錶

拆錶。

圖 5-7 Tables being shared between different contexts

圖 5-8 Pulling apart the shared table

數據庫重構

Book: Refactoring Database, Addison-Wesley

Staging the Break

先拆分庫,再拆分服務,步子不要邁得太大:

圖 5-9 Staging a service separation

事務邊界(Transactional Boundaries)

拆分成獨立服務後,原來單體應用中的事務邊界就丟失了,拆分後需要解决原子性的問題。

圖 5-10 Updating two tables in a single transaction

圖 5-11 Spanning transactional boundaries for a single operation

重試(Try Again Later)

將失敗的操作放到一個 queue 或 logfile 裏,稍後重試。對於一部分類型的應用來說這樣 是可行的。

屬於最終一致性(eventual consistency)。第 11 章會詳細討論。

全部回退(Abort The Entire Operation)

需要一個補償事務(compensating transaction)執行回退操作。

如果補償事務又失敗了怎麼辦?

  1. 重試
  2. 直接報錯,人工介入清理髒數據
  3. 有相應的後臺進程或服務,定期清理髒數據

當只有兩個步驟時,保持兩個事務的原子性還算簡單。但假如有三個、四個、五個步驟時呢 ?補償事務方式顯然將極其複雜,這時候就要用到分布式事務。

分布式事務

有一個中心的 transaction manager。跨服務編排事務。

最常見的 short-lived transaction 算法:兩階段提交(two-phase commit)。

  1. 投票(voting)階段:每個參與方分別向事務管理員匯報它是否可以執行事務
  2. 提交(commit)階段:如果所有參與方投票都是 yes,事務管理員就下達提交命令,參 與方開始執行事務

缺點:

  1. 依賴一個中心的 transaction manager 發號施令,transaction manager 出問題時整個 系統將無法執行事務操作
  2. 任何一個參與方無法應答 transaction manager 時,事務都會無法進行
  3. 參與方提交階段失敗:兩階段提交算法假設每個參與方的提交階段只會成功不會失敗, 但這個假設並不成立。這意味著這個算法在理論上不是可靠的(foolproof), 只能解决大部分場景(參與方提交成功的場景)
  4. 提交失敗的情况:參與方會鎖住資源無法釋放,極大限制了系統的可擴展性

兩階段提交算法原理簡單,實現複雜,因為可能導致失敗的條件非常多,代碼都得做相應處理。

建議:

  1. 能不用就不用
  2. 必須得用時,優先考慮找一個已有的實現,而不是自己寫

到底怎麼辦呢?

分布式事務不可靠,補償事務太複雜,那到底該怎麼辦呢?

面對這種問題時,首先考慮,是否真的需要保持分布式事務屬性?能否用多個本地事務加最 終一致性代替?後者更容易構建和擴展。

如果真的是必須要保持事務屬性,那建議:盡最大努力保持為單個事務,不要做事務拆分。

If you do encounter state that really, really wants to be kept consistent, do everything you can to avoid splitting it up in the first place. Try really hard. If you really need to go ahead with the split, think about moving from a purely technical view of the process (e.g., a database transaction) and actually create a concrete concept to represent the transaction itself.

報錶(Reporting)

將比特於一個或多個地方的數據集中到一起,生成報錶。

模型 1:數據庫複制

圖 5-12 Standard read replication

典型的報錶數據庫是獨立的,定期從主數據庫同步。

優點:簡單直接。

缺點:

  1. 主數據庫的錶結構共享給力報錶系統,二者產生了耦合。主數據庫的修改可能會 break 報錶系統;而且,錶結構修改阻力更大,因為對接的團隊肯定不想總是跟著改
  2. 數據庫優化手段會更受限:到底是該為主業務進行優化,還是該對報錶系統進行優化, 二者可能是沖突的
  3. 報錶系統和主業務綁定到了一種數據庫,無法用到比較新的、可能更合適的數據庫,例 如非關系型數據庫

模型 2:Pull 模型

通過服務調用的方式主動去拉取所需的數據。

存在的問題:

  1. 服務方的 API 不是為報錶系統設計的,取一份想要的數據得調用多次 API
  2. 取回來的數據量可能很大,而且還不能做緩存,因為原始數據可能會被修改,導致緩存 失效
  3. 數據量太大,API 太慢,解决方式:提供批量 API

批量 API 參考流程:

  • 客戶端調用批量 API
  • 服務端返回 202:請求已接受,但還沒開始處理
  • 客戶端輪詢狀態
  • 服務端返回 201:已創建

Pull 模型的缺點:

  1. 請求量很大時,HTTP 頭開銷比較大
  2. 服務端可能還要專門為報錶系統提供 API

模型 3:Push 模型

主動向報錶系統推送數據。

一個單獨的程序從數據源拉取數據,存儲到報錶系統的數據庫。

圖 5-13 Using a data pump to periodically push data to a central reporting database

效果如下,每個模塊也可有自己獨立的報錶數據庫 schema:

圖 5-14 Utilizing materialized views to form a single monolithic reporting schema

模型 4:事件或消息隊列模型

圖 5-15 An event data pump using state change events to populate a reporting database

優點:

  1. 比定時同步的方式時效性更高
  2. 耦合更低

缺點:當數據量非常大時,效率沒有基於數據庫層的 push 模型高。

模型 5:備份數據模型

和數據庫複制類似,但複制的是文件或其他元數據,存儲在對象存儲系統中,再用 Hadoop 之類的平臺讀取文件進行分析。適用於超大規模系統的報錶。

實時性

不同類型的報錶對實時性的要求是不同的。

6 部署(Deployment)

持續集成簡史

With CI, the core goal is to keep everyone in sync with each other, which we achieve by making sure that newly checked-in code properly integrates with existing code. To do this, a CI server detects that the code has been committed, checks it out, and carries out some verification like making sure the code compiles and that tests pass.

持續發布

圖 6-2 A standard release process modeled as a build pipeline

7 測試

8 監控

9 安全

10 康威定律和系統設計

Melvin Conway, 1968:

Any organization that designs a system will inevitably produce a design whose structure is a copy of the organization’s communication structure.

Eric S. Raymond,The New Hacker’s Dictionary (MIT Press):

If you have four groups working on a compiler, you’ll get a 4-pass compiler.

諷刺的是,康威的論文提交給《哈佛商業評論》的時候被拒了,理由是這個定理未經證明。

反面教材:Windows Vista

證明教材:Amazon 和 Netflix

Amazon 很早就意識到了每個團隊負責自己的系統的整個生命周期的重要性。另外,它也意 識到小團隊比大團隊運轉起來更加高效。

這產生了著名的 two-pizza team:如果一個團隊兩個披薩還吃不飽,那這個團隊就該 拆分了。

Netflix 也是從一開始就規劃為小的、獨立的團隊。

11 大規模微服務

服務降級

架構安全

避免系統雪崩,級聯崩潰。

措施

  • 超時
  • 熔斷
  • bulkhead(防水倉),來自 Release It! 的概念,丟弃發生錯誤的部分,保持核 心功能的正常
  • 隔離(isolation):及時隔離發生故障的下遊應用,這樣上遊壓力就會减輕

回源的系統,需要考慮到極端情况下全部 miss 時,所有請求都將打到源節點,是否會發生 雪崩。其中一種解决方式是:隱藏源站,miss 時直接返回 404,源站异步地將內容同步到 緩存。

圖 11-7 Hiding the origin from the client and populating the cache asynchronously

這種方式只對一部分系統有參考意義,但它至少能在下遊發生故障的時候,保護自己不受影 響。

CAP 極簡筆記

三句話:

  1. 三者無法同時滿足
  2. 無 P 不成分布式系統
  3. 可選:CP 或 AP

12 總結

圖 12-1 Principles of microservices

版權聲明
本文為[Java愛好狂]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/134/202205141820529750.html

隨機推薦