微服務(wù)架構(gòu)設(shè)計

周末比較忙,找了一篇之前舊文,希望能加深大家對互聯(lián)網(wǎng)微服務(wù)架構(gòu)的理解程度。

本文主要針對微服務(wù)的架構(gòu)發(fā)展由來,發(fā)展的原因,集群部署,做了統(tǒng)一的介紹。(原文出自國外大神對亞馬遜架構(gòu)發(fā)展,及uber公司的技術(shù)發(fā)展的介紹進行一定的節(jié)選得出)

單體式應(yīng)用的不足

敏捷開發(fā)和部署舉步維艱,其中最主要問題就是這個應(yīng)用太復(fù)雜,以至于任何單個開發(fā)者都不可能搞懂它。因此,修正bug和正確的添加新功能變的非常困難,應(yīng)用的啟動時間很耗時,復(fù)雜而巨大的單體式應(yīng)用也不利于持續(xù)性開發(fā)

因為所有模塊都運行在一個進程中,任何一個模塊中的一個bug,比如內(nèi)存泄露,將會有可能弄垮整個進程。除此之外,因為所有應(yīng)用實例都是唯一的,這個bug將會影響到整個應(yīng)用的可靠性。 最后,單體式應(yīng)用使得采用新架構(gòu)和語言非常困難。

微處理架構(gòu)——處理復(fù)雜事物

將應(yīng)用分解為小的、互相連接的微服務(wù)。 一個微服務(wù)一般完成某個特定的功能,比如下單管理、客戶管理等等。所有服務(wù)都是采用異步的,基于消息的通訊。API Gateway負責負載均衡、緩存、訪問控制、API 計費監(jiān)控等等任務(wù),可以通過NGINX方便實現(xiàn)。運行時,行程管理服務(wù)由多個服務(wù)實例構(gòu)成。每一個服務(wù)實例都是一個Docker容器。為了保證高可用,這些容器一般都運行在多個云VM上。服務(wù)實例前是一層諸如NGINX的負載均衡器,他們負責在各個實例間分發(fā)請求。負載均衡器也同時處理其它請求,例如緩存、權(quán)限控制、API統(tǒng)計和監(jiān)控。 這種微服務(wù)架構(gòu)模式深刻影響了應(yīng)用和數(shù)據(jù)庫之間的關(guān)系,不像傳統(tǒng)多個服務(wù)共享一個數(shù)據(jù)庫,微服務(wù)架構(gòu)每個服務(wù)都有自己的數(shù)據(jù)庫。另外,這種思路也影響到了企業(yè)級數(shù)據(jù)模式。同時,這種模式意味著多份數(shù)據(jù),但是,如果你想獲得微服務(wù)帶來的好處,每個服務(wù)獨有一個數(shù)據(jù)庫是必須的,因為這種架構(gòu)需要這種松耦合。另外,每種服務(wù)可以用更適合自己的數(shù)據(jù)庫類型,也被稱作多語言一致性架構(gòu)。

微服務(wù)架構(gòu)的好處

微服務(wù)架構(gòu)模式有很多好處。首先,通過分解巨大單體式應(yīng)用為多個服務(wù)方法解決了復(fù)雜性問題每個服務(wù)都有一個用RPC-或者消息驅(qū)動API定義清楚的邊界。微服務(wù)架構(gòu)模式給采用單體式編碼方式很難實現(xiàn)的功能提供了模塊化的解決方案,由此,單個服務(wù)很容易開發(fā)、理解和維護。 第二,這種架構(gòu)使得每個服務(wù)都可以有專門開發(fā)團隊來開發(fā)。甚至于,因為服務(wù)都是相對簡單,即使用現(xiàn)在技術(shù)重寫以前代碼也不是很困難的事情。 第三,微服務(wù)架構(gòu)模式是每個微服務(wù)獨立的部署。開發(fā)者不再需要協(xié)調(diào)其它服務(wù)部署對本服務(wù)的影響。

舉個例子:

亞馬遜Android客戶端

購物車服務(wù) -- 購物車中的物品數(shù)

下單服務(wù) -- 下單歷史

分類服務(wù) -- 基本產(chǎn)品信息,如名字、圖片和價格

評論服務(wù) -- 用戶評論

庫存服務(wù) -- 低庫存警告

快遞服務(wù) -- 快遞選項、截止時間、來自不同快遞API的成本計算

推薦服務(wù) -- 推薦產(chǎn)品

實現(xiàn)一個API Gateway

如此多的服務(wù)互相關(guān)聯(lián) 調(diào)用,需要創(chuàng)建一個支持同步、非阻塞I/O的API Gateway。在JVM上,采用基于NIO技術(shù)的框架,如Netty,Vertx,Spring Reactor或者JBoss Undertow。

服務(wù)調(diào)用

一個基于微服務(wù)的應(yīng)用是一個分布式系統(tǒng),并且必須采用線程間通信的機制。有兩種線程間通信的方法。一種是采用異步機制,基于消息的方法。這類的實現(xiàn)方法有JMS和AMQP。另外的,例如Zeromq屬于服務(wù)間直接通信。還有一種線程間通信采用同步機制,例如Thrift和HTTP。事實上一個系統(tǒng)會同時采用同步和異步兩種機制。

服務(wù)發(fā)現(xiàn)

API Gateway需要知道每一個微服務(wù)的IP和端口。在傳統(tǒng)應(yīng)用中,你可能會硬編碼這些地址,但是在現(xiàn)在云基礎(chǔ)的微服務(wù)應(yīng)用中,這將是個簡單的問題?;A(chǔ)服務(wù)通常會采用靜態(tài)地址,可以采用操作系統(tǒng)環(huán)境變量來指定。但是,探測應(yīng)用服務(wù)的地址就沒那么容易了。應(yīng)用服務(wù)通常動態(tài)分配地址和端口。同樣的,由于擴展或者升級,服務(wù)的實例也會動態(tài)的改變。因此,API Gateway需要采用系統(tǒng)的服務(wù)發(fā)現(xiàn)機制,要么采用服務(wù)端發(fā)現(xiàn),要么是客戶端發(fā)現(xiàn)

處理部分失敗

一個需要考慮的問題就是部分失敗。這個問題發(fā)生在分布式系統(tǒng)中當一個服務(wù)調(diào)用另外一個服務(wù)超時或者不可用的情況。API Gateway不應(yīng)該被阻斷并處于無限期等待下游服務(wù)的狀態(tài)。Netfilix提供了一個比較好的解決方案,具體的應(yīng)對措施包括: ? 網(wǎng)絡(luò)超時:當?shù)却憫?yīng)時,不要無限期的阻塞,而是采用超時策略。使用超時策略可以確保資源不會無限期的占用。 ? 限制請求的次數(shù):可以為客戶端對某特定服務(wù)的請求設(shè)置一個訪問上限。如果請求已達上限,就要立刻終止請求服務(wù)。 ??斷路器模式(Circuit Breaker Pattern):記錄成功和失敗請求的數(shù)量。如果失效率超過一個閾值,觸發(fā)斷路器使得后續(xù)的請求立刻失敗。如果大量的請求失敗,就可能是這個服務(wù)不可用,再發(fā)請求也無意義。在一個失效期后,客戶端可以再試,如果成功,關(guān)閉此斷路器。 ? 提供回滾:當一個請求失敗后可以進行回滾邏輯。例如,返回緩存數(shù)據(jù)或者一個系統(tǒng)默認值。?Netflix Hystrix是一個實現(xiàn)相關(guān)模式的開源庫。如果使用JVM,推薦考慮使用Hystrix。而如果使用非JVM環(huán)境,你可以使用類似功能的庫。新舊版本一起運行:REST,一種解決方案是把版本號嵌入到URL中。每個服務(wù)都可能同時處理多個版本的API?;蛘撸憧梢圆渴鸲鄠€實例,每個實例負責處理一個版本的請求。?同步模式:客戶端請求需要服務(wù)端即時響應(yīng),甚至可能由于等待而阻塞。? ? http 異步模式:客戶端請求不會阻塞進程,服務(wù)端的響應(yīng)可以是非即時的。?? 消息隊列

交互模式

客戶端和服務(wù)器之間有很多的交互模式? 兩種維度:一對一/多? 同步/異步

??一對一:每個客戶端請求有一個服務(wù)實例來響應(yīng)。 ??一對多:每個客戶端請求有多個服務(wù)實例來響應(yīng) 第二個維度是這些交互式。

同步還是異步: ? 同步模式:客戶端請求需要服務(wù)端即時響應(yīng),甚至可能由于等待而阻塞。 ? 異步模式:客戶端請求不會阻塞進程,服務(wù)端的響應(yīng)可以是非即時的。?

?一對多的交互模式有以下幾種方式:

? 發(fā)布/ 訂閱模式:客戶端發(fā)布通知消息,被零個或者多個感興趣的服務(wù)消費。

? 發(fā)布/異步響應(yīng)模式:客戶端發(fā)布請求消息,然后等待從感興趣服務(wù)發(fā)回的響應(yīng)。?

異步的,基于消息通信

當使用基于異步交換消息的進程通信方式時,一個客戶端通過向服務(wù)端發(fā)送消息提交請求。如果服務(wù)端需要回復(fù),則會發(fā)送另外一個獨立的消息給客戶端。因為通信是異步的,客戶端不會因為等待而阻塞,一個消息由頭部(元數(shù)據(jù)例如發(fā)送方)和消息體構(gòu)成。消息通過channel發(fā)送,任何數(shù)量的生產(chǎn)者都可以發(fā)送消息到channel,同樣的,任何數(shù)量的消費者都可以從渠道中接受數(shù)據(jù)。

有兩類channel,點對點?和?發(fā)布/訂閱。點對點channel會把消息準確的發(fā)送到某個從channel讀取消息的消費者,服務(wù)端使用點對點來實現(xiàn)之前提到的一對一交互模式;而發(fā)布/訂閱則把消息投送到所有從channel讀取數(shù)據(jù)的消費者,服務(wù)端使用發(fā)布/訂閱channel來實現(xiàn)上面提到的一對多交互模式。

打車軟件如何使用發(fā)布/訂閱:

行程管理服務(wù)在發(fā)布-訂閱channel內(nèi)創(chuàng)建一個行程消息,并通知調(diào)度服務(wù)有一個新的行程請求,調(diào)度服務(wù)發(fā)現(xiàn)一個可用的司機然后向發(fā)布-訂閱channel寫入司機建議消息(Driver Proposed message)來通知其他服務(wù)。

有大量開源消息系統(tǒng)可選,比如RabbitMQApache Kafka、Apache ActiveMQNSQ。它們都支持某種形式的消息和channel,并且都是可靠的、高性能和可擴展的;然而,它們的消息模型完全不同。 使用消息機制有很多優(yōu)點: ??解耦客戶端和服務(wù)端:客戶端只需要將消息發(fā)送到正確的channel??蛻舳送耆恍枰私饩唧w的服務(wù)實例,更不需要一個發(fā)現(xiàn)機制來確定服務(wù)實例的位置。 ??Message Buffering:在一個同步請求/響應(yīng)協(xié)議中,例如HTTP,所有的客戶端和服務(wù)端必須在交互期間保持可用。而在消息模式中,消息broker將所有寫入channel的消息按照隊列方式管理,直到被消費者處理。也就是說,在線商店可以接受客戶訂單,即使下單系統(tǒng)很慢或者不可用,只要保持下單消息進入隊列就好了。 ? 彈性客戶端-服務(wù)端交互:消息機制支持以上說的所有交互模式。 ??直接進程間通信:基于RPC機制,試圖喚醒遠程服務(wù)看起來跟喚醒本地服務(wù)一樣。然而,因為物理定律和部分失敗可能性,他們實際上非常不同。消息使得這些不同非常明確,開發(fā)者不會出現(xiàn)問題。 然而,消息機制也有自己的缺點: ??額外的操作復(fù)雜性:消息系統(tǒng)需要單獨安裝、配置和部署。消息broker(代理)必須高可用,否則系統(tǒng)可靠性將會受到影響。 ??實現(xiàn)基于請求/響應(yīng)交互模式的復(fù)雜性:請求/響應(yīng)交互模式需要完成額外的工作。

同步的,基于請求/響應(yīng)的IPC

當使用一個同步的,基于請求/響應(yīng)的IPC機制,客戶端向服務(wù)端發(fā)送一個請求,服務(wù)端處理請求,返回響應(yīng)。最常見的兩個協(xié)議是REST和Thrift。首先我們來看下REST。?REST?現(xiàn)在很流行使用RESTful風格的API。REST是基于HTTP協(xié)議的。另外,REST是一個資源,一般代表一個業(yè)務(wù)對象,比如一個客戶或者一個產(chǎn)品,或者一組商業(yè)對象。REST使用HTTP語法協(xié)議來修改資源,一般通過URL來實現(xiàn)。舉個例子,GET請求返回一個資源的簡單信息,響應(yīng)格式通常是XML或者JSON對象格式。POST請求會創(chuàng)建一個新資源,PUT請求更新一個資源。

客戶端發(fā)現(xiàn)模式

當使用客戶端發(fā)現(xiàn)模式時,客戶端負責決定相應(yīng)服務(wù)實例的網(wǎng)絡(luò)位置,并且對請求實現(xiàn)負載均衡。客戶端從一個服務(wù)注冊服務(wù)中查詢,其中是所有可用服務(wù)實例的庫。客戶端使用負載均衡算法從多個服務(wù)實例中選擇出一個,然后發(fā)出請求。

服務(wù)實例的網(wǎng)絡(luò)位置是在啟動時注冊到服務(wù)注冊表中,并且在服務(wù)終止時從注冊表中刪除。服務(wù)實例注冊信息一般是使用心跳機制來定期刷新的。?Netflix OSS提供了一種非常棒的客戶端發(fā)現(xiàn)模式。Netflix Eureka是一個服務(wù)注冊表,為服務(wù)實例注冊管理和查詢可用實例提供了REST API接口。Netflix Ribbon是一種IPC客戶端,與Eureka合同工作實現(xiàn)對請求的負載均衡。我們會在后面詳細討論Eureka。 客戶端發(fā)現(xiàn)模式也是優(yōu)缺點分明。這種模式相對比較直接,而且除了服務(wù)注冊表,沒有其它改變的因素。除此之外,因為客戶端知道可用服務(wù)注冊表信息,因此客戶端可以通過使用哈希一致性(hashing consistently)變得更加聰明,更加有效的負載均衡。 而這種模式一個最大的缺點是需要針對不同的編程語言注冊不同的服務(wù),在客戶端需要為每種語言開發(fā)不同的服務(wù)發(fā)現(xiàn)邏輯。

服務(wù)端發(fā)現(xiàn)模式

客戶端通過負載均衡器向某個服務(wù)提出請求,負載均衡器向服務(wù)注冊表發(fā)出請求,將每個請求轉(zhuǎn)發(fā)往可用的服務(wù)實例。跟客戶端發(fā)現(xiàn)一樣,服務(wù)實例在服務(wù)注冊表中注冊或者注銷。 AWS Elastic Load Balancer(ELB)是一種服務(wù)端發(fā)現(xiàn)路由的例子,ELB一般用于均衡從網(wǎng)絡(luò)來的訪問流量,也可以使用ELB來均衡VPC內(nèi)部的流量??蛻舳耸褂肈NS,通過ELB發(fā)出請求(HTTP或者TCP)。ELB負載均衡器負責在注冊的EC2實例或者ECS容器之間均衡負載,并不存在一個分離的服務(wù)注冊表,而EC2實例和ECS實例也向ELB注冊.HTTP服務(wù)和類似NGINX和NGINX Plus的負載均衡器都可以作為服務(wù)端發(fā)現(xiàn)均衡器。例如,這篇博文就描述如何使用Consul Template來動態(tài)配置NGINX反向代理。Consul Template是周期性從存放在Consul Template注冊表中配置數(shù)據(jù)重建配置文件的工具。當文件發(fā)生變化時,會運行一個命令。在如上博客中,Consul Template產(chǎn)生了一個nginx.conf文件,用于配置反向代理,然后運行一個命令,告訴NGINX重新調(diào)入配置文件。更復(fù)雜的例子可以用HTTP API或者DNS動態(tài)重新配置NGINX Plus。 某些部署環(huán)境,例如KubernetesMarathon在集群每個節(jié)點上運行一個代理,此代理作為服務(wù)端發(fā)現(xiàn)負載均衡器。為了向服務(wù)發(fā)出請求,客戶端使用主機IP地址和分配的端口通過代理將請求路由出去。代理將次請求透明的轉(zhuǎn)發(fā)到集群中可用的服務(wù)實例。 服務(wù)端發(fā)現(xiàn)模式也有優(yōu)缺點。最大的優(yōu)點是客戶端無需關(guān)注發(fā)現(xiàn)的細節(jié),客戶端只需要簡單的向負載均衡器發(fā)送請求,實際上減少了編程語言框架需要完成的發(fā)現(xiàn)邏輯。

服務(wù)注冊表

服務(wù)注冊表是服務(wù)發(fā)現(xiàn)很重要的部分,它是包含服務(wù)實例網(wǎng)絡(luò)地址的數(shù)據(jù)庫。服務(wù)注冊表需要高可用而且隨時更新。客戶端可以緩存從服務(wù)注冊表獲得的網(wǎng)絡(luò)地址。Netflix Eureka是一個服務(wù)注冊表很好地例子,提供了REST API注冊和請求服務(wù)實例。 服務(wù)實例使用POST請求注冊網(wǎng)絡(luò)地址,每30秒必須使用PUT方法更新注冊表,使用HTTP DELETE請求或者實例超時來注銷。可以想見,客戶端可以使用HTTP GET請求接受注冊服務(wù)實例信息。 Netflix通過在每個AWS EC2域運行一個或者多個Eureka服務(wù)實現(xiàn)高可用性,每個Eureka服務(wù)器都運行在擁有彈性IP地址的EC2實例上。DNS TEXT記錄用于存儲Eureka集群配置,其中存放從可用域到一系列Eureka服務(wù)器網(wǎng)絡(luò)地址的列表。當Eureka服務(wù)啟動時,向DNS請求接受Eureka集群配置,確認同伴位置,給自己分配一個未被使用的彈性IP地址。 Eureka客戶端—服務(wù)和服務(wù)客戶端—向DNS請求發(fā)現(xiàn)Eureka服務(wù)的網(wǎng)絡(luò)地址,客戶端首選使用同一域內(nèi)的服務(wù)。然而,如果沒有可用服務(wù),客戶端會使用另外一個可用域的Eureka服務(wù)。 另外一些服務(wù)注冊表例子包括:

etcd?– 是一個高可用,分布式的,一致性的,鍵值表,用于共享配置和服務(wù)發(fā)現(xiàn)。兩個著名案例包括Kubernetes和Cloud Foundry。

consul?– 是一個用于發(fā)現(xiàn)和配置的服務(wù)。提供了一個API允許客戶端注冊和發(fā)現(xiàn)服務(wù)。Consul可以用于健康檢查來判斷服務(wù)可用性。

Apache ZooKeeper?– 是一個廣泛使用,為分布式應(yīng)用提供高性能整合的服務(wù)。Apache ZooKeeper最初是Hadoop的子項目,現(xiàn)在已經(jīng)變成頂級項目。

。一種方式是服務(wù)實例自己注冊,也叫自注冊模式(self-registration pattern);另外一種方式是為其它系統(tǒng)提供服務(wù)實例管理的,也叫第三方注冊模式(third party registration pattern)。

自注冊模式:

服務(wù)實例負責在服務(wù)注冊表中注冊和注銷。另外,如果需要的話,一個服務(wù)實例也要發(fā)送心跳來保證注冊信息不會過時

Eureka客戶端負責處理服務(wù)實例的注冊和注銷。

微服務(wù)和分布式數(shù)據(jù)管理問題

單體式應(yīng)用一般都會有一個關(guān)系型數(shù)據(jù)庫,由此帶來的好處是應(yīng)用可以使用 ACID transactions,可以帶來一些重要的操作特性:

原子性 – 任何改變都是原子性的

一致性 – 數(shù)據(jù)庫狀態(tài)一直是一致性的

隔離性 – 即使交易并發(fā)執(zhí)行,看起來也是串行的

Durable – 一旦交易提交了就不可回滾

應(yīng)用可以簡化為:開始一個交易,改變(插入,刪除,更新)很多行,然后提交這些交易。 使用關(guān)系型數(shù)據(jù)庫帶來另外一個優(yōu)勢在于提供SQL(功能強大,可聲明的,表轉(zhuǎn)化的查詢語言)支持。用戶可以非常容易通過查詢將多個表的數(shù)據(jù)組合起來,RDBMS查詢調(diào)度器決定最佳實現(xiàn)方式,用戶不需要擔心例如如何訪問數(shù)據(jù)庫等底層問題。另外,因為所有應(yīng)用的數(shù)據(jù)都在一個數(shù)據(jù)庫中,很容易去查詢。 然而,對于微服務(wù)架構(gòu)來說,數(shù)據(jù)訪問變得非常復(fù)雜,這是因為數(shù)據(jù)都是微服務(wù)私有的,唯一可訪問的方式就是通過API。這種打包數(shù)據(jù)訪問方式使得微服務(wù)之間松耦合,并且彼此之間獨立。如果多個服務(wù)訪問同一個數(shù)據(jù),schema會更新訪問時間,并在所有服務(wù)之間進行協(xié)調(diào)。 更甚于,不同的微服務(wù)經(jīng)常使用不同的數(shù)據(jù)庫。應(yīng)用會產(chǎn)生各種不同數(shù)據(jù),關(guān)系型數(shù)據(jù)庫并不一定是最佳選擇。某些場景,某個NoSQL數(shù)據(jù)庫可能提供更方便的數(shù)據(jù)模型,提供更加的性能和可擴展性。例如,某個產(chǎn)生和查詢字符串的應(yīng)用采用例如Elasticsearch的字符搜索引擎。同樣的,某個產(chǎn)生社交圖片數(shù)據(jù)的應(yīng)用可以采用圖片數(shù)據(jù)庫,例如,Neo4j;因此,基于微服務(wù)的應(yīng)用一般都使用SQL和NoSQL結(jié)合的數(shù)據(jù)庫,也就是被稱為polyglot persistence的方法。 分區(qū)的,polyglot-persistent架構(gòu)用于存儲數(shù)據(jù)有許多優(yōu)勢,包括松耦合服務(wù)和更佳性能和可擴展性。然而,隨之而來的則是分布式數(shù)據(jù)管理帶來的挑戰(zhàn)。 第一個挑戰(zhàn)在于如何完成一筆交易的同時保持多個服務(wù)之間數(shù)據(jù)一致性。之所以會有這個問題,我們以一個在線B2B商店為例,客戶服務(wù)維護包括客戶的各種信息,例如credit lines。訂單服務(wù)管理訂單,需要驗證某個新訂單與客戶的信用限制沒有沖突。在單一式應(yīng)用中,訂單服務(wù)只需要使用ACID交易就可以檢查可用信用和創(chuàng)建訂單。

事件驅(qū)動架構(gòu)

對許多應(yīng)用來說,這個解決方案就是使用事件驅(qū)動架構(gòu)(event-driven architecture)。在這種架構(gòu)中,當某件重要事情發(fā)生時,微服務(wù)會發(fā)布一個事件,例如更新一個業(yè)務(wù)實體。當訂閱這些事件的微服務(wù)接收此事件時,就可以更新自己的業(yè)務(wù)實體,也可能會引發(fā)更多的時間發(fā)布。 可以使用事件來實現(xiàn)跨多服務(wù)的業(yè)務(wù)交易。交易一般由一系列步驟構(gòu)成,每一步驟都由一個更新業(yè)務(wù)實體的微服務(wù)和發(fā)布激活下一步驟的事件構(gòu)成。

?事件驅(qū)動架構(gòu)也是既有優(yōu)點也有缺點,此架構(gòu)可以使得交易跨多個服務(wù)且提供最終一致性,并且可以使應(yīng)用維護最終視圖;而缺點在于編程模式比ACID交易模式更加復(fù)雜:為了從應(yīng)用層級失效中恢復(fù),還需要完成補償性交易,例如,如果信用檢查不成功則必須取消訂單;另外,應(yīng)用必須應(yīng)對不一致的數(shù)據(jù),這是因為臨時(in-flight)交易造成的改變是可見的,另外當應(yīng)用讀取未更新的最終視圖時也會遇見數(shù)據(jù)不一致問題。另外一個缺點在于訂閱者必須檢測和忽略冗余事件。

原子操作Achieving Atomicity

事件驅(qū)動架構(gòu)還會碰到數(shù)據(jù)庫更新和發(fā)布事件原子性問題。例如,訂單服務(wù)必須向ORDER表插入一行,然后發(fā)布Order Created event,這兩個操作需要原子性。如果更新數(shù)據(jù)庫后,服務(wù)癱了(crashes)造成事件未能發(fā)布,系統(tǒng)變成不一致狀態(tài)。確保原子操作的標準方式是使用一個分布式交易,其中包括數(shù)據(jù)庫和消息代理。

使用本地交易發(fā)布事件

獲得原子性的一個方法是對發(fā)布事件應(yīng)用采用multi-step process involving only local transactions,技巧在于一個EVENT表,此表在存儲業(yè)務(wù)實體數(shù)據(jù)庫中起到消息列表功能。應(yīng)用發(fā)起一個(本地)數(shù)據(jù)庫交易,更新業(yè)務(wù)實體狀態(tài),向EVENT表中插入一個事件,然后提交此次交易。另外一個獨立應(yīng)用進程或者線程查詢此EVENT表,向消息代理發(fā)布事件,然后使用本地交易標志此事件為已發(fā)布,

挖掘數(shù)據(jù)庫交易日志

另外一種不需要2PC而獲得線程或者進程發(fā)布事件原子性的方式就是挖掘數(shù)據(jù)庫交易或者提交日志。應(yīng)用更新數(shù)據(jù)庫,在數(shù)據(jù)庫交易日志中產(chǎn)生變化,交易日志挖掘進程或者線程讀這些交易日志,將日志發(fā)布給消息代理。

使用事件源

Event sourcing (事件源)通過使用根本不同的事件中心方式來獲得不需2PC的原子性,保證業(yè)務(wù)實體的一致性。 這種應(yīng)用保存業(yè)務(wù)實體一系列狀態(tài)改變事件,而不是存儲實體現(xiàn)在的狀態(tài)。事件是長期保存在事件數(shù)據(jù)庫中,提供API添加和獲取實體事件。事件存儲跟之前描述的消息代理類似,提供API來訂閱事件。事件存儲將事件遞送到所有感興趣的訂閱者,事件存儲是事件驅(qū)動微服務(wù)架構(gòu)的基干。 事件源方法有很多優(yōu)點:解決了事件驅(qū)動架構(gòu)關(guān)鍵問題,使得只要有狀態(tài)變化就可以可靠地發(fā)布事件,也就解決了微服務(wù)架構(gòu)中數(shù)據(jù)一致性問題。

第一個挑戰(zhàn)就是如何在多服務(wù)之間維護業(yè)務(wù)交易一致性;第二個挑戰(zhàn)是如何從多服務(wù)環(huán)境中獲取一致性數(shù)據(jù)。 最佳解決辦法是采用事件驅(qū)動架構(gòu)。其中碰到的一個挑戰(zhàn)是如何原子性的更新狀態(tài)和發(fā)布事件。有幾種方法可以解決此問題,包括將數(shù)據(jù)庫視為消息隊列、交易日志挖掘和事件源。

微服務(wù)部署策略

部署一個單體式應(yīng)用意味運行大型應(yīng)用的多個副本,典型的提供若干個(N)服務(wù)器(物理或者虛擬),運行若干個(M)個應(yīng)用實例。部署單體式應(yīng)用不會很直接,但是肯定比部署微服務(wù)應(yīng)用簡單些。 一個微服務(wù)應(yīng)用由上百個服務(wù)構(gòu)成,服務(wù)可以采用不同語言和框架分別寫就。每個服務(wù)都是一個單一應(yīng)用,可以有自己的部署、資源、擴展和監(jiān)控需求。例如,可以根據(jù)服務(wù)需求運行若干個服務(wù)實例,除此之外,每個實例必須有自己的CPU,內(nèi)存和I/O資源。

單主機多服務(wù)實例模式

部署微服務(wù)的一種方法就是單主機多服務(wù)實例模式,使用這種模式,需要提供若干臺物理或者虛擬機,每臺機器上運行多個服務(wù)實例。例如,需要在Apache Tomcat Server上部署一個Java服務(wù)實例作為web應(yīng)用。一個Node.js服務(wù)實例可能有一個父進程和若干個子進程構(gòu)成。 另外一個參數(shù)定義同一進程組內(nèi)有多少服務(wù)實例運行。例如,可以在同一個Apache Tomcat Server上運行多個Java web應(yīng)用,或者在同一個OSGI容器內(nèi)運行多個OSGI捆綁實例。 單主機多服務(wù)實例模式也是優(yōu)缺點并存。主要優(yōu)點在于資源利用有效性。多服務(wù)實例共享服務(wù)器和操作系統(tǒng),如果進程組運行多個服務(wù)實例效率會更高,例如,多個web應(yīng)用共享同一個Apache Tomcat Server和JVM。 另一個優(yōu)點在于部署服務(wù)實例很快。只需將服務(wù)拷貝到主機并啟動它。如果服務(wù)用Java寫的,只需要拷貝JAR或者WAR文件即可。對于其它語言,例如Node.js或者Ruby,需要拷貝源碼。也就是說網(wǎng)絡(luò)負載很低。 因為沒有太多負載,啟動服務(wù)很快。如果服務(wù)是自包含的進程,只需要啟動就可以;否則,如果是運行在容器進程組中的某個服務(wù)實例,則需要動態(tài)部署進容器中,或者重啟容器。 除了上述優(yōu)點外,單主機多服務(wù)實例也有缺陷。其中一個主要缺點是服務(wù)實例間很少或者沒有隔離,除非每個服務(wù)實例是獨立進程。如果想精確監(jiān)控每個服務(wù)實例資源使用,就不能限制每個實例資源使用。因此有可能造成某個糟糕的服務(wù)實例占用了主機的所有內(nèi)存或者CPU。 同一進程內(nèi)多服務(wù)實例沒有隔離。所有實例有可能,例如,共享同一個JVM heap。某個糟糕服務(wù)實例很容易攻擊同一進程中其它服務(wù);更甚至于,有可能無法監(jiān)控每個服務(wù)實例使用的資源情況。 另一個嚴重問題在于運維團隊必須知道如何部署的詳細步驟。服務(wù)可以用不同語言和框架寫成,因此開發(fā)團隊肯定有很多需要跟運維團隊溝通事項。其中復(fù)雜性增加了部署過程中出錯的可能性。?

遷移到微服務(wù)綜述

策略1——停止挖掘

Law of Holes是說當自己進洞就應(yīng)該停止挖掘。對于單體式應(yīng)用不可管理時這是最佳建議。換句話說,應(yīng)該停止讓單體式應(yīng)用繼續(xù)變大,也就是說當開發(fā)新功能時不應(yīng)該為舊單體應(yīng)用添加新代碼,最佳方法應(yīng)該是將新功能開發(fā)成獨立微服務(wù)。

策略2——將前端和后端分離

減小單體式應(yīng)用復(fù)雜度的策略是講表現(xiàn)層和業(yè)務(wù)邏輯、數(shù)據(jù)訪問層分開。典型的企業(yè)應(yīng)用至少有三個不同元素構(gòu)成:

表現(xiàn)層——處理HTTP請求,要么響應(yīng)一個RESTAPI請求,要么是提供一個基于HTML的圖形接口。對于一個復(fù)雜用戶接口應(yīng)用,表現(xiàn)層經(jīng)常是代碼重要的部分。

業(yè)務(wù)邏輯層——完成業(yè)務(wù)邏輯的應(yīng)用核心?

數(shù)據(jù)訪問層——訪問基礎(chǔ)元素,例如數(shù)據(jù)庫和消息代理?

在表現(xiàn)層與業(yè)務(wù)數(shù)據(jù)訪問層之間有清晰的隔離。業(yè)務(wù)層有由若干方面組成的粗粒度(coarse-grained)的API,內(nèi)部包含了業(yè)務(wù)邏輯元素。API是可以將單體業(yè)務(wù)分割成兩個更小應(yīng)用的天然邊界,其中一個應(yīng)用是表現(xiàn)層,另外一個是業(yè)務(wù)和數(shù)據(jù)訪問邏輯。

策略3——抽出服務(wù)

第三種遷移策略就是從單體應(yīng)用中抽取出某些模塊成為獨立微服務(wù)。

例如,將內(nèi)存數(shù)據(jù)庫抽取出來成為一個微服務(wù)會非常有用,可以將其部署在大內(nèi)存主機上。同樣的,將對計算資源很敏感的算法應(yīng)用抽取出來也是非常有益的,這種服務(wù)可以被部署在有很多CPU的主機上。通過將資源消耗模塊轉(zhuǎn)換成微服務(wù),可以使得應(yīng)用易于擴展。 查找現(xiàn)有粗粒度邊界來決定哪個模塊應(yīng)該被抽取,也是很有益的,這使得移植工作更容易和簡單。例如,只與其他應(yīng)用異步同步消息的模塊就是一個明顯邊界,可以很簡單容易地將其轉(zhuǎn)換為微服務(wù)。 如何抽取模塊 抽取模塊第一步就是定義好模塊和單體應(yīng)用之間粗粒度接口,由于單體應(yīng)用需要微服務(wù)的數(shù)據(jù),

遷移第一步就是定義一套粗粒度APIs,第一個接口應(yīng)該是被X模塊使用的內(nèi)部接口,用于激活Z模塊;第二個接口是被Z模塊使用的外部接口,用于激活Y模塊。 遷移第二步就是將模塊轉(zhuǎn)換成獨立服務(wù)。內(nèi)部和外部接口都使用基于IPC機制的代碼,一般都會將Z模塊整合成一個微服務(wù)基礎(chǔ)框架,來出來割接過程中的問題,例如服務(wù)發(fā)現(xiàn)。 抽取完模塊,也就可以開發(fā)、部署和擴展另外一個服務(wù),此服務(wù)獨立于單體應(yīng)用和其它服務(wù)??梢詮念^寫代碼實現(xiàn)服務(wù);這種情況下,將服務(wù)和單體應(yīng)用整合的API代碼成為容災(zāi)層,在兩種域模型之間進行翻譯工作。每抽取一個服務(wù),就朝著微服務(wù)方向前進一步。隨著時間推移,單體應(yīng)用將會越來越簡單,用戶就可以增加更多獨立的微服務(wù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容