天天看點

值得收藏的微服務實踐筆記

前言

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.
值得收藏的微服務實踐筆記

img

圖 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,使用同一個資料庫)。

值得收藏的微服務實踐筆記

圖 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

Change is inevitable. Embrace it.

作者:SpringForAll社群

來源:http://r6a.cn/gqSH