程式設計師必知的六種隔離技術

phodal發表於2016-04-07

節選自《Growth: 全棧增長工程師指南》

為了將我們的應用部署到伺服器上,我們需要為其配置一個執行環境。從底層到頂層有這樣的執行環境及容器:

  1. 隔離硬體:虛擬機器

  2. 隔離作業系統:容器虛擬化

  3. 隔離底層:Servlet容器

  4. 隔離依賴版本:虛擬環境

  5. 隔離執行環境:語言虛擬機器

  6. 隔離語言:DSL

實現上這是一個請求的處理過程,一個HTTP請求會先到達你的主機。如果你的主機上執行著多個虛擬機器例項,那麼請求就會來到這個虛擬機器上。又或者是如果你是在Docker這一類容器裡執行你的程式的話,那麼也會先到達Docker。隨後這個請求就會交由HTTP伺服器來處理,如Apache、Nginx,這些HTTP伺服器再將這些請求交由對應的應用或指令碼來處理。隨後將交由語言底層的指令來處理。 

不同的環境有不同的選擇,當然也可以結合在一起。不過,從理論上來說在最外層還是應該有一個真機的,但是我想大家都有這個明確的概念,就不多解釋了。

1、隔離硬體(虛擬機器)

在虛擬機器技術出現之前,為了執行不同使用者的應用程式,人們需要不同的物理機才能實現這樣的需求。對於Web應用程式來說,有的使用者的網站訪問量少消耗的系統資源也少,有的使用者的網站訪問量大消耗的系統資源也多。雖然有不同的伺服器型別可以選擇,然而對於多數的訪問少的使用者來說他們需要支付同樣的費用。這聽上去相當的不合理,並且也浪費了大量的資源。並且對於系統管理員來說,管理這些系統也不是一件容易的事。在過去硬體技術革新特別快,讓作業系統執行在不同的機器上也不是一件容易的事。

虛擬機器(Virtual Machine)指通過軟體模擬的具有完整硬體系統功能的、執行在一個完全隔離環境中的完整計算機系統。

這是一個很有意思的技術,它可以讓我們在一個主機上同時執行幾個不同的作業系統。我們可以為這幾個作業系統使用不同的硬體,在這之上的應用可以使用不同的技術棧來執行,並且從理論上互相不影響。其架構如下圖所示:

藉助於虛擬機器技術,當我們需要更多的資源的時候,建立一個新的虛擬機器就行了。同時,由於這些虛擬機器上執行的是同樣的作業系統,並且可以使用相同的配置,我們只需要編寫一些指令碼就可以實現其自動化。當我們的物聯機發生問題時,我們也可以很快將虛擬機器遷移或恢復到另外的宿主機。

2、隔離作業系統(容器虛擬化)

對於大部分的開發團隊來說,直接開發基於虛擬機器的自動化工具不是一件容易的事,並且他從使用成本上來說比較高。這時候我們就需要一些更輕量級的工具容器——它可以提供輕量級的虛擬化,以便隔離程式和資源,而且不需要提供指令解釋機制以及全虛擬化的其他複雜性。並且,它從啟動速度上來說更快。

LXC

在介紹Docker之前,我們還是稍微提一下LXC。因為在過去我有一些使用LXC的經歷,讓我覺得LXC很贊。

LXC,其名稱來自Linux軟體容器(Linux Containers)的縮寫,一種作業系統層虛擬化(Operating system–level virtualization)技術,為Linux核心容器功能的一個使用者空間介面。它將應用軟體系統打包成一個軟體容器(Container),內含應用軟體本身的程式碼,以及所需要的作業系統核心和庫。通過統一的名字空間和共用API來分配不同軟體容器的可用硬體資源,創造出應用程式的獨立沙箱執行環境,使得Linux使用者可以容易的建立和管理系統或應用容器。

我們可以將之以上面說到的虛擬機器作一個簡單的對比,其架構圖如下所示:

我們會發現虛擬機器中多了一層Hypervisor——執行在物理伺服器和作業系統之間,它可以讓多個作業系統和應用共享一套基礎物理硬體。這一層級可以協調訪問伺服器上的所有物理裝置和虛擬機器,然而由於這一層級的存在,它也將消耗更多的能量。據愛立信研究院和阿爾託大學發表的論文表示:Docker、LXC與Xen、KVM在完成相同的工作時要少消耗10%的能耗。

LXC主要是利用cgroups與namespace的功能,來向提供應用軟體一個獨立的作業系統執行環境。cgroups(即Control Groups)j Linux核心提供的一種可以限制、記錄、隔離程式組所使用的物理資源的機制。而由namespace來責任隔離控制。

與虛擬機器相比,LXC隔離性方面有所不足,這就意味著在實現可移植部署會遇到一些困難。這時候,我們就需要Docker來提供一個抽象層,並提供一個管理機制。

Docker

Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然後釋出到任何流行的 Linux 機器上,也可以實現虛擬化。Docker可以自動化打包和部署任何應用、建立一個輕量級私有PaaS雲、搭建開發測試環境、部署可擴充套件的Web應用等。

構建出Docker的Container是一個很有意思的過程。在這一個過程中,首先我們需要一個base images,這個基礎映象不僅包含了一個基礎系統,如Ubuntu、Debian。他還包含了一系列的模組,如初始化程式、SSH服務、syslog-ng等等的一些工具。由上面原內容構建了一個基礎映象,隨後的修改都將於這個映象,我們可以用它生成新的映象,一層層的往上疊加。而使用者的程式執行在writeable的layer中。 

從上圖中我們還可以發現一點: Docker容器是建立在Aufs基礎上的。AUFS是一種Union File System,它可以不同的目錄掛載到同一個虛擬檔案系統下。它的目的就是為了實現上圖的增量遞增的過程,同時又不會影響原有的目錄。即如下的流程如下:

其增量的過程和我們使用Git的過程中有點像,除了在最開始的時候會有一個映象層。隨後我們的修改都可以儲存下來,並且當下次我們提交修改的時候,我們也可以在舊有的提交上執行。

因此,Docker與LXC的差距就如下如圖所示:

LXC時每個虛擬機器只能是一個虛擬機器,而Docker則是一系列的虛擬機器。

3、隔離底層(Servlet容器)

在上面的例子裡我們已經隔離開了作業系統的因素,接著我們還需要解決作業系統、開發環境引起的差異。早期開發Web應用時,人們使用CGI技術,它可以讓一個客戶端,從網頁瀏覽器向執行在網路伺服器上的程式請求資料。並且CGI程式可以用任何指令碼語言或者是完全獨立程式語言實現,只要這個語言可以在這個系統上執行。而這樣的指令碼語言在多數情況下是依賴於系統環境的,特別是針對於C++這一類的編譯語言來說,在不同的作業系統中都需要重新編譯。

而Java的Servlet則是另外一種有趣的存在,它是一種獨立於平臺和協議的伺服器端的Java應用程式,可以生成動態的Web頁面。

Tomcat

在開發Java Web應用的過程中,我們在開始環境使用Jetty來執行我們的服務,而在生產環境使用Tomcat來執行。他們都是Servlet容器,可以在其上面執行著同一個Servlet應用。Servlet是指由Java編寫的伺服器端程式,它們是為響應Web應用程式上下文中的HTTP請求而設計的。它是應用伺服器中位於元件和平臺之間的介面集合。

Tomcat伺服器是一個免費的開放原始碼的Web應用伺服器。它執行時佔用的系統資源小,擴充套件性好,支援負載平衡與郵件服務等開發應用系統常用的功能。除此,它還是一個Servlet和JSP容器,獨立的Servlet容器是Tomcat的預設模式。其架構如下圖所示:

Servlet被部署在應用伺服器中,並由容器來控制其生命週期。在執行時由Web伺服器軟體處理一般請求,並把Servlet呼叫傳遞給“容器”來處理。並且Tomcat也會負責對一些靜態資源的處理。

4、隔離依賴版本(虛擬環境)

對於Java這一類的編譯語言來說,不存在太多語言執行帶來的問題。而對於動態語言來說就存在這樣的問題,如Ruby、Python、Node.js等等,這一個問題主要集中於開發環境。當然如果你在一個伺服器上執行著幾個不同的應用來說,也會存在這樣的問題。這一類的工具在Python裡有VirtualEnv,在Ruby裡有RVM、Rbenv,在Node.js裡有NVM。

下圖是使用VirtualEnv時的不同幾個應用的架構圖:

如下所示,在不同的虛擬環境裡,我們可以使用不同的依賴庫。在這上面構建不同的應用,也可以使用不同的Python版本來構建系統。通常來說,這一類的工具主要用於本地的開發環境。

5、隔離執行環境(語言虛擬機器)

最後一個要介紹的可能就是更加抽象的,但是也是更加實用的一個,JVM就是這方面的一個代表。在我們的程式設計生涯裡,我們很容易就會遇到跨平臺問題——即我們在我們的開發機器上開發的軟體,在我們的產品環境的機器上就沒有辦法執行。特別是當我們使用Mac OS或者Windows機器上開發了我們的應用,然後我們需要在Linux系統上執行,就會遇到各種問題。並且當我們使用了一個需要重新編譯的庫時,這種問題就更加麻煩。

如下圖所示的是JVM的架構示意圖

JVM是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。它可以實現“編寫一次,到處執行”。

換句話來說,它在底層實現了環境隔離,它遮蔽了與具體作業系統平臺相關的資訊,使得Java程式只需生成在Java虛擬機器上執行的目的碼(位元組碼),就可以在多種平臺上不加修改地執行。

基於此,只要其他程式語言的編譯器能生成正確Java bytecode檔案,這個語言也能實現在JVM上執行。如下圖所示的是基於JVM的Jython語言的架構圖:

其底層是基於JVM,而編寫時則是用Python語言,並且他可以使用Java的模組來程式設計。

常見擁有同樣架構的工具,還有MySQL,如下圖是所示的是MySQL的架構圖:

MySQL在最頂層提供了一個名為SQL的查詢語言,這個查詢語言只能用於查詢資料庫,然而它卻是一種更高階的用法 。它不像通用目的語言那樣目標範圍涵蓋一切軟體問題,而是專門針對某一特定問題的計算機語言,即領域特定語言。

6、隔離語言(DSL)

這是一門特別有意思也特別值得期待的技術,但是實現它並不是一件容易的事。

作為討論隔離環境的一部分,我們只看外部DSL。內部DSL與外部DSL最大的區別在於:外部DSL近似於建立了一種新的語法和語義的全新語言。如下圖所示是兩中DSL的一種對比:

在這樣的外部DSL裡,我們有自己的語法、自己的解析器、型別檢測器等等。最簡單且最常用的DSL就是Markdown,如下圖所示:

如果我們可以將我們的業務邏輯寫成DSL,那麼我們就不需要擔心底層語言的變動過多地影響原有的業務邏輯。換句話說,這相當於建立了我們自己的語言隔離環境,我們不需要思考用何種語言來實用我們的業務。

相關文章