從Web開發(fā)需求驅(qū)動看容器與微服務架構的完美結合
本文中,我們將探討容器的一些特性,在減輕開發(fā)/測試工作量的同時,也使得它們非常適合在AWS中構建一個微服務構架。對于Web應用程序來說,微服務架構可以讓應用程序的代碼庫更加敏捷,并且容易管理。下面,我們將介紹這個架構為何可以大幅提升開發(fā)者生產(chǎn)效率的原因,并了解它能夠快速迭代和擴充一個代碼庫的原理。對于快速發(fā)展中的創(chuàng)業(yè)公司來說,微服務架構可以讓開發(fā)團隊在研發(fā)過程中更加的敏捷和靈活。
Web開發(fā)簡史
首先,我們快速回顧一下過去20年的Web開發(fā)歷史,這有助于解釋為什么微服務架構變得如此流行,以及這一架構解決的問題。
在Web應用開發(fā)的最初階段,應用都是使用通用網(wǎng)關接口(CGI)創(chuàng)建的。這個接口為Web服務器提供了一種在處理瀏覽器HTTP請求時執(zhí)行腳本(通常是使用Perl編寫)的方法。由于對腳本資源的每個請求都需要啟動一個Perl進程處理,CGI架構無法很好地擴展。為了解決這個問題,當時流行的Web服務器添加了模塊支持。Apache,如今***的Web服務器之一,添加了“mod_perl”模塊,在服務器內(nèi)部運行Perl代碼,這使得CGI腳本能更快執(zhí)行。
盡管像mod_perl這樣的技術讓傳統(tǒng)CGI獲得了巨大提升,問題依然存在。負責構建視圖的代碼(比如解析頁面HTML的動態(tài)部分)與應用的業(yè)務邏輯混雜在一起。這意味著要完成一個簡單的任務,比如在HTML表格中添加一列或在表單中添加一個新元素,都需要修改底層應用代碼。因此,Web編程技術的下一輪革新成果“server pages”出現(xiàn)了,它是允許在HTML里嵌入可執(zhí)行代碼的模板框架。可以更清晰地將應用邏輯與視圖邏輯分開。在Java編程世界,一種名為“Model 2”的設計模式迅速出現(xiàn),如圖1所示,它將應用代碼放入Java servlet、數(shù)據(jù)放入叫Java Bean的類中、視圖邏輯放入JSP:
“Model 2”很快演變成了如今廣泛使用的Model-View-Controller(MVC)設計模式。很多早期的MVC框架都是以Java為基礎的(如Apache Struts),不過其它框架如Ruby On Rails也在快速普及。在MVC設計模式中,“controller”類定義了映射到“route”類URL的方法。controller方法使用封裝了核心應用實體業(yè)務邏輯和數(shù)據(jù)的“model”類。***,每個controller方法渲染一個用于顯示和編輯相應model類數(shù)據(jù)的“view”。如圖2所示,這個模式將業(yè)務、應用和視圖邏輯清晰地分離開了:
REST協(xié)議崛起
在MVC作為Web開發(fā)的事實選擇被快速采納的同時,進程間通訊(IPC)演進為使用以文本為基礎的系列化格式,如XML和JSON。像SOAP這樣的協(xié)議允許通過HTTP進行IPC,很快Web開發(fā)人員不再只是創(chuàng)建為瀏覽器提供內(nèi)容的Web應用,還有為其它程序執(zhí)行任務并交付數(shù)據(jù)的Web服務。這種基于服務的架構擁有非常強大的功能,因為它消除了對共享代碼庫的依賴,允許應用開發(fā)人員更進一步解耦應用組件。SOAP協(xié)議和相關的WS-*標準很快變得越來越復雜,并且嚴重依賴于應用服務器的具體實現(xiàn),因此開發(fā)人員遷移到更輕量的REST協(xié)議上。隨著移動設備的廣泛使用,Web UX開發(fā)轉(zhuǎn)移到了AJAX和JavaScript框架上,應用開發(fā)人員開始擴展REST的功能用于在客戶端設備與Web服務器間傳輸數(shù)據(jù)。
事實上,MVC框架也非常適合開發(fā)REST端。如圖3所示,REST面向資源的特性被很好的映射成了控制器和模型理念,如圖3所示:
一體化架構(Monolithic Architecure)
MVC應用由模型、視圖和控制器組成,主要為應用提供可變的HTML內(nèi)容,包括傳統(tǒng)的HTML和REST端的JSON。很多這類應用使用了一體化架構。應用以一個單一文件(如Java)或放置在同一目錄下的一組文件(如Rails)的形式進行部署。所有應用代碼運行在同一進程中。擴展時要求將完全相同的應用代碼部署到多臺服務器上。圖4是一體化架構的表示:
一體化架構存在一些問題。首先,在添加功能和服務到應用中時,代碼庫會變得非常復雜。對于新的開發(fā)人員是個巨大挑戰(zhàn)。如今的IDE甚至在加載整個應用代碼時會發(fā)生問題,而且編譯與構建時間非常久。因為所有的應用代碼都運行在服務器的同一個進程中,要擴展應用的其中一部分(就算可能)非常困難。假如有一個服務是內(nèi)存密集型的,另一個是CPU密集型的,服務器就必須提供足夠的內(nèi)存和CPU來處理每個服務的基本負載。如果每臺服務器都需要大量CPU和內(nèi)存,花費不小,如果使用負載均衡進行應用的橫向擴展,則費用將更高。***,更微妙的是,隨著時間推移,工程團隊架構常常開始呈現(xiàn)為應用架構。UX工程師被派去構建UI組件,中間層開發(fā)人員構建服務端,數(shù)據(jù)庫工程師和DBA處理數(shù)據(jù)訪問組件和數(shù)據(jù)庫。如果一個UX工程師想要添加一個數(shù)據(jù)到界面上,就必須與中間層和數(shù)據(jù)庫工程師協(xié)商。像水一樣,人類傾向于選擇阻力最小的路徑,這意味著每個工程組都會在他們控制的應用部分嵌入盡可能多的邏輯。時間一久,注定會產(chǎn)生難以維護的代碼。
#p#
微服務架構
微服務架構設計用于解決這個問題。在一體化架構中定義的服務被分解成一個個單獨的服務,并在不同主機上分開部署。
每個微服務針對一個特定的業(yè)務功能,并且只定義該業(yè)務功能所需的操作。這聽起來像是以服務為導向的架構(SOA),實際上,微服務架構與SOA有一些共通的特性。兩個架構都將代碼組織成服務,二者都定義了清晰邊界來區(qū)隔服務。然而,SOA源于將提供API(通常以SOAP為基礎)的一體化應用與其他應用整合在一起的需求。在SOA中,整合嚴重依賴于中間件,特別是企業(yè)服務總線(ESB)。微服務架構經(jīng)常使用消息總線,不過在消息層完全沒有邏輯,它簡單的被用來在服務間傳送信息。這與ESB存在巨大差別,后者包含了大量消息路由、schema驗證、消息轉(zhuǎn)換和業(yè)務規(guī)則的邏輯。由此可見,微服務架構相比傳統(tǒng)SOA累贅更少,且在定義服務間接口時不要求相同級別的治理和規(guī)范的數(shù)據(jù)模型。使用微服務,開發(fā)速度快并且服務可根據(jù)業(yè)務需要演進。
微服務架構的另一個關鍵優(yōu)勢在于,服務可以根據(jù)其資源需求進行單獨擴展。與運行配備了大量CPU和內(nèi)存的大型服務器不同,微服務可以部署在僅包含該服務需要的資源的較小主機上。此外,每個服務可以使用最適合其操作執(zhí)行的語言來實現(xiàn)。圖像處理服務可以使用高性能語言如C++實現(xiàn)。執(zhí)行數(shù)學或統(tǒng)計操作的服務可以使用Python實現(xiàn)。執(zhí)行基本的資源CRUD操作的服務可以用Ruby實現(xiàn)。微服務架構不要求一體化架構的“一刀切”模型,后者通常使用單一的MVC架構和單一的編程語言。
不過,微服務也有一些缺點。因為服務分布在多臺主機上,要了解哪臺主機運行了某些服務可能會很困難。另外,盡管每臺主機可能不像運行一體化架構的主機那么強大,在微服務架構橫向擴展時,主機的數(shù)量將比一體化架構增加得更快。在AWS環(huán)境中,即便最小的EC2實例類型,微服務可能都不需要其全部資源,結果就是超量供給及成本上升。如果服務使用不同編程語言實現(xiàn),意味著每個服務的部署都需要一個完全不同的庫與框架集合,使得部署到服務器非常復雜。
容器是解決之道
Linux容器可以減輕微服務架構面臨的這些挑戰(zhàn)。Linux容器使用了內(nèi)核接口如別名(cname)和命名空間(namespace),允許多個容器共享同一個內(nèi)核,同時運行時又與其他容器完全隔離。Docker執(zhí)行環(huán)境使用了一個叫l(wèi)ibcontainer的模塊,用于規(guī)范這些接口。Docker還提供了一個與GitHub類似的名叫DockerHub的容器鏡像倉庫,便于共享和分發(fā)容器。
同一臺主機上運行的多個容器間的隔離性使得部署用不同語言和框架開發(fā)的微服務代碼時非常簡單。使用Docker,我們可以創(chuàng)建一個Dockerfile描述所有該服務使用的語言、框架和依賴庫。比如,以下Dockerfile可以用來定義一個微服務的Docker鏡像,在其中使用了Ruby和Sinatra框架:
- FROM ubuntu:14.04
- MAINTAINER John Doe <jdoe@example.com>
- RUN apt-get update && apt-get install -y curl wget default-jre git
- RUN adduser --home /home/sinatra --disabled-password --gecos '' sinatra
- RUN adduser sinatra sudo
- RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
- USER sinatra
- RUN curl -sSL https://get.rvm.io | bash -s stable
- RUN /bin/bash -l -c "source /home/sinatra/.rvm/scripts/rvm"
- RUN /bin/bash -l -c "rvm install 2.1.2"
- RUN /bin/bash -l -c "gem install sinatra"
- RUN /bin/bash -l -c "gem install thin"
使用該鏡像創(chuàng)建的容器可以簡單地放置到一臺運行了另一個容器的主機上,即便這個容器創(chuàng)建自一個使用了Java和DropWizard框架的Docker鏡像。容器執(zhí)行環(huán)境將運行在該主機上的容器彼此隔離開,因此容器使用的語言、庫和框架依賴不會與其它容器相沖突。
容器的可移植性讓微服務的部署變得微不足道。要對運行在指定主機上的服務進行升級,只需要簡單地停止運行中的容器,然后啟動以***版服務代碼的Docker鏡像創(chuàng)建的新的容器。該主機上的所有其它容器都不會受此變化影響。
容器還有助于提高主機資源利用率。如果指定的服務無法使用Amazon EC2實例的所有資源,可以啟動其它服務容器來利用這些空閑資源。當然,如果是手工在容器中部署服務、管理哪些服務運行在哪些主機上以及監(jiān)控所有運行容器的主機的利用率,事情將迅速變得難以掌控。
最近發(fā)布的Amazon EC2容器服務(Amazon ECS)就是用于解決這個問題的。使用Amazon ECS,定義一個名叫“集群(cluster)”的計算資源池。一個集群由一個或多個Amazon EC2實例組成。Amazon ECS管理了運行在集群里的所有容器應用的狀態(tài),提供自動監(jiān)測和日志,管理集群的利用率,完成工作的高效調(diào)度。Amazon ECS提供了一個叫“任務定義(task definition)”的組件,用于定義一組包含應用的容器。任務定義里的每個容器指明其所需資源,Amazon ECS將根據(jù)集群里的可用資源調(diào)度任務執(zhí)行。
微服務可以簡單的定義成一個任務,并可由兩個容器組成——一個運行服務端代碼,另一個是數(shù)據(jù)庫。Amazon ECS管理著容器間的依賴,以及集群里的資源均衡。Amazon ECS還提供了對彈性負載均衡(Elastic Load Balancing)、Amazon EBS、彈性網(wǎng)絡接口(Elastic Network Interface)及自動擴展(Auto Scaling)等重要的AWS服務的無縫訪問。使用Amazon ECS,容器應用也可以使用所有這些部署應用到EC2的關鍵功能。
像Amazon ECS這樣的容器管理方案還簡化了“服務發(fā)現(xiàn)(service discovery)”的實現(xiàn)。因為微服務經(jīng)常要跨多主機部署,并常常需要根據(jù)負載進行伸縮,要讓一個服務知道如何定位另一個服務,服務發(fā)現(xiàn)就是必需的。最簡單的方式,就是使用負載均衡器。不過很多情況下,還是有必要使用一個真正的分布式配置服務,比如Apache Zookeeper。Amazon ECS API可以整合像Zookeeper這樣的第三方工具。也能使用Amazon ECS管理Zookeeper集群。可以使用任務定義將組成Zookeeper集群的容器分在一組,并通過Amazon ECS服務調(diào)度到集群的Amazon EC2主機上執(zhí)行。
從很多方面而言,使用容器實現(xiàn)微服務架構是一項不同于過去20年Web開發(fā)觀察到的進化。這種進化受到了如何更好的利用計算資源和維護日益復雜的web應用的需求驅(qū)動。如我們所見,通過Linux容器使用微服務架構解決了這兩項需求。我們簡要說明了如何將微服務定義成Amazon ECS的一項任務,但是在分布式系統(tǒng)中使用容器遠遠不僅限于微服務。漸漸地,容器已經(jīng)成了所有分布式系統(tǒng)的“一等公民”,未來的文章中我們將探討類似Amazon ECS這樣的工具如何在管理以容器為基礎的計算資源中發(fā)揮重要作用。