全網(wǎng)首發(fā):逐一解讀云原生應(yīng)用開發(fā)“12-Factors”

本文節(jié)選自普元信息即將出版的《微服務(wù)企業(yè)架構(gòu)最佳實(shí)踐》一書,本文作者普元云計(jì)算架構(gòu)師宋瀟男,為該書的合著者之一。轉(zhuǎn)載本文需注明出處:微信公眾號(hào)EAWorld,違者必究。

作者自序:

12原則的提出已有五年之久,可惜業(yè)界一直缺乏一篇對(duì)其進(jìn)行簡(jiǎn)明解讀的指導(dǎo)性文章,所以我決定寫這樣一篇文章。在微服務(wù)模式的大背景下,力求對(duì)12原則的來龍去脈做出明確和完備的解釋,并對(duì)12原則原文的含糊之處做出澄清。如果各位讀者對(duì)本書的其他內(nèi)容感興趣,那么也敬請(qǐng)關(guān)注我們的公眾號(hào)eaworld。

12-Factors經(jīng)常被直譯為12要素,也被稱為12原則,12原則由公有云PaaS的先驅(qū)Heroku于2012年提出(原文參見12factor.net),目的是告訴開發(fā)者如何利用云平臺(tái)提供的便利來開發(fā)更具可靠性和擴(kuò)展性、更加易于維護(hù)的云原生應(yīng)用。距離12原則的提出已有五年之久,12原則的有些細(xì)節(jié)可能已經(jīng)不那么跟得上時(shí)代,也有人批評(píng)12原則的提出從一開始就有過于依賴Heroku自身特性的傾向。不過不管怎么說,12原則依舊是業(yè)界最為系統(tǒng)的云原生應(yīng)用開發(fā)指南,我們可以把它作為一個(gè)非常有力的參考,但是也千萬(wàn)不要教條。

原則1:一份基準(zhǔn)代碼,多份部署

這個(gè)原則不管對(duì)微服務(wù)模式還是其他軟件開發(fā)模式來說都非常基本,所以被列為12原則的第一條,該原則包括如下四個(gè)子原則

  1. 使用代碼庫(kù)管理代碼,一般是Git或者SVN,這個(gè)要求非常初級(jí),相信本書的讀者都會(huì)遵守。

  2. 一份基準(zhǔn)代碼(即一個(gè)代碼庫(kù))對(duì)應(yīng)一個(gè)應(yīng)用。如果通過一份基準(zhǔn)代碼可以編譯出多個(gè)應(yīng)用,那么應(yīng)該考慮將該基準(zhǔn)代碼按應(yīng)用拆分為多份;如果一個(gè)應(yīng)用需要多份基準(zhǔn)代碼,那么要么考慮將多份基準(zhǔn)代碼合并,要么考慮將該應(yīng)用按基準(zhǔn)代碼拆分為多個(gè)。

  3. 不允許多個(gè)應(yīng)用共享一份基準(zhǔn)代碼,如果確實(shí)需要共享,那就把需要共享的基準(zhǔn)代碼的穩(wěn)定版本發(fā)布為類庫(kù),然后通過依賴管理策略進(jìn)行加載。

  4. 同一應(yīng)用的多份部署可以使用同一份基準(zhǔn)代碼的不同版本,但是不可以使用不同的基準(zhǔn)代碼,類似原則2,使用不同基準(zhǔn)代碼的應(yīng)用不應(yīng)被視為同一應(yīng)用。

違反子原則2和3,會(huì)給代碼管理和編譯工作帶來麻煩:

  1. 如果一份基準(zhǔn)代碼可以編譯出多個(gè)應(yīng)用,那么這幾個(gè)應(yīng)用之間必然會(huì)存在不清晰的依賴關(guān)系,隨著時(shí)間的推移,這種依賴關(guān)系會(huì)變得愈加混亂,以至于修改一個(gè)應(yīng)用的代碼,會(huì)給其他應(yīng)用帶來不可預(yù)知的影響。這樣的基準(zhǔn)代碼顯然極難維護(hù)。

  2. 基準(zhǔn)代碼的劃分和應(yīng)用的劃分非常類似,也是系統(tǒng)邊界的一種體現(xiàn),如果一個(gè)應(yīng)用需要從多份基準(zhǔn)代碼編譯,那么多數(shù)情況下這個(gè)應(yīng)用的內(nèi)外部邊界問題會(huì)存在問題。如果邊界不存在問題,那么請(qǐng)將多份基準(zhǔn)代碼合并為一份,而不是維持這種古怪的設(shè)計(jì)。

  3. 如果多個(gè)應(yīng)用不是通過類庫(kù),而是直接共享一份基準(zhǔn)代碼,那么這份被共享的基準(zhǔn)代碼會(huì)很難維護(hù),對(duì)這份基準(zhǔn)代碼的修改必須謹(jǐn)慎考慮對(duì)多個(gè)應(yīng)用可能造成的影響。正確的方式是將這份基準(zhǔn)代碼發(fā)布為類庫(kù),保持清晰的邊界和接口約定供其它應(yīng)用調(diào)用。

原則2:顯式聲明依賴關(guān)系

這里的依賴指所有的依賴,包括應(yīng)用程序本身的類庫(kù)和操作系統(tǒng)層面被應(yīng)用程序所使用的庫(kù)文件或者其他二進(jìn)制文件,都必須進(jìn)行顯示聲明,并對(duì)版本做出明確的指定。不要假定運(yùn)行環(huán)境中已經(jīng)存在應(yīng)用所需要的任何依賴項(xiàng),而是應(yīng)該假定什么都沒有(即使有也很可能不是應(yīng)用所需要的版本)。

如果使用容器方式進(jìn)行部署,容器的基礎(chǔ)鏡像很可能是Busybox或者Alpine之類的迷你Linux,那么就幾乎等于什么都沒有。如果使用微服務(wù)模式,理想情況下,微服務(wù)之間的依賴關(guān)系也應(yīng)該進(jìn)行顯示聲明。

以前我們往往不會(huì)對(duì)依賴做如此嚴(yán)格的管理,因?yàn)閼?yīng)用不會(huì)有太大規(guī)模的部署,也不會(huì)進(jìn)行頻繁的發(fā)布,如果發(fā)現(xiàn)運(yùn)行環(huán)境里缺少某些依賴,那么就臨時(shí)手工處理一下,也不是什么太大的問題。如今在微服務(wù)模式下,應(yīng)用的部署規(guī)模大、發(fā)布頻率高,還記得前文所說的“不可變服務(wù)器”嗎?如果這個(gè)時(shí)候還是使用原有的模式,則會(huì)帶來混亂。

聲明依賴的方式有很多,常見的方式是使用依賴清單,根據(jù)依賴清單進(jìn)行依賴檢查,同時(shí)使用依賴隔離工具保證應(yīng)用不會(huì)調(diào)用系統(tǒng)中存在但是依賴清單中未聲明的依賴項(xiàng);另一種方式是使用容器技術(shù),將應(yīng)用和依賴打包為容器鏡像,依賴的聲明和隔離就一并解決了。

原則3:在環(huán)境中存儲(chǔ)配置

首先需要明確的是,這里的配置指與部署環(huán)境有關(guān)的配置,例如:

  • 數(shù)據(jù)庫(kù)、消息代理、緩存系統(tǒng)等后端服務(wù)的連接配置和位置信息,如URL、用戶名、密碼等。

  • 第三方服務(wù)的證書。

  • 每份部署獨(dú)有的配置,例如:域名、連接數(shù)、與部署目標(biāo)環(huán)境資源規(guī)模有關(guān)的JVM參數(shù)等。

所有部署中都相同的信息,例如原則2里講到的依賴信息,不在本原則所討論的范圍內(nèi)。一些雖然在不同的部署中有所差異、但是和業(yè)務(wù)相關(guān)的信息,例如資金結(jié)算的轉(zhuǎn)換比例,也不屬于本原則所討論的配置。

我想大多數(shù)的開發(fā)者都知道如何通過使用配置文件實(shí)現(xiàn)配置和代碼的分離,但是這種方式仍然存在一些缺點(diǎn),例如:

  1. 配置文件容易被開發(fā)人員不小心提交到代碼庫(kù)中,造成密碼、證書等敏感信息泄露。提交到代碼庫(kù)中的配置文件還容易被和應(yīng)用一起部署到目標(biāo)環(huán)境中,很可能會(huì)導(dǎo)致在目標(biāo)環(huán)境中應(yīng)用了錯(cuò)誤的配置或者造成配置沖突。

  2. 配置文件會(huì)分散在不同的目錄中,并且有不同的格式(配置文件的格式往往與開發(fā)語(yǔ)言和框架相關(guān)),這會(huì)給配置的統(tǒng)一管理造成困難。

為了避免上述問題,本原則要求將在環(huán)境中存儲(chǔ)配置。一種典型的方式是把配置存儲(chǔ)在環(huán)境變量中,這會(huì)使配置和代碼徹底的分離,格式上也與開發(fā)語(yǔ)言和框架再無瓜葛,并且也不會(huì)被誤提交到代碼庫(kù)中。還可以使用Spring Cloud Config Server這類配置管理服務(wù)進(jìn)行配置推送,并將配置的歷史版本和變更原因也一起管理起來。

原則4:把后端服務(wù)當(dāng)作附加資源

這里的后端服務(wù)指的是應(yīng)用運(yùn)行所依賴的各種服務(wù),例如數(shù)據(jù)庫(kù)、消息代理、緩存系統(tǒng)等,對(duì)于云原生應(yīng)用來說,往往還會(huì)有日志收集服務(wù)、對(duì)象存儲(chǔ)服務(wù)、以及各種通過API訪問的服務(wù);當(dāng)作附加資源指的是把這些服務(wù)作為外部的、通過網(wǎng)絡(luò)調(diào)用的資源。

該原則有如下幾層含義:

  1. 不要將這些服務(wù)放在應(yīng)用本地:云原生應(yīng)用要求應(yīng)用本身無狀態(tài)化,那么狀態(tài)信息就應(yīng)該存儲(chǔ)在外部服務(wù)中(參見不可變服務(wù)器)。同時(shí),微服務(wù)模式要求應(yīng)用責(zé)權(quán)單一以實(shí)現(xiàn)可靠性和擴(kuò)展性,如果在應(yīng)用本地放置數(shù)據(jù)庫(kù),那么微服務(wù)平臺(tái)將無法通過更換應(yīng)用的故障實(shí)例實(shí)現(xiàn)應(yīng)用的高可用性,也無法通過自動(dòng)化的橫向伸縮實(shí)現(xiàn)擴(kuò)展性,因?yàn)閼?yīng)用實(shí)例內(nèi)包含兩種性質(zhì)完全不同的軟件(應(yīng)用和數(shù)據(jù)庫(kù)),無法對(duì)兩者使用同一種方式進(jìn)行橫向擴(kuò)展。另外,如果將這些服務(wù)放在應(yīng)用本地,那么也無法通過充分利用云平臺(tái)提供的能力簡(jiǎn)化運(yùn)維工作,例如,如果在應(yīng)用本地放置數(shù)據(jù)庫(kù),而不是使用云平臺(tái)提供的數(shù)據(jù)庫(kù)服務(wù),那么顯然無法利用數(shù)據(jù)庫(kù)服務(wù)提供的自動(dòng)備份、安全、和高可用等特性。

  2. 通過URL或者服務(wù)注冊(cè)/認(rèn)證中心訪問這些后端服務(wù):應(yīng)用應(yīng)該能夠在不進(jìn)行任何代碼修改的情況下,在不同的目標(biāo)環(huán)境中進(jìn)行部署,應(yīng)用不應(yīng)該和后端服務(wù)的任何一種具體實(shí)現(xiàn)存在緊耦合關(guān)系。

  3. 類似“顯式聲明依賴關(guān)系”原則,應(yīng)用最好也能夠?qū)ζ涫褂玫倪@些后端服務(wù)進(jìn)行顯示聲明,以方便云平臺(tái)對(duì)服務(wù)資源進(jìn)行自動(dòng)綁定,在后端服務(wù)出現(xiàn)故障的時(shí)候,云平臺(tái)也能夠?qū)ζ溥M(jìn)行自動(dòng)恢復(fù)。

原則5:嚴(yán)格分離構(gòu)建、發(fā)布和運(yùn)行

在本原則中,構(gòu)建、發(fā)布和運(yùn)行這三個(gè)概念可能和從前有所不同,因此有必要首先對(duì)其進(jìn)行明確:

  • 構(gòu)建指的是將應(yīng)用代碼轉(zhuǎn)化為執(zhí)行體的過程:構(gòu)建時(shí)會(huì)拉取特定版本的代碼和依賴項(xiàng),將其編譯為二進(jìn)制文件(針對(duì)編譯型語(yǔ)言),并和資源文件一起打包。

  • 發(fā)布指的是將構(gòu)建的結(jié)果和部署所需的配置相結(jié)合,并將其放置于運(yùn)行環(huán)境之中。

  • 運(yùn)行指的是將發(fā)布的結(jié)果啟動(dòng)為運(yùn)行環(huán)境中的一個(gè)或多個(gè)進(jìn)程。

本原則要求構(gòu)建、發(fā)布和運(yùn)行這三個(gè)步驟嚴(yán)格區(qū)分:

  1. 禁止直接修改運(yùn)行狀態(tài)的代碼或者對(duì)應(yīng)用進(jìn)行打補(bǔ)丁,因?yàn)檫@些修改很難再同步回構(gòu)建步驟,這時(shí)運(yùn)行狀態(tài)的代碼就成為了“孤本”。同時(shí),也不應(yīng)該在運(yùn)行期間修改應(yīng)用的配置,配置的修改應(yīng)該僅限于發(fā)布階段(參見不可變服務(wù)器)。

  2. 運(yùn)行這一步驟應(yīng)該非常簡(jiǎn)單,僅限于啟動(dòng)進(jìn)程,資源文件的關(guān)聯(lián)應(yīng)僅限于構(gòu)建階段,配置的結(jié)合應(yīng)僅限于發(fā)布階段。

同時(shí),每一次發(fā)布都應(yīng)該對(duì)應(yīng)一個(gè)唯一的發(fā)布ID,發(fā)布的版本應(yīng)當(dāng)像一個(gè)只能追加的賬本,一旦發(fā)布就不能修改。這么做的好處是:

  1. 每一份運(yùn)行狀態(tài)的代碼都可以在對(duì)應(yīng)的發(fā)布和構(gòu)建階段找到它的來源,這是實(shí)現(xiàn)重新發(fā)布、故障實(shí)例的自動(dòng)替換、發(fā)布出錯(cuò)后的版本回退等機(jī)制的基礎(chǔ)。

  2. 運(yùn)行步驟非常簡(jiǎn)單,這樣在硬件重啟、實(shí)例故障和橫向擴(kuò)展等情況下,應(yīng)用可以簡(jiǎn)單和快速的實(shí)現(xiàn)重啟。

原則6:以一個(gè)或多個(gè)無狀態(tài)的進(jìn)程

運(yùn)行應(yīng)用

本原則要求應(yīng)用進(jìn)程的內(nèi)部不要保存狀態(tài)信息,任何狀態(tài)信息都應(yīng)該被保存在數(shù)據(jù)庫(kù)、緩存系統(tǒng)等外部服務(wù)中。應(yīng)用實(shí)例之間的數(shù)據(jù)共享也要通過數(shù)據(jù)庫(kù)和緩存系統(tǒng)等外部服務(wù)進(jìn)行,直接的數(shù)據(jù)共享不但違反無狀態(tài)原則,還引入了串行化的單點(diǎn),這會(huì)為應(yīng)用的橫向擴(kuò)展帶來障礙。

在微服務(wù)模式下,應(yīng)用不應(yīng)該在自身進(jìn)程內(nèi)部緩存數(shù)據(jù)以供將來的請(qǐng)求使用,因?yàn)槲⒎?wù)模式以多實(shí)例方式運(yùn)行應(yīng)用,將來的請(qǐng)求多半會(huì)被路由到其他實(shí)例,此時(shí)雖然可以使用粘滯會(huì)話將請(qǐng)求保持在同一個(gè)實(shí)例上,但是無論是云原生應(yīng)用還是微服務(wù)模式都極力反對(duì)使用粘滯會(huì)話,原因如下:

  1. 很難對(duì)粘滯會(huì)話實(shí)現(xiàn)負(fù)載均衡,因?yàn)檎硿?huì)話的均衡性不僅決定于負(fù)載均衡策略,還和會(huì)話本身的行為相關(guān),例如,可能存在應(yīng)用某些實(shí)例上的會(huì)話已經(jīng)大量退出,而另一些實(shí)例上的會(huì)話依然處于活動(dòng)狀態(tài),此時(shí)這兩部分實(shí)例的負(fù)載處于不均衡狀態(tài),而負(fù)載均衡器無法將活動(dòng)會(huì)話轉(zhuǎn)移到空閑的應(yīng)用實(shí)例。

  2. 啟動(dòng)新的應(yīng)用實(shí)例不會(huì)立即提高應(yīng)用的整體處理能力,因?yàn)檫@些新實(shí)例只能承接新會(huì)話,舊的會(huì)話依舊粘滯在舊的應(yīng)用實(shí)例上。

  3. 應(yīng)用實(shí)例退出會(huì)導(dǎo)致會(huì)話丟失,所以在實(shí)例發(fā)生故障時(shí),即使云平臺(tái)可以對(duì)故障實(shí)例進(jìn)行自動(dòng)替換,也會(huì)導(dǎo)致用戶數(shù)據(jù)丟失。即使是對(duì)應(yīng)用實(shí)例進(jìn)行人工維護(hù),也需要在維護(hù)之前對(duì)該實(shí)例上的會(huì)話進(jìn)行轉(zhuǎn)移,這往往意味著需要編寫復(fù)雜的業(yè)務(wù)代碼。

    在傳統(tǒng)模式下,可以通過在雙機(jī)之間進(jìn)行會(huì)話復(fù)制來實(shí)現(xiàn)對(duì)用戶無感知的單機(jī)下線維護(hù)(雖然會(huì)付出處理能力減半的代價(jià)),但是在微服務(wù)模式下,應(yīng)用的實(shí)例數(shù)量往往遠(yuǎn)不止兩個(gè),在大量的實(shí)例之間進(jìn)行會(huì)話復(fù)制會(huì)使實(shí)例之間原本非常簡(jiǎn)單的邏輯關(guān)系復(fù)雜化,此時(shí)將無法通過云平臺(tái)對(duì)其進(jìn)行無差別的自動(dòng)化維護(hù)。另外,在實(shí)例之間進(jìn)行會(huì)話復(fù)制也意味著實(shí)例之間存在著直接的數(shù)據(jù)共享,這會(huì)為應(yīng)用的橫向擴(kuò)展帶來障礙。

所以,粘滯會(huì)話是應(yīng)用實(shí)現(xiàn)可用性和擴(kuò)展性的重要障礙,使用粘滯會(huì)話顯然是種得不償失的選擇。更好的實(shí)現(xiàn)方式是將會(huì)話信息存儲(chǔ)在緩存服務(wù)中。

原則7:通過端口綁定提供服務(wù)

服務(wù)端應(yīng)用通過網(wǎng)絡(luò)端口提供服務(wù),這點(diǎn)毋庸置疑,但是本原則還有如下兩個(gè)深層次的含義:

  1. 無論是云原生應(yīng)用還是微服務(wù)模式都要求應(yīng)用應(yīng)該完全自我包含,而不是依賴于外部的應(yīng)用服務(wù)器,端口綁定指的是應(yīng)用直接與端口綁定,而不是通過應(yīng)用服務(wù)器進(jìn)行端口綁定。

    如果一定要使用應(yīng)用服務(wù)器,那就使用嵌入式應(yīng)用服務(wù)器,無論是云原生應(yīng)用還是微服務(wù)模式都極力反對(duì)將多個(gè)應(yīng)用放置于同一個(gè)應(yīng)用服務(wù)器上運(yùn)行,因?yàn)樵谶@種模式下,一個(gè)應(yīng)用出錯(cuò)會(huì)對(duì)同一個(gè)應(yīng)用服務(wù)器上的其他應(yīng)用造成影響,也無法針對(duì)單一應(yīng)用做橫向擴(kuò)展。

  2. 端口綁定工作應(yīng)該由云平臺(tái)自動(dòng)進(jìn)行,云平臺(tái)在實(shí)現(xiàn)應(yīng)用到端口的綁定之外,還需要實(shí)現(xiàn)內(nèi)部端口到外部端口的映射和外部端口到域名的映射。在應(yīng)用的整個(gè)生命周期內(nèi),應(yīng)用實(shí)例會(huì)經(jīng)歷多次的重新部署、重啟或者橫向擴(kuò)展,端口會(huì)發(fā)生變化,但URL會(huì)保持不變。

原則8:通過進(jìn)程模型進(jìn)行擴(kuò)展

與通過進(jìn)程模型進(jìn)行擴(kuò)展相反的方式是通過線程模型進(jìn)行擴(kuò)展,這是一種相對(duì)較為傳統(tǒng)的方式,典型的例子是Java應(yīng)用。當(dāng)我們啟動(dòng)一個(gè)Java進(jìn)程的時(shí)候,通常會(huì)通過JVM參數(shù)為其設(shè)置各個(gè)內(nèi)存區(qū)域的容量上下限,同時(shí)還可能會(huì)在應(yīng)用層面為其設(shè)置一個(gè)或者多個(gè)線程池的容量上下限,當(dāng)外部負(fù)載變化時(shí),進(jìn)程所占用的內(nèi)存容量和進(jìn)程內(nèi)部的線程數(shù)量可以在這些預(yù)先設(shè)置好的上下限之間進(jìn)行擴(kuò)展,這種方式也被稱為縱向擴(kuò)展或者垂直擴(kuò)展。

但是這種方式存在一些問題,首先,在進(jìn)程的內(nèi)存容量和線程數(shù)量提高時(shí),應(yīng)用的某些性能指標(biāo)可能不會(huì)得到同步提高,甚至可能會(huì)下降(這往往是因?yàn)槌绦驅(qū)δ承o法擴(kuò)展的資源進(jìn)行爭(zhēng)用所造成的),這種參差不齊的性能擴(kuò)展對(duì)外部負(fù)載提高的承接能力會(huì)很不理想,有時(shí)甚至?xí)m得其反;

其次,為了使進(jìn)程本身可以完成縱向擴(kuò)展,還需要在虛擬機(jī)層面或者容器層面為其預(yù)留內(nèi)存資源和對(duì)應(yīng)的CPU資源,這會(huì)造成大量的資源浪費(fèi)(當(dāng)然,也可以使虛擬機(jī)或者容器跟隨進(jìn)程一起進(jìn)行縱向擴(kuò)展,這在技術(shù)上是可行的,但是會(huì)為虛擬機(jī)或者容器管理平臺(tái)的資源調(diào)度造成一些不必要的困難,例如頻繁的虛擬機(jī)遷移或者容器重啟)。

所以,現(xiàn)在更為推崇使用“固定的”進(jìn)程(對(duì)前面Java應(yīng)用的例子來說,就是固定的內(nèi)存容量和線程池容量),在外部負(fù)載提高時(shí),啟動(dòng)更多的進(jìn)程,在外部負(fù)載降低時(shí),停止一部分進(jìn)程,這種方式就是本原則所說的通過進(jìn)程模型進(jìn)行擴(kuò)展,有時(shí)候也被稱為橫向擴(kuò)展或者水平擴(kuò)展。

這種擴(kuò)展方式的好處是,在進(jìn)程數(shù)量增加的時(shí)候,應(yīng)用的各種性能指標(biāo)會(huì)得到同步的提高,這種提高即使不是線性的,也會(huì)按照一種平滑和可預(yù)期的曲線展開,可以更為穩(wěn)定的應(yīng)對(duì)外部負(fù)載的變化。

云原生應(yīng)用和微服務(wù)模式極力推崇將通過進(jìn)程模型進(jìn)行擴(kuò)展作為唯一的擴(kuò)展方式,除了前文所述,還有一個(gè)原因是進(jìn)程是云平臺(tái)可以操作的最小運(yùn)行單元(當(dāng)然,可以通過其他技術(shù)手段去操作線程,但是那不會(huì)成為云平臺(tái)的通用技術(shù)特性),云平臺(tái)可以根據(jù)各個(gè)層面的監(jiān)控?cái)?shù)據(jù),通過預(yù)設(shè)規(guī)則決定是否為應(yīng)用增加或者減少進(jìn)程,例如,當(dāng)前端的負(fù)載均衡器檢測(cè)到訪問某個(gè)后端應(yīng)用的并發(fā)用戶數(shù)超過某個(gè)閾值時(shí),可以立即為這個(gè)后端應(yīng)用啟動(dòng)更多的進(jìn)程,以承接更大的負(fù)載,同時(shí)還可以選擇是否對(duì)該應(yīng)用后端的數(shù)據(jù)庫(kù)進(jìn)行擴(kuò)展。

如果此時(shí)選擇對(duì)應(yīng)用進(jìn)行縱向擴(kuò)展,則云平臺(tái)既不知道應(yīng)用處理能力的變化,也無法對(duì)這種變化進(jìn)行預(yù)期管理,更無法使應(yīng)用的前后端對(duì)這種變化進(jìn)行聯(lián)動(dòng),即該應(yīng)用的擴(kuò)展行為脫離了云平臺(tái)的管理。在微服務(wù)模式下,如果大量的進(jìn)程都采用縱向擴(kuò)展方式,則會(huì)為平臺(tái)的資源調(diào)度帶來極大的混亂。

注3:該原則似乎更適合被稱為橫向擴(kuò)展原則,但是為了和12原則的原文保持一直,這里我們?nèi)匀粚⑵浞Q為“通過進(jìn)程模型進(jìn)行擴(kuò)展”。

原則9:快速啟動(dòng)和優(yōu)雅終止

可最大化健壯性

該原則要求應(yīng)用可以瞬間(理想情況下是數(shù)秒或者更短)啟動(dòng)和停止,因?yàn)檫@將有利于應(yīng)用快速進(jìn)行橫向擴(kuò)展和變更或者故障后的重新部署,而這兩者都是程序健壯性的體現(xiàn)。

前文不止一次提到過應(yīng)用的快速啟動(dòng),在理念章節(jié)的開頭,我們提到過平價(jià)的進(jìn)程生成對(duì)多道程序設(shè)計(jì)至關(guān)重要,而微服務(wù)模式在某種程度上可以認(rèn)為是多道程序設(shè)計(jì)在Web領(lǐng)域和分布式系統(tǒng)下的進(jìn)一步擴(kuò)展,這里所說的平價(jià)進(jìn)程生成指的是操作系統(tǒng)的一種特性,是應(yīng)用快速啟動(dòng)的基礎(chǔ),除此之外為了保證應(yīng)用可以在數(shù)秒內(nèi)完成啟動(dòng),還需要大量的優(yōu)化工作,需要開發(fā)人員掌握復(fù)雜的調(diào)優(yōu)技術(shù)與工具,有些工作必須在應(yīng)用的初始設(shè)計(jì)階段完成,例如:如果應(yīng)用體積過大或者是引用了太多的庫(kù)文件,那么再多的后期優(yōu)化也無法將啟動(dòng)時(shí)間降低到數(shù)秒以內(nèi)。

“原則5:嚴(yán)格分離構(gòu)建、發(fā)布和運(yùn)行”中我們還提到,應(yīng)用的運(yùn)行步驟應(yīng)該非常簡(jiǎn)單,這里的“簡(jiǎn)單”也隱含著快速的意思,目的是為了在硬件重啟、實(shí)例故障和橫向擴(kuò)展等情況下,應(yīng)用可以快速的實(shí)現(xiàn)重啟。除此之外,“原則6:以一個(gè)或多個(gè)無狀態(tài)的進(jìn)程運(yùn)行應(yīng)用”也與應(yīng)用的快速啟動(dòng)有關(guān),遵守?zé)o狀態(tài)原則,使用云平臺(tái)提供的緩存服務(wù),而不是在應(yīng)用內(nèi)部加載緩存,可以避免在應(yīng)用啟動(dòng)期間進(jìn)行耗時(shí)的緩存預(yù)熱。

比起應(yīng)用的快速啟動(dòng),優(yōu)雅終止(Graceful Shutdown)需要考慮的問題會(huì)更為廣泛一些。優(yōu)雅終止需要盡可能降低應(yīng)用終止對(duì)用戶造成的不良影響(對(duì)于微服務(wù)應(yīng)用,用戶可能是人,也可能是其他微服務(wù))。

對(duì)于短任務(wù)來說,這一般意味著拒絕所有新的請(qǐng)求,并將已經(jīng)接收的請(qǐng)求處理完畢后再終止;對(duì)于長(zhǎng)任務(wù)來說,這一般意味著應(yīng)用重啟后的客戶端重連和為任務(wù)設(shè)置斷點(diǎn)并在重啟后繼續(xù)執(zhí)行。除此之外,優(yōu)雅終止還需要釋放所有被進(jìn)程鎖定的資源,并對(duì)事務(wù)的完整性和操作的冪等性做出完備的考慮。

最后,應(yīng)用還必須應(yīng)對(duì)突如其來的退出,在硬件出現(xiàn)故障時(shí)或者進(jìn)程崩潰時(shí),應(yīng)用需要保證不會(huì)對(duì)其使用的數(shù)據(jù)造成損壞,遵守?zé)o狀態(tài)原則、將數(shù)據(jù)交由后端服務(wù)處理的應(yīng)用可以很容易的將應(yīng)對(duì)突然退出的復(fù)雜度外部化。

原則10:開發(fā)環(huán)境與線上環(huán)境等價(jià)

本原則的淺層次含義是要求在開發(fā)環(huán)境和線上環(huán)境中使用相同的軟件棧,并盡可能為這些軟件棧使用相同的配置,以避免“It works on my machine.”這類問題。本原則反對(duì)在不同的環(huán)境中使用不同的后端服務(wù),雖然可以使用適配器或者在代碼中做出兼容性考慮以消除后端服務(wù)的差異,但是這將牽扯開發(fā)人員和測(cè)試人員大量的精力以保證這些適配器和代碼確實(shí)可以按預(yù)期工作,在應(yīng)用的整個(gè)開發(fā)周期中,這將積累極大的額外工作量,是一種非常不必要的資源浪費(fèi)。

近年來個(gè)人電腦的性能大幅提高,開發(fā)人員一度得以在本地開發(fā)環(huán)境中運(yùn)行與生產(chǎn)環(huán)境中一致的軟件棧,而不是像曾經(jīng)那樣采用輕量的替代方案。但是隨著云原生應(yīng)用和微服務(wù)模式的流行,情況又發(fā)生了微妙的變化:開發(fā)微服務(wù)時(shí)需要依賴云平臺(tái)提供的基礎(chǔ)服務(wù)和其他微服務(wù),越來越難以把這些服務(wù)完整的運(yùn)行在本地,與此同時(shí),完全的在線開發(fā)愈發(fā)成為一種趨勢(shì),那樣的話至少在軟件棧上開發(fā)環(huán)境和線上環(huán)境就真的沒有任何區(qū)別了。

在我編寫這段文字的時(shí)候,Red Hat公司剛好在洽購(gòu)在線開發(fā)環(huán)境創(chuàng)業(yè)公司Codenvy用以充實(shí)他們的云平臺(tái)產(chǎn)品OpenShift,而另一家與Codenvy類似的創(chuàng)業(yè)公司Cloud9在差不多一年前被Amazon公司收購(gòu)。

本原則的深層次含義是盡量縮小開發(fā)環(huán)境和線上環(huán)境中時(shí)間和人員的差異。開發(fā)環(huán)境中的代碼每天都在更新,而這些更新往往會(huì)累積數(shù)周甚至數(shù)月才會(huì)被發(fā)布到線上環(huán)境,這是開發(fā)環(huán)境和線上環(huán)境在時(shí)間上的巨大差異;開發(fā)人員只關(guān)心開發(fā)環(huán)境,運(yùn)維人員只關(guān)心線上環(huán)境,開發(fā)人員和運(yùn)維人員在工作上鮮有交集,這是開發(fā)環(huán)境和線上環(huán)境在人員上的巨大差異。

對(duì)于前一個(gè)差異,本原則要求更為密集和頻繁的向線上環(huán)境發(fā)布更新,要求建立機(jī)制以保障開發(fā)人員可以在數(shù)小時(shí)甚至數(shù)分鐘內(nèi)既可將更新發(fā)布到線上,這也正是本章理念部分中持續(xù)交付所提倡的;對(duì)于后一個(gè)差異,本原則要求開發(fā)人員不能只關(guān)心開發(fā)環(huán)境中自己的代碼,更要密切關(guān)注代碼的部署過程和代碼在線上的運(yùn)行情況,這也正是DevOps所提倡的。

原則11:把日志當(dāng)作事件流

應(yīng)用程序應(yīng)該將其產(chǎn)生的事件以每個(gè)事件一行的格式按時(shí)間順序輸出,這點(diǎn)毋庸置疑,但是本原則想說的其實(shí)是:應(yīng)用程序不要自行管理日志文件。

以前我們習(xí)慣將應(yīng)用程序產(chǎn)生的事件分門別類的輸出到不同的日志文件,并為每個(gè)日志文件指定在本地文件系統(tǒng)上的存儲(chǔ)位置,為了避免單一日志文件過大,還會(huì)為它們配置輪轉(zhuǎn)策略。

該原則極力反對(duì)上述做法,而是要求應(yīng)用程序?qū)⑷罩疽允录鞯姆绞捷敵龅綐?biāo)準(zhǔn)輸出STDOUT和標(biāo)準(zhǔn)錯(cuò)誤輸出STDERR,然后由運(yùn)行環(huán)境捕獲這些事件流,并轉(zhuǎn)發(fā)到專門的日志處理服務(wù)進(jìn)行處理。這樣做的原因是:

1.?“原則6:以一個(gè)或多個(gè)無狀態(tài)的進(jìn)程運(yùn)行應(yīng)用”要求應(yīng)用程序無狀態(tài),那么應(yīng)用程序就不應(yīng)該將日志文件這種價(jià)值信息存儲(chǔ)在本地文件系統(tǒng)上。當(dāng)然,可以在本地運(yùn)行一個(gè)日志收集進(jìn)程讀取日志文件,并將其轉(zhuǎn)發(fā)到專門的日志處理服務(wù),以保證價(jià)值信息不被意外丟棄,但是這帶來了如下問題:

  • 需要提供一種機(jī)制以保證日志收集進(jìn)程可靠運(yùn)行。

  • 需要通過配置文件告知日志收集進(jìn)程去哪里讀取日志文件。

  • 需要在應(yīng)用程序所在的虛擬機(jī)或者容器上為日志收集進(jìn)程開放一個(gè)網(wǎng)絡(luò)端口以供其發(fā)送日志內(nèi)容,這不僅增加了網(wǎng)絡(luò)的復(fù)雜度,還給網(wǎng)絡(luò)安全帶來了隱患。

由此可見,直接將日志輸出到STDOUT和STDERR并由運(yùn)行環(huán)境對(duì)其進(jìn)行捕獲遠(yuǎn)比這種方案來的簡(jiǎn)潔和可靠。

2. 在存在專門的日志處理服務(wù)時(shí),由應(yīng)用程序自行對(duì)日志進(jìn)行分類顯得死板和毫無必要;只需將日志以事件流方式發(fā)送給日志處理服務(wù),日志處理服務(wù)可以對(duì)這些日志按不同視角進(jìn)行靈活的分類,而不是受限于一種既定的分類規(guī)則。

3. “原則6:以一個(gè)或多個(gè)無狀態(tài)的進(jìn)程運(yùn)行應(yīng)用”中還提到“微服務(wù)模式以多實(shí)例方式運(yùn)行應(yīng)用,將來的請(qǐng)求多半會(huì)被路由到其他實(shí)例”,所以單個(gè)應(yīng)用實(shí)例的日志無法描述完整的業(yè)務(wù)操作,不具備業(yè)務(wù)層面的價(jià)值。必須將應(yīng)用所有實(shí)例的日志匯總到日志處理服務(wù),由日志處理服務(wù)按特定規(guī)則(如按用戶ID或者對(duì)象ID)對(duì)其進(jìn)行聚合,才能完整展現(xiàn)應(yīng)用在業(yè)務(wù)層面的操作過程。

應(yīng)用在以多實(shí)例方式運(yùn)行時(shí),應(yīng)用的單個(gè)實(shí)例可能會(huì)因?yàn)檐浻布收隙貑ⅲ蛘弑粰M向擴(kuò)展機(jī)制創(chuàng)建和銷毀,所以必須將應(yīng)用所有實(shí)例的日志匯總,才能完整的描述應(yīng)用的運(yùn)行情況。

原則12:后臺(tái)管理任務(wù)當(dāng)作一次性進(jìn)程運(yùn)行

這是12原則的最后一條,也是最晦澀的一條。如果你在看過原文之后覺得哪里有些不大對(duì),不必?fù)?dān)心,因?yàn)楹芏嗳说南敕ê湍阋粯印T诒竟?jié)的開頭曾提到有人批評(píng)12原有過于依賴Heroku自身特性的傾向,這些批評(píng)多半可能是本原則導(dǎo)致的。

事實(shí)上,通過SSH接入線上環(huán)境并使用腳本語(yǔ)言執(zhí)行管理任務(wù)的做法已經(jīng)不再被提倡,無論是云原生應(yīng)用還是微服務(wù)模式都極力反對(duì)這種做法,原因可以參見“理念五:不可變服務(wù)器”和“理念六:提供聲明式接口”。另外還有一個(gè)原因顯而易見:你的應(yīng)用有數(shù)個(gè)或者數(shù)十個(gè)實(shí)例,那么應(yīng)該登錄到哪個(gè)實(shí)例中執(zhí)行管理任務(wù)呢?如果在管理任務(wù)執(zhí)行的過程中,所在實(shí)例因?yàn)檐浻布收现貑ⅲ蛘弑粰M向擴(kuò)展機(jī)制銷毀,那又該怎么辦?

正確的做法是,如果管理任務(wù)是修改應(yīng)用配置,那么應(yīng)該通過配置管理服務(wù)進(jìn)行操作,參見“原則3:在環(huán)境中存儲(chǔ)配置”;如果管理任務(wù)是批處理任務(wù),例如數(shù)據(jù)的遷移、清洗或者檢查,那么應(yīng)該通過云平臺(tái)的批處理機(jī)制進(jìn)行操作,大多數(shù)的云平臺(tái)都會(huì)提供這種機(jī)制,例如Kubernetes的Jobs。

本原則還提到“應(yīng)用的管理進(jìn)程應(yīng)該和應(yīng)用的常駐進(jìn)程運(yùn)行于同一環(huán)境,并使用相同的代碼、版本和配置”,這是一條比較有價(jià)值的建議,可以避免由于環(huán)境或代碼等不一致造成的一些潛藏問題。雖然現(xiàn)在不提倡通過SSH接入應(yīng)用常駐進(jìn)程所在的環(huán)境并執(zhí)行管理任務(wù),但是如果你使用容器技術(shù),那么很容易通過容器模板創(chuàng)建一個(gè)和應(yīng)用常駐進(jìn)程一致的運(yùn)行環(huán)境,并在其中執(zhí)行管理任務(wù)。

關(guān)于作者:宋瀟男

Unix和分布式系統(tǒng)專家,網(wǎng)格時(shí)代的幸存者,在云計(jì)算行業(yè)有近十年的研發(fā)和市場(chǎng)工作經(jīng)驗(yàn),對(duì)操作系統(tǒng)、中間件和云平臺(tái)有深入研究和大型項(xiàng)目經(jīng)驗(yàn)。曾在華為云計(jì)算部門負(fù)責(zé)產(chǎn)品規(guī)劃、商業(yè)洞察、以及EMEA地區(qū)的市場(chǎng)推廣與技術(shù)合作工作。現(xiàn)任普元云計(jì)算架構(gòu)師。

關(guān)于EAWorld

微服務(wù),DevOps,元數(shù)據(jù),企業(yè)架構(gòu)原創(chuàng)技術(shù)分享EAii(Enterprise Architecture Innovation Institute)企業(yè)架構(gòu)創(chuàng)新研究院旗下官方微信公眾號(hào)。微信搜索:EAWORLD


閱讀原文:http://platformplus.blog.sohu.com/324921436.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容