天天看點

老司機避坑指南:如何快速搞定微服務架構?

如今,微服務架構已經成為了現代應用開發的首選。雖然它能夠解決大部分的程式問題,但是它并非一顆百試不爽的“銀彈”。

老司機避坑指南:如何快速搞定微服務架構?

在采用這種架構之前,我們應當事先了解可能出現的各種問題及其共性,預先為這些問題準備好可重用的解決方案。

那麼,在開始深入讨論微服務的不同設計模式之前,讓我們先了解一下微服務架構的一些建構原則:

 ●  可擴充性

 ●  可用性

 ●  彈性

 ●  獨立、自主性

 ●  去中心化治理

 ●  故障隔離

 ●  自動調配

 ●  通過 DevOps 實作持續傳遞

在遵循上述各條原則的同時,我們難免會碰到一些挑戰。下面我們來具體讨論可能出現的各種問題、及其解決方案。

分解模式

按照業務功能分解

問題:微服務是有關松散耦合的服務,它采用的是單一職責原則。雖然我們在邏輯原理上都知道要将單個應用分成多個小塊,但是在實際操作中,我們又該如何将某個應用程式成功分解成若幹個小的服務呢?

解決方案:有一種政策是按照業務功能進行分解。此處的業務功能是指能夠産生價值的某種業務的最小機關。那麼一組給定業務的功能劃分則取決于企業本身的類型。

例如,一家保險公司的功能通常會包括:銷售、營銷、承保、理賠處理、結算、合規等方面。每一個業務功能都可以被看作是一種面向業務、而非技術的服務。

按照子域分解

問題:按照業務功能對應用程式進行分解隻是一個良好的開端,之後您可能會碰那些不易分解的所謂“神類”(God Classes)。這些類往往會涉及到多種服務。

例如,訂單類就會被訂單管理、訂單接受、訂單傳遞等服務所使用到,那麼我們又該如何分解呢?

解決方案:對于“神類”的問題,DDD(Domain Driven Design,領域驅動設計)能夠派上用場。

它使用子域(Subdomain)和邊界上下文(Bounded Context)的概念來着手解決。

DDD 會将企業的整個域模型進行分解,并建立出多個子域。每個子域将擁有一個模型,而該模型的範圍則被稱為邊界上下文。那麼每個微服務就會圍繞着邊界上下文被開發出來。

注意:識别子域并不是一件容易的事,我們需要通過分析業務與組織架構,識别不同的專業領域,來對企業加強了解。

刀砍模式(Strangler Pattern)

問題:前面我們讨論的設計模式一般适用于針對那些“白手起家”的 Greenfield 應用進行分解。

但是我們真實接觸到的、約占 80% 的是 Brownfield 應用,即:一些大型的、單體應用(Monolithic Application)。

由于它們已經被投入使用、且正在運作,如果我們簡單按照上述方式,同時對它們進行小塊服務的分解,将會是一項艱巨的任務。

解決方案:此時,刀砍模式(Strangler Pattern)就能派上用場了。我們可以把扼殺模式想象為用刀砍去纏在樹上的藤蔓。

該方案适用于那些反複進行調用的 Web 應用程式。對于每一個 URI(統一資源辨別符)的調用來說,單個服務可以被分解為不同的域和單獨的子服務。其設計思想是一次僅處理一個域。

這樣,我們就可以在同一個 URI 空間内并行地建立兩套獨立的應用程式。最終,在新的應用重構完成後,我們就能“刀砍”或替換掉原來的應用程式,直到最後我們可以完全關閉掉原來的單體應用。

老司機避坑指南:如何快速搞定微服務架構?

內建模式

API 網關模式

問題:當一個應用程式被分解成多個小的微服務時,我們需要關注如下方面。

具體如下:

 ●  如何通過調用多個微服務,來抽象出 Producer(生産者)的資訊。

 ●  在不同的管道上(如電腦桌面、移動裝置和平闆電腦),應用程式需要不同的資料來響應相同的後端服務,比如:UI(使用者界面)就可能會有所不同。

 ●  不同的 Consumer(消費者)可能需要來自可重用式微服務的不同響應格式。誰将去做資料轉換或現場操作?

 ●  如何處理不同類型的協定?特别是一些可能不被 Producer 微服務所支援的協定。

解決方案:API 網關将有助于解決在微服務實施過程中所涉及到的上述關注點。

具體如下:

 ●  API 網關是任何微服務調用的統一入口。

 ●  它像代理服務一樣,能夠将一個微服務請求路由到其相關的微服務處,并抽象出 Producer 的細節。

 ●  它既能将一個請求扇出(fan out,輸出)到多個服務上,也能彙總多個結果,并發回給 Consumer。

 ●  鑒于通用 API 無法解決 Consumer 的所有請求,該方案能夠為每一種特定類型的用戶端建立細粒度的 API。

 ●  它也可以将某種協定請求(如:AMQP)轉換為另一種協定(如:HTTP),反之亦然,進而友善了 Producer 和 Consumer 的處理。

 ●  它也可以将認證與授權存儲庫從微服務中解除安裝出去。

聚合器模式

問題:雖然我們已經在 API 網關模式中讨論了如何解決聚合資料的問題,不過我們仍将做進一步的讨論。

當我們将業務功能分解成多個較小的邏輯代碼塊時,有必要思考每個服務的傳回資料是如何進行協作的。

顯然,該責任不會留給 Consumer,那麼我們就需要了解 Producer 應用的内部實作。

解決方案:聚合器模式将有助于解決該問題。它涉及到如何聚合來自不同服務的資料,然後向 Consumer 發送最終響應。

具體說來,我們有如下兩種實作方法:

 ●  複合微服務(Composite Microservice) 将會去調用全部所需的微服務,整合各種資料,并在回傳之前轉換資料。

 ●  API 網關(API Gateway) 也能對多個微服務的請求進行 Partition(分區),并在發送給 Consumer 之前聚合資料。

我們建議:如果您用到了任何業務邏輯的話,請選用複合微服務;否則請采用 API 網關方案。

用戶端 UI 合成模式

問題:當各種服務按照業務功能和子域被分解開發時,它們需要根據使用者體驗的預期效果,從一些不同的微服務中提取資料。

在過去的單體應用中,我們隻要從 UI 到後端服務的唯一調用中擷取所有的資料,并重新整理和送出到 UI 頁面上便可。如今,情況則不同了。

解決方案:對于微服務來說,UI 必須被設計成單屏、單頁面的多段、多區域的結構。

每一段都會去調用單獨的後端微服務,以提取資料。像 Angular JS 和 React JS 之類的架構都能夠實作為特定的服務合成 UI 元件。

通過被稱為單頁應用(Single Page Applications,SPA)的方式,它們能夠使得應用程式僅重新整理螢幕的特定區域,而不是整個頁面。

資料庫模式

按服務配置設定資料庫

問題:您可能會碰到如何定義資料庫架構的微服務問題。

下面是具體的關注點:

 ●  服務必須是松散耦合的,以便能夠被二次開發、部署和獨立擴容。

 ●  各個業務交易需要在橫跨多個服務時,仍保持不變。

 ●  某些業務交易需要從多個服務中查詢到資料。

 ●  資料庫有時需要根據規模需求被複制與分片。

 ●  不同的服務具有不同的資料存儲需求。

解決方案:為了解決上述需求,我們需要通過設計為每個微服務配備一個獨享的資料庫模式。

即:該資料庫僅能被其對應微服務的 API 單獨通路,而不能被其他服務直接通路到。

例如,對于關系型資料庫,我們可以使用:按服務配置設定私有表集(private-tables-per-service)、按服務配置設定表結構(schema-per-service)、或按服務配置設定資料庫伺服器(database-server-per-service)。

每個微服務應該擁有一個單獨的資料庫 ID,以便它們在獨享通路的同時,禁止再通路其他的服務表集。

按服務共享資料庫

問題:上面讨論的按服務配置設定資料庫是一種理想的微服務模式,它一般被前面提到的 Greenfield 應用和 DDD 式的開發。但是,如果我們面對的是需要采用微服務的單體應用就沒那麼容易了。

解決方案:按服務共享資料庫的模式雖然有些違背微服務的理念,但是它對于将前面提到的 Brownfield 應用(非建立應用)分解成較小的邏輯塊是比較适用的。

在該模式下,一個資料庫可以比對不止一個的微服務,當然也至多 2~3 個,否則會影響到擴容、自治性和獨立性。

指令查詢職責隔離(CQRS)

問題:對于按服務配置設定資料庫的模式而言,我們如何在微服務的架構中,實作對多個服務進行聯合查詢資料的需求呢?

解決方案:CQRS 建議将應用程式拆分成兩個部分:指令和查詢。指令部分主要處理建立、更新和删除之類的請求;查詢部分則利用物化視圖(Materialized Views)來處理各種查詢。

它通常配合事件溯源模式(Event Sourcing Pattern)一起建立針對任何資料的變更事件。而物化視圖則通過訂閱事件流,來保持更新。

Saga 模式

問題:當每個服務都有自己的資料庫,而且業務交易橫跨多個服務時,我們該如何確定整體業務資料的一緻性呢?

例如:對于某個帶有客戶信用額度辨別的電商應用而言,它需要確定新的訂單不會超出客戶的信用額度。

但是,由于訂單和客戶分屬不同的資料庫,應用程式無法簡單地實作本地交易的 ACID(原子性、一緻性、隔離性、持久性)特性。

解決方案:Saga 代表了一個高層次的業務流程,它是由一個服務中的多個子請求,并伴随着逐個更新的資料所組成。在某個請求失敗時,它的補償請求會被執行。

實作方式有如下兩種:

 ●  編排(Choreography): 沒有中央協調器,每個服務都會産生并偵聽其他服務的事件,以決定是否應采取行動。

 ●  協調(Orchestrator): 由一個中央協調器(對象)負責集中處理某個事件(Saga)的決策,和業務邏輯的排序。

老司機避坑指南:如何快速搞定微服務架構?

觀測模式

日志聚合

問題:我們來考慮這樣一個用例:某個應用程式包括了那些在多台機器上運作的多個服務執行個體,各種請求橫跨在這些多個服務執行個體之中。同時,每個服務執行個體都會生成一種标準格式的日志檔案。

那麼我們如何針對某個特定的請求,通過各種日志來了解該應用程式的行為呢?

解決方案:顯然,我們需要一個集中化的日志服務,将各個服務執行個體的日志予以聚合,以便使用者對日志進行搜尋和分析。他們可以針對日志中可能出現的某些消息,配置相應的警告。

例如:PCF(Pivotal Cloud Foundry)平台擁有一個日志聚合器,它從每種元素(如:路由器、控制器等)中收集與應用相關的日志。而 AWS Cloud Watch 也具有相似的功能。

性能名額

問題:當各種服務組合随着微服務架構變得越來越複雜時,監控交易的完整性,并能夠在出現問題時及時發出警告,就顯得尤為重要了。那麼我們該如何收集與應用相關的性能名額呢?

解決方案:為了收集不同操作的統計資訊,并提供相應的報告和警告。

我們一般會用兩種模式來聚集各項名額:

 ●  推式: 将各項名額推給專門的名額服務,如:NewRelic 和 AppDynamics。

 ●  拉式: 從名額服務處拉取各項名額,如:Prometheus。

分布式跟蹤

問題:在微服務架構中,橫跨多個服務的請求是比較常見的。某個服務需要通過橫跨多個服務去執行一到多項操作,才能處理一些特定的請求。

那麼,我們該如何通過跟蹤某個端到端的請求,以獲知出現的問題呢?

解決方案:我們需要一種具有特性的服務。

具體特性服務如下:

 ●  為每個外部請求配置設定一個唯一的 ID。

 ●  将該外部請求 ID 傳給所有的服務。

 ●  在所有的日志消息中都包含該外部請求 ID。

 ●  在集中式服務中,記錄處理外部請求的相關資訊,包括:開始時間、結束時間、和執行時間。

Spring Cloud Slueth + Zipkin Server,是一種常見的實作方式。

健康檢查

問題:我們在實施微服務架構的過程中,可能會碰到某個服務雖已啟動,但是無法處理交易的情況。

那麼,我們該如何通過負載均衡的模式,來確定請求不會“落入”失敗的執行個體中呢?

解決方案:每個服務都需要有一個端點,通過諸如 /health 的參數,對應用進行健康檢查。

該 API 需要能夠檢查主機的狀态,其他服務與基礎設施的連接配接性,以及任何特定的邏輯關系。

Spring Boot Actuator 不但能夠實作端點的健康檢查,還能夠被定制實施。

橫切關注點模式(Cross-Cutting Concern Patterns)

外部配置

問題:通常情況下,一個服務需要去調用其他的服務和資料庫。在諸如開發、QA(Quality Assurance,品質保證)、UAT(User Acceptance Test,使用者驗收測試)、和生産環境中,端點的 URL、或某些配置的屬性會有所不同。

是以,有時候我們需要對這些服務的各種屬性進行重構、和重新部署。那麼我們如何避免在配置變更中修改代碼呢?

解決方案:外部化(externalize)所有的配置,包括各個端點的 URL 和信任憑據,以保證應用程式在啟動時、或運作中能夠加載它們。

Spring Cloud 配置伺服器提供了向 GitHub 進行屬性外部化的選項,并将其作為環境屬性予以加載。

此法保證了應用程式能夠在啟動時就被通路到,或是在不重新開機伺服器的情況下實作重新整理。

服務發現模式

問題:當微服務初具規模時,我們需要考慮如下兩個關于調用服務方面的問題。

具體問題如下:

 ●  由于采用了容器技術,IP 位址往往被動态地配置設定給不同的服務執行個體。是以,每次當 IP 位址發生變化時,Consumer 服務可能會受到影響,需要我們手動更改。

   ●   Consumer 需要記住每個服務的 URL,這就倒退成了緊耦合的狀态。

那麼,Consumer 或路由器該如何獲知所有可用的服務執行個體與位置呢?

解決方案:我們需要建立一個服務系統資料庫,來儲存每個 Producer 服務的中繼資料(Meta Data)。

一個服務執行個體在啟動時,應當被注冊到表中;而在關閉時,需從表中被登出。

Consumer 或路由器通過查詢該系統資料庫,就能夠找到服務的位置。Producer 服務也需要對該系統資料庫進行健康檢查,以確定能夠消費到那些可用的、且正在運作的服務執行個體。

我們一般有兩種服務發現的類型:用戶端和伺服器端。使用用戶端發現的例子是 Netflix Eureka;而使用伺服器端發現的例子是 AWS ALB。

斷路器模式

問題:有時候,某個服務在調用其他服務,以擷取資料的時候,會出現下遊服務(Downstream Service)“掉線”的情況。

它一般會帶來兩種結果:

 ●  該請求持續發往該掉線服務,直至網絡資源耗盡和性能降低。

 ●  使用者産生不可預料的、較差的使用體驗。

那麼我們該如何避免服務的連鎖故障,并妥善處置呢?

解決方案:Consumer 應該通過一個代理來調用某項遠端服務,就像電路中的斷路器一樣。

當出現持續失敗的數量超過設定門檻值時,斷路器就會“跳閘”一段時間,進而導緻所有調用遠端服務的嘗試被立即切斷。

在超過設定時間之後,斷路器隻允許有限數量的測試請求通過。而如果這些請求成功了,那麼斷路器将恢複正常運作;否則判定為故障依舊,并重新開始新的定時周期。

Netflix Hystrix 就很好地使用了該斷路器模式。它可以在斷路器“跳閘”的時候,幫助您定義一種回退機制,以提供更好的使用者體驗。

藍綠部署模式

問題:在微服務架構中,一個應用程式可以有多個微服務。如果我們為了部署一個增強版,而停止所有的服務,那麼停機時間一旦過長,就會對業務造成影響。

況且,這對于回退來說也将會是一場噩夢。那麼我們該如何避免、或減少部署過程中服務的停機時間呢?

解決方案:我們可以采用藍綠部署的政策,以減少或消除停機時間。在藍、綠兩個相同的生産環境中,我們假設綠色環境有着目前真實的執行個體,而藍色環境具有應用程式的最新版本。

在任何時候,隻有一個環境能夠處理所有真實的流量,并對外提供服務。如今,所有的雲服務平台都能提供基于藍綠部署的選項。

當然,我們還可以采用許多其他的微服務架構模式,如:Sidecar 模式、鍊式微服務(Chained Microservice)、分支微服務(Branch Microservice)、事件溯源模式(Event Sourcing Pattern)、和持續傳遞方式等。

原文釋出時間為:2018-11-14

本文作者:陳峻編譯

本文來自雲栖社群合作夥伴“技術瑣話”,了解相關資訊可以關注“技術瑣話”。