所在位置:首頁 -- 軟件設計 -- 正文

微服務實戰:從架構到部署


發布時間:2017-4-1  來源:admin

 在這篇文章里, 我計劃涵蓋微服務架構(MSA)的核心架構概念,以及你如何在實踐中使用這些架構理論。

如今,微服務“microservice”已經成為軟件架構領域最流行的熱詞之一。市面上也有很多與微服務的基礎知識以及優點相關的學習資料,但是關于如何在真實的企業場景中應用微服務的資料還是不多。

在這篇文章里, 我計劃涵蓋微服務架構(MSA)的核心架構概念,以及你如何在實踐中使用這些架構理論。

單體架構

企業軟件設計需要滿足多種多樣的業務需求。因此,一個特定的應用軟件會包括有幾百個功能項,而所有這些功能項都打包進了一個單體的應用中。典型的例子有,ERP, CRM等其他各種各樣的軟件。對于這種野獸級別的軟件應用,部署,排錯, 擴展和升級工作都是一個個噩夢。

面向服務架構(SOA) 設計是針對上述問題的一個解決方案, SOA引入了服務的概念,用來將軟件中相似的功能進行分組聚合在一起。因此,有了SOA,軟件就被設計為一組粗粒度服務的組合。 但是SOA并沒有解決所有的問題。在SOA里,一個服務的范圍是非常廣的。由此帶來的弊端是服務本身龐大而復雜,數十個功能點,以及復雜的消息格式和標準(例如所有的WS*規范)

圖1 單體架構

在大多數情況下,SOA里面的服是互相獨立的,而且是與其他所有的服務部署在同一個運行時上面。(可以想象一下多個web應用部署到同一個tomcat實例當中)。 而且與單體軟件類似,這些服務會隨時間越長越大,因為累加的功能越來越多。最后,這些應用本身已變成了單體軟件,與傳統的單體軟件(比如ERP)也沒啥兩樣。圖1描述了一個零售業的軟件,它包含有多個服務,所有這些服務都部署在同一個運行時上。 這是一個很好的單體架構的例子。這里我列出這種基于單體架構軟件的一些特點:

單獨應用是作為一個整體單元來設計,開發,部署的

單體應用非常復雜,導致的結果就是維護,升級和增加新功能都非常困難

在單體架構下,非常難實踐敏捷的開發和部署方法

如果要更新它的某個部署,則需要重新部署整個應用

擴展:必須作為單個軟件來擴展,當有資源需求沖突時擴展就變得非常困難(比如 一個服務需要更多的CPU但是其他的服務要更多內存)

可靠性:一個不穩定的服務可能會導致整個應用不可用

阻礙創新: 由于所有的功能都基于同一套技術框架來夠構建,想加入新的技術或者框架就非常困難

微服務架構

微服務架構的基礎是開發一個應用由一組小但是獨立的服務來組成,這些服務運行在自己的進程中,可以被獨立開發,獨立部署。

在大多數的微服務架構的定義里,這被解釋為將一個單體應用里面的服務拆分為一組獨立的服務。但是,我覺得,這不是微服務的全部。

核心的觀點是通過查看單體服務提供的功能項目,來識別出必須的業務能力。然后這些業務能力可以作為一個完全獨立的,細粒度的,自包含的服務來實現(微服務)。他們的實現可以是基于不同的技術棧,而且每個服務描述的是一個明確的特定的有限的業務范圍。

因此,我們上文中提到的在線零售系統可以用圖2里面的微服務架構來實現。 在微服務架構下,零售軟件應用通過一組微服務來實現。所以你看到圖2中,我們增加在原來單體應用里面的一組服務的基礎上新增加了一個服務。所以很明顯,使用微服務架構不是僅僅將單體應用里面的服務拆分那么簡單。

圖2 微服務架構

接下來,讓我們更加深入了解微服務的核心架構原則。更重要的,讓我們關注如何將他們應用到實踐中。

設計微服務: 大小,范圍和能力

你可能在使用微服務架構從頭構建一個軟件,也可能是要把已有的應用服務轉換為微服務。無論哪種,非常重要的一點都是你必須合理的決定微服務的大小,范圍和能力。這極有可能是在實踐微服務架構初期碰到的最難的事情。

接下來我們來討論與微服務的大小,范圍,能力相關的一些實際的考慮點和錯誤觀點。

代碼行數和團隊大小事很糟糕的度量指標:基于代碼行數或者團隊大小來決定微服務的大小已經有多個討論了。(比如 兩個pizza的團隊 )。 但是,這些都是非常不切實際而且非常糟糕的度量值,因為我們用更少的代碼或者兩個pizza的團隊開發出來的服務仍然可能完全違背微服務的架構原則。

微micro這個詞會導致誤解:大多數開發人員傾向于認為們應該將服務做的越小越好。但是這完全是錯誤的解釋。

在SOA的上下文里面,服務通常被實現為包括很多功能的和運營支持的單體結構。所以如果僅僅是將SOA那種服務重新打上微服務的標簽不會給你帶來微服務架構的如何好處。

那么,我們該如何合地設計微服務架構下的服務呢?

微服務設計原則

單一責任原則(Single Responsibility Principle, SRP): 對于一個微服務而言具有有限的和關注的業務范圍可以幫助我們滿足服務開發和交付的敏捷性。

在微服務的設計階段, 我們應該找到他們的邊界,并將它們與業務能力相關聯。(在 領域驅動設計 里這叫有邊界的上下文)

必須保證微服務設計能支持服務的敏捷/獨立地開發和部署

我們應該關注微服務的范圍,而不是一味的把服務做小。一個服務的(正確的)大小應該等于滿足某個特定業務能力所需要的大小。

與SOA里面的服務不同,一個給定的微服務應該有相當少的運營和功能點,以及簡單的消息格式

通常一個好的實踐是先從一個比較大的服務邊界開始,然后隨著時間推移基于業務需求來重構成更小的。

在我們的零售系統的案例中,你可以發現我們將原來單體應用的功能分割到了4個不同的微服務中, 'invenory", "accountng", "shipping", "store"。 它們描述的是一個有限但關注的業務范圍,而且服務之間互相完全解耦,保證了開發和部署的敏捷性。

微服務里的消息

在單體應用里面,不同組件的業務功能通過函數調用或者語言級別的方法調用來實現。在SOA中,這轉變為更加松耦合的web service級別的消息,主要是基于HTTP,JMS等不同協議的SOAP。Webservice 包含的幾十種操作以及復雜的消息機制是阻礙web services流行的一個重要因素。對于微服務架構而言,必須要有一個簡單且輕量級的消息機制。

同步消息- REST, Thrift

對于微服務領域的同步消息機制而言(可獲得需要服務給一個及時的響應否則一直等待), REST 是公認的選擇。它提供了一種簡單的消息風格,具體實現是HTTP的請求-響應,基于資源的API風格。因此大多數的微服務實現是使用http和基于資源API風格的。(每一個功能都是通過一個資源以及在它之上執行的操作來實現)

圖3, 通過REST接口來暴露微服務

Thrift 是REST/HTTP 同步消息之外的另一個選項。使用它你可以給你的微服務定義一個接口定義。

異步消息- AMQP, STOMP, MQTT

在某些微服務場景下,需要使用異步消息技術(可獲得不需要立即得到回復,甚至完全不要回復)。在這種場景下, 異步消息 AMPQ , STOMP , MQTT 等被廣泛使用

消息格式 - jSON, XML, Thrift,ProtoBuf,Avro

為微服務來決定最適合的消息格式是另一個關鍵要素。傳統的單體的軟件使用復雜的二進制的格式,SOA/Web services的應用使用基于復雜消息格式(SOAP)和schema(xsd)的文本消息。在大多數的微服務里面,它們使用簡單的基于文本的消息格式,例如基于HTTP 資源API風格之上的JSON/XML等。在某些情況下它們需要二進制的格式時(文本消息在某些場景下顯得啰嗦),可以使用二進制的協議例如二進制的Thrift, Protobuf , Arvo 。

服務協議 - 定義服務的接口 - Swagger,RAML, Thrift IDL

當你已經有一個業務能力以服務的形式實現之后, 你需要定義和發布服務協議。 在傳統單體應用中, 我們很少找到這個功能來定義某個應用的業務能力。 在SOA/Web services的世界里面, WSDL用來描述服務協議,但是,我們都知道,WSDL并不是描述微服務的理想方案,因為它太復雜了而且與SOAP高度耦合。

既然我們是基于REST架構風格來構建的微服務,我們可以使用同樣的REST API定義的技術來定義服務協議。因此,微服務使用標準的REST API定義語言來定義服務協議, 比如 Swagger 和 RAML 。

對于其他一些不是基于HTTP/REST的微服務實現(例如Thrift),我們可以協議級別的接口定義語言。比如 Thrift IDL

集成微服務(跨服務/進程通訊)

在微服務架構里,一個軟件應用是基于一組獨立的服務構建的。 因此為了實現某個應用場景,需要不同微服務、進程之間的通訊機制。這也是微服務之間跨服務、進程通訊這么重要的原因

在SOA的實現中,服務之間的跨服務通訊是通過企業服務總線ESB來實現的,并且大部分的業務邏輯在中間層中(消息路由,傳送,編排)。但是微服務架構推崇去掉中央消息總線將業務邏輯放到服務和客戶端去(也稱之為smart endpoints)

因為微服務使用HTTP, JSON等標準協議,當做跨微服務之間的通訊時,需要跟一個不同的協議做集成的需求很少。在微服務里面的另一個可選方案是使用一個輕量級的消息總線或者 網關 ,網關上帶最少的路由功能,不帶任何業務邏輯實現而僅僅是一個啞管道。基于這些方式,在微服務架構里面就有了如下幾種通訊模式。

點對點風格- 直接調用服務

在點對點風格里,整個的消息路由邏輯在端點上,服務之間直接通訊。每個服務暴露一組REST API,外部的服務或者客戶端通過REST API 來調用。

圖4, 服務間通訊: 點對點連接

明顯的,這種模型對于簡單的微服務架構應用有效。但是隨著服務數量的增加,它會慢慢變得復雜。這也是為什么在SOA里面要用ESB來避免雜亂的點對點的連接。讓我們試著總結一下點對點模式的弊端。

非功能需求 比如用戶認證, 流控,監控等必須在每個微服務里實現

由于通用功能的重復,每個微服務的實現變得復雜

在服務和客戶端之間沒有通訊控制(甚至對于監控,跟蹤,過濾等都沒有)

對于大的微服務實現來說直接的通訊形式通常被認為是 反模式

因此, 在復雜的微服務應用場景下,不要使用點對點直連或者中央的ESB,我們可以使用一個輕量級的中央消息總線給所有微服務提供一個抽象層,而且可以用來實現各種非功能的能力。這種風格也叫做API Gateway風格。

API Gateway風格

API Gateway風格的核心理念是使用一個輕量級的消息網關作為所有客戶端、消費者的主入口并且在網關層面上實現通用的非功能性需求。 通常,一個API網關允許你通過REST來消費一個受管理的API。 因此我們可以使用它來暴露微服務所實現的業務功能, 以受管理的API的形式。 實際上, 這是微服務架構與API管理的組合,給你帶來兩種技術的優點。

圖5 所有服務通過一個API網關來暴露

在我們零售的例子中,如圖5所描述的, 所有的服務通過API 網關來暴露,這是所有客戶端訪問的唯一入口。 如果一個微服務要訪問另一個微服務,也要通過這個網關。

API網關帶來以下優點:

在網關層面對存在的微服務提供必要的抽象。例如,網關可以選擇不提供一個適用所有的ApI, 而選擇對不同的用戶暴露不同的API。

在網關層面的輕量級消息路由和轉換

一個中心的地方提供非功能性的能力, 比如安全,監控,限流等

通過適用API網關模式,微服務可以變得更加輕量,因為非功能性需求都在網關上實現了。

API網關風格可能是大多數微服務 實現 里最被普遍采用的形式。

消息代理風格

微服務加可以與異步消息場景集成,比如單向的請求和使用隊列或者主題的發布訂閱消息機制。某個微服務可以使一個消息的制造者,它能將消息異步的發送到一個隊列或者主題里面。消費型的微服務可以消費隊列或者主題里來的消息。這種方式將消息的制造者和消費者解耦,而且中間的消息代理會緩存消息直到消費者處理它們。 制造消息的微服務對消費消息的微服務完全未知。

圖6 異步消息機制, 基于PUB-SUB集成

生產者與消費者直接的通訊由消息代理來完成,基于的是異步消息標準, 比如AMQP, MQTT,等等。

去中心化的數據管理

在單體架構中,應用將數據存在一個集中化的數據庫中來實現各種的功能和業務能力。

圖7 單體應用使用一個集中化的數據庫來實現所有特性

在微服務架構里,功能是跨多個微服務來提供的,這樣一來,如果我們繼續使用集中化的數據庫,那么微服務之間就不是互相獨立了(例如數據庫的某個schema為了某個服務要更改,那么極有可能會破壞其他的服務)。 因此每個微服務必須有自己的數據庫。

圖8 微服務有自己的私有數據庫,它們無法直接訪問其他微服務的數據庫

要實現微服務架構下的去中心化數據庫管理有如下幾個核心關注點:

每個微服務都有一個私有的數據庫, 存放的數據用來實現它所要提供的業務功能

一個特定的微服務自己能訪問自己的私有專用的數據庫,而不能直接訪問其他微服務的數據庫

在某些業務場景下,為了事務性要求你可能需要一次更新多個數據庫。在這種情況下,其他微服務的數據庫更新應該通過它的API調用來完成(不允許直接訪問它的數據庫)

去中心化的數據管理讓你可以得到完全解耦的數據庫, 并且也有了自由選擇各種數據庫技術的能力(比如SQL 或者NOSQL,每個服務都可以有不同的數據庫管理系統)。 但是, 對于復雜的涉及多個微服務的事務型應用場景下,事務操作應該使用各個微服務提供的API實現,具體邏輯應該在客戶端或者中間層(網關)中實現。

去中心化治理

微服務架構適用微服務治理。

總的來說,“治理”的意思是建立和實施“如何讓人員和解決方案為了組織目標而一起工作”。在SOA的上下文中, SOA治理 指導可重用服務的開發,指導服務該如何設計和開發,以及服務如何隨時間演進。它在服務的提供者與服務消費者之間建立協議,告訴消費者它們可以期望得到什么;告訴提供者它們有義務提供什么。在SOA治理中,有兩種普通采用的治理模型:

設計時治理 - 定義和控制服務的生成,設計以及服務策略的實現

運行時治理 - 在運行時實施服務策略的能力

那么,微服務上下文中的治理到底是什么意思?在微服務架構下,服務是以完全獨立解耦的方式構建的,用的技術棧可以完全不同。因此,定義一個通用的服務設計和開發標準沒有太大必要。 我們可以將微服務場景下的去中心化的治理能力總結如下:

在微服務架構下, 沒有必要擁有一個中心化的設計時治理

微服務可以自己決策自己的設計實現

微服務架構可以共享通用/可重用的服務

某些運行時治理, 比如SLA, 限流,監控,通用的安全需求以及服務發現可以在API網關級別實現

服務注冊與服務發現

在微服務架構下, 你需要管理的微服務數量相當之高。而且,由于微服務本身的快速敏捷的開發部署特性,它們的運行地點會動態變化。因此,你需要能夠在運行時找到一個微服務運行的位置。這個問題的解決方案是使用一個服務注冊表。

服務注冊表

服務注冊表保持微服務實例以及它們的位置。微服務實例在服務啟動時在注冊表里面注冊,在關閉時注銷。消費者可以通過注冊表找到可用的微服務以及它們的位置。

服務發現

要找到可用的微服務以及它們的位置,我們需要有一個服務發現機制。 有2種服務發現機制,客戶端發現和服務端發現。

客戶端發現 - 這種方式下,客戶端或者API-GW通過查詢服務注冊表來得到服務實例的位置

圖9 客戶端發現

這里,客戶端/API-GW通過調用服務注冊組件來實現服務發現邏輯。

服務端發現 - 這種方式下,客戶端/API-GW向運行在某個公知位置的組件發送請求(例如負載均衡器)。 這個組件調用服務注冊表然后得到這個微服務的絕對位置。

圖10 服務端發現

微服務部署方案如kubernetes( http://kubernetes.io/v1.1/docs ... s.htm l)提供的就是服務端解決方案

部署

提到微服務架構時,微服務的部署扮演著一個核心角色而且有如下核心要求:

有能力在不依賴其他服務的情況下部署/撤銷

能在每個微服務的級別進行擴展(某個服務可能比其他服務有更多的流量)

快速構建和部署微服務

一個微服務的失效不能影響其他服務

Docker (一個開源引擎可以讓開發者和系統管理員部署自包含的應用容器到linux環境中)提供了一個滿足上述需求的部署方案。里面涉及的核心步驟有:

將微服務打包為docker 鏡像

將每個服務實例部署為容器

通過改變容器的數量來實現服務的擴展

使用docker容器時服務的構建,部署和啟動都相當快(通常比虛擬機快的多)

kubernetes 擴展了docker的能力:可以像管理一個系統那樣管理一個linux容器的集群,跨主機運行和管理docker容器, 提供容器的多地部署,服務發現和復制控制。正如你看到的,這些特性中的大多數在微服務場景下也是特別核心的。因此使用kubernetes(基于docker)來做微服務部署贏成為一種相當強大的方法,特別對于大型的微服務部署而言。

圖11 以容器方式構建和部署微服務

在圖11中,展示了容器應用中的微服務的部署概覽。每個微服務實例部署為一個容器,每個主機上跑了兩個容器。 在任意一臺主機上你都可以指定跑的容器的數量。

安全

微服務安全是在實際場景中應用微服務的一個普遍要求。在講微服務安全之前,我們先看看在單體應用下我們通常是如何實現安全的。

在一個單體應用中,安全主要關心‘調用者是誰’, ‘調用者能干什么’, 以及‘我們如何傳播這個信息’

這通常在一個公用的安全組件上實現,它部署在請求處理鏈的首部,通過一個底層的用戶數據庫來填充必要的信息。

這樣, 我們可以將這個模型應用到微服務架構中嗎? 可以,但是要求在每個微服務級別實現一個安全組件,查詢中心共享的用戶庫來獲得必要的信息。這是一個非常繁瑣的方式來解決微服務場景下的安全問題。我們可以利用廣泛使用的API-安全標準來做,例如OAuth2, OpeniD Connect, 這是解決微服務安全問題的更好方式。在我們深入之前,我先總結一下每種標準的目的以及我們該如何使用。

OAuth2 - 是一個訪問授權協議。客戶端相授權服務器認證得到一個‘訪問令牌’,訪問令牌里面不包含關于用戶或者客戶端的任何信息。它僅僅包含一個對客戶信息的應用,而且僅僅能被授權服務器查詢。因此,也常被稱為'引用型令牌,即使在公網、互聯網上使用也是安全的。

OpenID Connect如OAuth2行為類似,但是除了訪問令牌之外,授權訪問也會發出一個ID令牌,其中包含有用戶的信息。這常通過JWT(JSON WEB TOKEN)實現,由授權服務器簽名。這樣保證了授權服務器與客戶端的互相信任。JWT令牌因此也稱為“值型令牌”,因為它里面包含有用戶信息,通過不適于在公共網絡使用。

現在,我們看看如何在零售的案例中使用這些安全標準來實現微服務的安全:

圖12 微服務安全,基于OAuth2和OpenID Connect

如圖12, 在實現微服務安全時有如下關雎步驟:

將認證交給OAuth2和OpenID Connect服務器(授權服務器),如此一來用戶只要有權使用這些數據微服務就可以提供訪問

使用API-GW方式,對于所有的客戶請求有單一入口

客戶連接到授權服務器得到訪問令牌(引用型令牌),然后將令牌和請求一起發給API-GW

網關做令牌翻譯 - API-GW提出訪問令牌,發送到授權服務器得到JWT(值型令牌)

網關將JWT和請求一起發給微服務層

JWT含有必要的信息來做用戶會話保存等。如果每個服務都可以理解JSON web token,那么你就擁有了可以分發身份信息到整個系統中的機制

在每個微服務層,我們可以有一個組件來處理JWT,這個實現通常非常簡單。

事務

如何在微服務中支持事務? 實際上, 跨多個微服務來實現分布式事務是一個相當復雜的工作。微服務架構本身鼓勵的是服務之間非事務的協調。

這個意思是基于每個服務完全自包含且單一責任的原則。需要跨多個服務之間的分布式事務通常是微服務設計上的缺陷,通常應該通過重構微服務的范圍來解決。盡管如此,如果必須要有這種跨服務的分布式事務, 這種場景可以通過在每個微服務層引入‘修正操作’來實現。 核心思想是,某個特定的微服務是根據單一責任設計的,如果它無法完成某個特定操作時,我們可以認為整個微服務都失敗了。 這時上游其他的微服務就要起到用它們各自的修正操作來回滾。

為’失效‘設計

微服務架構引入了一組離散的服務集合,與單體架構相比,這增加了在每一個微服務級別失敗的可能性。一個微服務的失效可能由于網絡問題,底層資源不可用等等因素。單個微服務的不可用或者沒響應不應該讓整個應用失敗。這樣,微服務應該是容錯的,可能的話有能力自動恢復,客戶端也要能優雅處理。

另外,因為服務可能隨時失敗,快速發現失敗(實時監控),可能的話自動恢復服務也十分重要。

在微服務場景下,有幾種處理錯誤的通用模式:

鏈路斷開器

當你對一個微服務做外部調用時,你可以給每一個調用配置一個錯誤監控組件。當失敗達到某個閾值時,組件會停止對那個服務的調用(斷開鏈路)。 在特定數目的請求是open狀態之后(可以自己定義),將鏈路閉合回去。

這個模式對避免無謂的資源消耗特別有用, 請求因為超時被推遲,也讓我們有機會監控系統狀態(基于活躍的open的鏈路狀態)

隔離墻

由于應用由相當數量的微服務組成,應用的某一部分的微服務失效不應影響應用的其他部分。隔離墻模式就是將應用的不同部分隔離,這樣一來,應用的某個部分的某個服務的失敗不會影響其他服務。

超時

超時模型是這樣一種機制,它允許在當你覺得服務的響應不會回來時停止等待。這樣你就可以配置等待的時間間隔。

那么,我們應該在服務中哪里使用及如何使用這些模式呢? 在大多數時候,大多數的模式適用于網關層。也就是說當服務不可用或者沒響應時,我們可以在網關級別決定使用鏈路斷開或者超時的模式來給服務發請求。同樣的, 在網關級別實現隔離墻的模式也十分重要,因為它是所有請求的唯一入口,所以某個服務的失敗不會影響其他服務的調用。

另外,網關也可以用作我們監控每個服務狀態的中心點,因為每個服務都是通過網關來調用的。

微服務,企業級集成, API管理,以及其他

我們已經討論了微服務架構的各種特性,以及如何在現代的企業it里實現它們。盡管如此,我們必須知道微服務不是包治百病的靈丹妙藥。盲目的吸收流行概念并不會真正解決企業it的實際問題。你通篇讀下來會覺得,微服務確實有很多優點我們應該利用。但是,我們也必須意識到使用微服務來解決所有的IT問題是不切實際的。 例如,微服務架構推崇去除作為中央總線的ESB,但是在實際的IT場景下,我們已經有相當數量的線上應用和服務并不是基于微服務的。因此,為了集成它們,我們必須使用某種集成總線。所以, 理想情況是,一個融合了微服務和其他企業架構理念(例如集成)的方法顯然更切合實際。我將在另一篇文章中單獨加以闡述。

中国北京单场足球彩票