在今年的CNUTCon全球容器技術(shù)大會(huì)2016上,Netflix資深架構(gòu)師介紹了支撐其龐大業(yè)務(wù)的微服務(wù)生態(tài)系統(tǒng),本文從總結(jié)了現(xiàn)場(chǎng)的演講,試圖為各位還原Netflix微服務(wù)的面目:
Netflix是一家在全球范圍內(nèi)提供流視頻服務(wù)的公司,截止到2016年已經(jīng)擁有8300+萬訂閱用戶,每天播放時(shí)間達(dá)到了1億2千萬小時(shí),是北美互聯(lián)網(wǎng)峰值下載量的1/3。
為了支撐龐大的用戶訪問,Netflix從2009便下決心向云原生的微服務(wù)生態(tài)系統(tǒng)演進(jìn),在2016年完成了整體應(yīng)用遷徙到云端,目前擁有500+的微服務(wù),在系統(tǒng)演進(jìn)的7年內(nèi),Netflix的流量增長了1000多倍,可謂是開著火箭換發(fā)動(dòng)機(jī)。堅(jiān)決的轉(zhuǎn)向微服務(wù)+云原生讓Netflix演進(jìn)為一家成功實(shí)踐微服務(wù)架構(gòu)的互聯(lián)網(wǎng)公司,更重要的是Netflix幾乎開源了公司內(nèi)部全部的微服務(wù)架構(gòu)技術(shù)棧,這套架構(gòu)在Netflix公司大規(guī)模分布式微服務(wù)環(huán)境中經(jīng)過數(shù)年的生產(chǎn)環(huán)境檢驗(yàn)被證明是可靠的。對(duì)于打算采用微服務(wù)架構(gòu)的公司來說,可以在這套事實(shí)標(biāo)準(zhǔn)架構(gòu)的基礎(chǔ)上進(jìn)行符合自身業(yè)務(wù)需求的定制化。所以看看Netflix給出的菜:
Archaius:服務(wù)配置管理組件 ?https://github.com/Netflix/archaius

對(duì)于傳統(tǒng)的單體應(yīng)用,配置文件可以解決配置問題,但試想一下在一個(gè)500+的微服務(wù)生態(tài)中,微服務(wù)采用多種語言開發(fā),配置文件格式五花八門,如果把每個(gè)微服務(wù)的配置文件都改一遍,假設(shè)修改完配置文件后還要重啟服務(wù),那是怎么樣的噩夢(mèng),簡直酸爽。所以,對(duì)于微服務(wù)架構(gòu)而言,一個(gè)通用的配置中心是必不可少的。
Archaius提供的DynamicIntProperty類可以在配置發(fā)生變化時(shí)動(dòng)態(tài)地獲取配置,并且不需要重啟應(yīng)用,一個(gè)簡單的例子如下:
DynamicIntProperty prop=DynamicPropertyFactory.getInstance().getIntProperty("myProperty", DEFAULT_VALUE);
// prop.get() may change value at runtime
myMethod(prop.get());
這樣的特性還不夠方便,所以Archaius還提供了一個(gè)回調(diào)方式讓微服務(wù)來響應(yīng)配置變化
prop.addCallback(newRunnable() {
? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? // ...
? ? ? ? ?}??
});
Archaius作為配置中心是如何獲取到配置項(xiàng)的呢?Archaius采用了pulling架構(gòu)從配置源獲取配置,并支持多種方式包括JDBC、REST、配置文件等等,換句話說Archaius可以從數(shù)據(jù)庫(比如Amazon DynamoDB)、配置文件等源獲取配置。并且Archaius可以啟動(dòng)一個(gè)定時(shí)器,定時(shí)從配置源同步配置項(xiàng),這個(gè)定時(shí)器則可以由用戶配置。
另外我們?cè)趯?shí)踐中知道對(duì)于一個(gè)微服務(wù)而言,在sit、uat、prod等環(huán)境中配置項(xiàng)一般是不一樣的,所以Archaius可以保存同一個(gè)配置項(xiàng)的不同版本,這些版本可以來自于不同的配置源,用戶可以為同一個(gè)配置項(xiàng)的不同版本定義等級(jí)(hierarchy),這樣不同環(huán)境就可以獲取不同hierarchy中的配置了。
在一個(gè)微服務(wù)生態(tài)中,成千上百的微服務(wù)都從配置中心動(dòng)態(tài)的讀取配置信息,而配置中心又在從配置源同步配置,所以這里就很自然的出現(xiàn)了一個(gè)讀寫安全的問題,好消息是Archaius已經(jīng)解決了這個(gè)問題,Archaius是線程安全的,讀寫可以并發(fā)進(jìn)行。
Eureka:服務(wù)注冊(cè)發(fā)現(xiàn)框架 https://github.com/Netflix/eureka
Eureka在希臘語中用以表達(dá)發(fā)現(xiàn)某件事物、真相時(shí)的感嘆詞。
傳說阿基米德在洗澡的時(shí)候發(fā)現(xiàn),當(dāng)他坐進(jìn)浴盆里時(shí)有許多水溢出來,溢出來的水的體積正好應(yīng)該等于他身體的體積,發(fā)現(xiàn)了不規(guī)則物體體積精確計(jì)算的方法,不禁高興的從浴盆跳了出來,邊跑邊喊叫著“Eureka!”(引用自:維基百科)
言歸正傳,Eureka是一個(gè)基于RESTful,用來發(fā)現(xiàn)運(yùn)行在AWS域(Region)中的中間層服務(wù),同時(shí)具備負(fù)載均衡、故障恢復(fù)的服務(wù)注冊(cè)與發(fā)現(xiàn)框架。

RESTful API或者更廣泛的來說HTTP API,以及Thrift API的調(diào)用client至少需要知曉服務(wù)端的IP地址以及端口,對(duì)于傳統(tǒng)的應(yīng)用而言,這些信息基本是固定的,即便有更新也可以通過修改client的配置文件讓client知曉更新后的信息。但是對(duì)于基于云端的微服務(wù)生態(tài)來說,這樣做并不容易,下圖簡單的描述了這個(gè)問題,總的來說在微服務(wù)生態(tài)中:
服務(wù)實(shí)例的網(wǎng)絡(luò)位置都是動(dòng)態(tài)分配的。由于擴(kuò)展、失敗和升級(jí),服務(wù)實(shí)例會(huì)經(jīng)常動(dòng)態(tài)改變,因此,客戶端代碼需要使用更加復(fù)雜的服務(wù)發(fā)現(xiàn)機(jī)制。
(引用自《Service Discovery in a Microservices Architecture》,作者:Chris Richardson,譯者:DaoCloud Lychee Li)

Eureka1.x已經(jīng)被Eureka2.x替代,因?yàn)?.x到2.x發(fā)生了重大改變,筆者就只介紹下Eureka2.x版本。最新的Eureka2.x版本采用fine grain subscription模式替代了pull based模式,可以提高更新速度,減少延遲。
Eureka分為服務(wù)端與客戶端,客戶端通過自注冊(cè)模式(self-registration pattern)被服務(wù)端發(fā)現(xiàn),客戶端將自身的識(shí)別碼與服務(wù)狀態(tài)發(fā)送服務(wù)端,由服務(wù)端處理后完成服務(wù)注冊(cè)操作??蛻舳顺税l(fā)送注冊(cè)信息外,還負(fù)責(zé)發(fā)送心跳、更新、銷毀信號(hào)到服務(wù)端。服務(wù)端則通過心跳信號(hào)判斷客戶端服務(wù)的存活。
Eureka的服務(wù)端包含write server與read server集群,write server集群用于處理上面提到的客戶端注冊(cè)與注冊(cè)信息維護(hù),write server采用最終一致算法保證注冊(cè)信息在各個(gè)節(jié)點(diǎn)中的一致性。read server讀取write server中的注冊(cè)信息,并將注冊(cè)信息推送給Eureka客戶端。read server如何知道將哪些注冊(cè)信息推送給哪些客戶端呢?就是通過最開始提到的fine grain subscription模式,客戶端可以向read server發(fā)起訂閱請(qǐng)求,訂閱請(qǐng)求聲明了客戶端所需依賴的服務(wù),當(dāng)這些依賴服務(wù)有更新時(shí),read server就負(fù)責(zé)將更新信息推送給訂閱了的客戶端??偨Y(jié)一下,微服務(wù)可以通過Eureka客戶端向Eureka服務(wù)端的write server發(fā)送注冊(cè)信號(hào)讓自身可以被發(fā)現(xiàn),微服務(wù)同時(shí)也可以通過Eureka客戶端向Eureka服務(wù)端的read server發(fā)送訂閱信息來獲取依賴服務(wù)的更新信息。當(dāng)然Eureka的服務(wù)端是一個(gè)集群,Eureka客戶端還需要一個(gè)發(fā)現(xiàn)機(jī)制來發(fā)現(xiàn)集群中的服務(wù)端位置,服務(wù)端也需要一種機(jī)制來發(fā)現(xiàn)同伴位置,這里不做介紹,感興趣的同學(xué)可查閱https://github.com/Netflix/eureka
Ribbon: IPC客戶端框架 https://github.com/Netflix/ribbon
在單體應(yīng)用中獲取一種服務(wù)或者能力,只需要通過調(diào)用一個(gè)函數(shù)來實(shí)現(xiàn),一般函數(shù)對(duì)象,調(diào)用者對(duì)象,數(shù)據(jù)都在同一個(gè)進(jìn)程中,所以靠編譯器或者解釋器就可以實(shí)現(xiàn)調(diào)用。但是在微服務(wù)中,各個(gè)服務(wù)在不同的進(jìn)程中,所以微服務(wù)間的交互需要通過進(jìn)程間通訊(IPC)的方式解決。Netflix給出的方案就是Ribbon。Ribbon的基礎(chǔ)功力總結(jié)來說是三多: 多協(xié)議(HTTP、TCP、 UDP);多格式(Avro、XML、JSON、Thrift、Google Protocol Buffers);多模式(同步模式、異步模式)。這些IPC客戶端的基本的功能就不詳細(xì)介紹了。當(dāng)然Ribbon不止是一個(gè)IPC客戶端框架,更重要的是Ribbon提供了客戶端負(fù)載均衡,服務(wù)端的負(fù)載均衡非常常見,Ribbon在客戶端實(shí)現(xiàn)了一個(gè)軟件級(jí)別的負(fù)載均衡,用于從一個(gè)集群中選出服務(wù)端地址。Ribbon的客戶端負(fù)載均衡包含三個(gè)組件:Rule、Ping、ServerList,其中Rule是負(fù)載均衡的邏輯組件,Ping是服務(wù)端存活情況的探測(cè)組件,在后臺(tái)運(yùn)行,ServerList用于維護(hù)服務(wù)端集群列表,這個(gè)列表可以是靜態(tài)的也可以是動(dòng)態(tài)的。Ribbon自帶了多個(gè)負(fù)載均衡規(guī)則(Rule)可以通過配置進(jìn)行選擇,同時(shí)結(jié)合上面提到的Archaius配置管理中心可以實(shí)現(xiàn)配置的動(dòng)態(tài)更新,不但可以更新負(fù)載均衡策略還可以更新ServerList,而且更新配置動(dòng)態(tài)生效不需要重啟客戶端,這樣在客戶端就可以實(shí)現(xiàn)靈活的流量調(diào)整,所以Netflix開源的不單單是幾個(gè)框架而是一套解決方案,對(duì)計(jì)算、網(wǎng)絡(luò)以及存儲(chǔ)的抽象化是這些特性的基礎(chǔ)。

Karyon:服務(wù)端框架 https://github.com/Netflix/karyon
上面介紹了客戶端框架Ribbon,再介紹了下Netflix給出的服務(wù)端框架Karyon。我們知道對(duì)于一個(gè)服務(wù)端框架而言,其重要的設(shè)計(jì)哲學(xué)就是"don't call me, I will call you",這樣的設(shè)計(jì)將開發(fā)人員從大量有共性的邏輯中解救出來,可以專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。Karyon也踐行了這一設(shè)計(jì)哲學(xué),按照Netflix的說法Karyon是一個(gè)用于開發(fā)云端web service所需的最小系統(tǒng)(原文: blueprint),Karyon提供的特性包括:應(yīng)用初始化、運(yùn)行時(shí)診斷、配置管理、服務(wù)發(fā)現(xiàn)等等。對(duì)于一個(gè)web service而言需要在啟動(dòng)時(shí)獲取并加載配置、初始化依賴等,Karyon通過應(yīng)用初始化組件使得完成了這些初始化操作變得非常簡單,比如Karyon可以在啟動(dòng)時(shí)自動(dòng)從Archaius獲取對(duì)應(yīng)的配置并初始化。對(duì)于一個(gè)微服務(wù)生態(tài)中的web service而言,在自身啟動(dòng)后,需要可以被發(fā)現(xiàn)采用對(duì)外提供服務(wù),為此Karyon集成了服務(wù)發(fā)現(xiàn)組件,開發(fā)人員則只需要提供一個(gè)名為eureka-client.properties的配置文件,包含服務(wù)名稱、服務(wù)IP地址/端口、URL等配置,Karyon負(fù)責(zé)自動(dòng)向Eureka發(fā)起服務(wù)注冊(cè)。除此之外Karyon還有許多特性,比如服務(wù)健康檢查、管理后臺(tái)等,在這些特性的幫助下開發(fā)人員在服務(wù)端開始時(shí)所需做的就只剩下業(yè)務(wù)邏輯實(shí)現(xiàn)了。

Hystrix:斷路器組件 https://github.com/Netflix/Hystrix
斷路器是一種能在遠(yuǎn)程服務(wù)不可用時(shí)自動(dòng)斷開,并在遠(yuǎn)程服務(wù)恢復(fù)時(shí)自動(dòng)閉合的開關(guān)。斷路器對(duì)于微服務(wù)系統(tǒng)非常重要,因?yàn)槲⒎?wù)系統(tǒng)中存在著復(fù)雜的依賴關(guān)系,一旦某個(gè)服務(wù)發(fā)生延遲甚至出錯(cuò)如果不能對(duì)其進(jìn)行隔離,應(yīng)用本身也會(huì)處于被拖垮的危險(xiǎn),在高并發(fā)的環(huán)境下,由于雪崩效應(yīng)的作用,應(yīng)用資源將被耗盡造成應(yīng)用癱瘓。


對(duì)于基于云端的微服務(wù)來說,可靠性并不是有保證的,事實(shí)上即使每個(gè)服務(wù)的可靠度達(dá)到99.999%在一個(gè)包含100+微服務(wù)的系統(tǒng)中也不能保證整個(gè)系統(tǒng)可靠度達(dá)到99.99%。所以斷路器或者服務(wù)容錯(cuò)是非常必要的。Hystrix擁有多種容錯(cuò)策略,包括:阻止并發(fā)超出服務(wù)的最高限制,超過后拒絕請(qǐng)求并回退;當(dāng)限流或者斷路發(fā)生時(shí)提供多種回退方式進(jìn)行保護(hù);提供多種隔離策略比如bulkhead、swimlane、circuit breaker來保護(hù)應(yīng)用不受某一依賴故障的影響。有了Hystrix,開發(fā)人員可以在需要容錯(cuò)的環(huán)節(jié)使用HystrixCommand或者HystrixObservableCommand對(duì)象來封裝自己的調(diào)用就可以自動(dòng)的獲得容錯(cuò)能力,同時(shí)Hystrix還會(huì)為每個(gè)依賴維護(hù)一個(gè)線程池并做限流與回退,這樣做使得應(yīng)用的每個(gè)依賴互相隔離,當(dāng)故障發(fā)生時(shí)限制了應(yīng)用可以使用的資源數(shù)量(比如線程、隊(duì)列等等),并通過回退策略來決定故障發(fā)生時(shí)返回何種response。
微服務(wù)架構(gòu)還需要解決兩個(gè)重要的挑戰(zhàn):
第一個(gè)挑戰(zhàn)就是如何實(shí)現(xiàn)業(yè)務(wù)事務(wù),保持多個(gè)服務(wù)的一致性。第二個(gè)挑戰(zhàn)就是如何從多個(gè)服務(wù)中檢索數(shù)據(jù),實(shí)現(xiàn)查詢。
(引用自《Service Discovery in a Microservices Architecture》,作者:Chris Richardson,譯者:DaoCloud Lychee Li)
第一個(gè)挑戰(zhàn)說明微服務(wù)應(yīng)用在保持事務(wù)原子性上不再像單體應(yīng)用那樣簡單,因?yàn)槲⒎?wù)之間是松散的。舉個(gè)簡單的例子,兩個(gè)微服務(wù)都要需要一項(xiàng)數(shù)據(jù)(比如客戶ID),但是這兩個(gè)微服務(wù)由兩個(gè)團(tuán)隊(duì)獨(dú)立開發(fā),使用的開發(fā)語言不一樣,連使用的數(shù)據(jù)庫類型都不一樣,有些微服務(wù)使用了NoSQL的數(shù)據(jù)庫。那么如何保持?jǐn)?shù)據(jù)的一致性就是我們面臨的第一個(gè)挑戰(zhàn)。
第二個(gè)挑戰(zhàn)引申自第一個(gè)挑戰(zhàn),我們?nèi)绾螐亩鄠€(gè)服務(wù)中查詢數(shù)據(jù)?對(duì)于單體應(yīng)用而言所有的數(shù)據(jù)都在一個(gè)數(shù)據(jù)庫中,并且數(shù)據(jù)是結(jié)構(gòu)化的(表就是數(shù)據(jù)的結(jié)構(gòu)化表示),我們很容易從多個(gè)表中查詢數(shù)據(jù),但是在整個(gè)微服務(wù)系統(tǒng)中數(shù)據(jù)變得沒有結(jié)構(gòu)了,數(shù)據(jù)是分散的,當(dāng)然我們可以通過API的方式從各個(gè)服務(wù)中獲取數(shù)據(jù)并拼裝成結(jié)構(gòu)化數(shù)據(jù),但這樣做效率太低了,微服務(wù)將變成慢服務(wù),有沒有更好的方式呢?事實(shí)上我們可以參照單體應(yīng)用的思路,單體應(yīng)用將數(shù)據(jù)結(jié)構(gòu)化,在微服務(wù)系統(tǒng)中我們可以將多個(gè)微服務(wù)結(jié)構(gòu)化,而事件就是這個(gè)結(jié)構(gòu)的“主鍵”。對(duì)于這兩個(gè)挑戰(zhàn)更詳細(xì)的解決方式可以參考:Event-Driven Data Management for Microservices,作者Chris Richardson在文章中介紹了如何采用事件驅(qū)動(dòng)的架構(gòu)來應(yīng)對(duì)挑戰(zhàn)。
后話:
上面介紹的開源框架已經(jīng)可以覆蓋微服務(wù)實(shí)施過程中需要的大部分基礎(chǔ)框架,當(dāng)然還有一些框架也是不可或缺的,感興趣的同學(xué)可以自己查閱,比如:
Zuul: 網(wǎng)關(guān)服務(wù)https://github.com/Netflix/zuul
Blitz4j:日志組件? https://github.com/Netflix/blitz4j
SimianArmy:猴子軍團(tuán)?https://github.com/Netflix/SimianArmy
這不是一個(gè)必須的組件但是猴子軍團(tuán)改變了我對(duì)于應(yīng)用可靠性的認(rèn)識(shí),通常我們認(rèn)為應(yīng)用要可靠運(yùn)行需要為應(yīng)用創(chuàng)造一個(gè)穩(wěn)定的運(yùn)行環(huán)境,但事實(shí)上這樣做并不能保證可靠性,故障或者災(zāi)難一般發(fā)生在我們難以預(yù)料的情況下,做好萬全的準(zhǔn)備事實(shí)上被證明是不可行的,所以對(duì)抗風(fēng)險(xiǎn)的辦法是反過來歡迎風(fēng)險(xiǎn),猴子軍團(tuán)可以在生產(chǎn)環(huán)境的制造不同種類的故障,評(píng)估在不正常狀態(tài)下系統(tǒng)的應(yīng)對(duì)能力發(fā)現(xiàn)弱點(diǎn),為持續(xù)改進(jìn)我們的應(yīng)用提供數(shù)據(jù)支撐。打不死你的只會(huì)讓你更強(qiáng)!
Servo: Java的Metrics組件 https://github.com/Netflix/servo