部署Swift對象存儲中間件

本文精選自新書《對象存儲:OpenStack Swift應用、管理與開發》。


通過中間件機制,OpenStack Swift能夠很容易地進⾏行功能定製和擴展,甚⾄至編寫少量的代碼就可以實現對數據的加密、壓縮和轉碼等功能。奧思數據的OStorage對象存儲產品提供兼容阿⾥裏雲OSS接口的功能,進而提供混合雲存儲方案,即是通過編寫中間件實現的。具體如何利⽤用中間件擴展Swift對 象存儲的功能,敬請閲讀《對象存儲:OpenStack Swift應⽤用、管理與開發》的第八章《部署Swift中間件》。


第八章 部署Swift中間件
 

僅僅進行默認的 Swift 安裝,就可以為您解決很多分佈式存儲上的難題。如果這仍然是不能滿足 您的需求,Swift 還附帶了⼀些有⽤用的中間件來擴展 Swift 的功能 。但是,有時候開發者需要針對特定的需求進行定製,Swift 的開放式設計可以令您很容易地通過中間件來添加您所需要的功能。使用中間 件是定製 Swift 的好方法。


不過中間件也有其複雜性。本章旨在為您提供 Swift 中間件的工作原理的概述。有兩點需要引起 注意:第一,Swift 是創建在 Python 的 Web 服務網關接⼝口( WSGI )模型之上的,第二,您需要通過 Python Paste 框架來配置。中間件可能有些不符合常理,因為它“包裹”了Swift 核心進程(和其他中間 件層)。因此,在某種意義上,您需要由內而外地設計您的系統——外部來的請求在到達Swift核心進程 前,將通過多層中間件,並且每層中間件都可能改變請求。在闡述這些背景後,本章將通過一些例子 幫助您熟悉如何編寫 Swift 中間件。最後,我們將為您提供一些關於如何使⽤用中間件的建議。


WSGI 框架介紹 


WSGI 框架是 Swift 架構的基本組件,開發者們對 WSGI 有褒有貶。WSGI 的最大的優勢是它的中 間件模型:中間件一層層地“包裹”其他中間件,最中心為具體應⽤用進程(本例中為 Swift 核心進程 )。對 開發者和部署者來説,這使問題變得更加簡單,因為每層中間件不需要了解其他層和最內部的應用程 序層(本例為 Swift 核心進程)的任何細節。因此中間件的代碼可以非常簡單易懂。但另一方⾯,WSGI 的編程模型卻會讓您暈頭轉向。不過別擔心,我們會在下⾯面的部分來幫助您理解WSGI編程模型。


我們先來看一下嵌有多箇中間件層的系統是如何工作的:最外層的中間件收到請求,並可能會對它進行修改,然後再傳遞給下一層中間件。隨後,下一層中間件同樣可能在請求向內層傳遞前對它進行修改,如圖8-1。當請求到達中間件管道(pipeline)的末尾時,由 Swift 核⼼心進程進行處理,併產生一個響應。響應順着管道反向傳遞,管道中所有中間件都可以對響應進行修改。當然,任何一層中 間件也可以選擇不對請求或響應做任何更改(大多數情況下都如此)。一旦最外層的中間件對響應進行處 理(或者不作任何修改)之後,最終的響應就會返回給⽤用户。


圖 8-1. WSGI 中的多層中間件是如何⼯工作的


管道的每層中間件都可以短路請求並返回一個響應或錯誤。例如,如果未認證用户想要執行一個 需要認證的操作,認證中間件就會返回一個錯誤。當收到健康檢查請求時,健康檢查中間件可以選擇 直接返回一個成功的響應,而不會繼續向內層中間件傳遞該健康檢查請求。在這些短路的例子中,都 不會將請求傳遞到下游的中間件或Swift核心進程(圖8-2)。


圖 8-2. 請求的短路處理方式


總之,收到請求時,每個中間件選擇性地檢查或修改請求,並選擇是繼續傳遞給下游中間件還是 直接短路請求並返回⼀一個響應。同樣,在響應返回時,也可以選擇是檢查響應還是更改這個響應,或 者兩者都做。


編寫WSGI 


一個WSGI應⽤用不是一個Web服務器——它不在80端⼝口監聽傳入的請求等。相反,一個獨⽴立的兼容 WSGI的Web服務器將扮演該角色,並把請求傳遞給WSGI應用。為了移交請求,Web服務器給您的應用進程提供了兩個東⻄西: 一是請求環境(request environment),是一個類似字典的對象,它包含了 該請求所有用的信息(包括HTTP頭,HTTP請求方法,客户端IP地址,路徑和查詢參數等),二是 ⼀一個函數,Web服務器會在您的WSGI應用準備好開始迴應請求時調用該函數。值得注意的是,對請求 發出響應(通過80端口發出字節)是Web服務器的職責而不是您的應用的職責,您的應用只需要給 Web服務器提供一些HTTP頭和響應內容就可以了。 


因此,一個WSGI應用往往被編寫成為一個擁有兩個參數的函數:一個是包含請求環境信息的字典 對象,另一個是返回響應時調用的函數。換⾔言之,一個WSGI應用實現瞭如下接口:



很多網上關於WSGI的教程從接口定義開始講解卻沒有介紹為什麼要這麼做,這樣可能會導致讀者 困惑。讀者可能會想:“多麼奇怪的接口!為什麼start_response()這樣定義?為什麼我們跳過了循 環?”(假設這位讀者已經明白提供請求環境environment和start_response函數的是Web服務器而不是 WSGI。)


其實WSGI協議這樣來定義接口是有着很好的理由的,這些理由大多與中間件有關。中間件利用請 求環境來添加信息並且與其他中間件通信。start_response()是由兼容WSGI的Web服務器提供的一個方 法。一旦您的應用調用start_response(),那麼這個HTTP響應的首部就會被髮送且不可再更改。在這之 前,中間件都可以添加、改變或刪除首部字段。 


是時候向您展示一下中間件了,下面是一個WSGI中間件的模版或者説簡單的例子。該代碼僅僅向 您展示中間件和中間件管道,除此之外,並不具備任何功能:



讓我們考慮一下它是如何工作的。在一台機器上運行着一個配置好的兼容WSGI的Web服務器,用 於給WSGI應用分發請求。(記住,“WSGI應用”不是一個獨⽴立的進程而僅僅是一個實現上述接口的函數 或者其他調用)。Web服務器實現了類似下面偽代碼所⽰

示的功能:



現在想象一下,前⾯面這個兼容WSGI的Web服務器是通過調用應用進程app來提供服務的,現在將 其配置為調用TrivialMiddleware(app)來提供服務。TrivialMiddleware的構造函數把app作為參數,因此 初始化能正常完成。對於每個請求而言,服務器不再調用app(environ,start_response),而是調用TrivailMiddleware實例的call方法。上例中這個方法只傳回app(environ,start_response)的值。到目前 為止,我們還沒有做任何事情,但是我們展示瞭如何構建一個前面所述的中間件管道。


數據流和數據的修改 


雖然我們上面介紹的TriviaMiddleare類只是簡單地把響應結果一成不變地返回給用户,但是要編 寫一個修改響應的中間件也是很簡單的。請您記住,應用進程返回值是一個可以迭代的值。這可以讓 數據量大的響應以流的形式傳遞而不是一次性全部讀入Web服務器的內存中。如果您知道您的響應數 據量不大,您可以選擇在內存中實例化整個響應,但我們不建議這樣做。這裏有一箇中間件實例,它 可以讓所有的響應實體中的字符統一變成大寫字母,它就是一次性讀取整個響應並把它送入內存當 中:



這裏還有另外一個實現,它返回一個生成器而不是一個列表,這樣就可以避免讀取整個響應到內 存中:



在這種情況下,讓中間件以流的方式處理數據實際上沒有增加任何代價,並且在處理大的響應方面更有優勢。在一些複雜的情況下,為了簡化代碼,可以⼀次性把完整的響應讀入內存中來。如果您 能確定您將處理的響應的大小,您可以這樣做——但是必須萬分謹慎,特別是在處理大規模存儲的時 候! 


如果最裏層的應用進程調⽤用了start_response,那麼在外層的中間件如何改變頭部呢?當外部中間 件需要這樣做的時候——⽐方説添加一個新的頭部——這時要給管道中的下一個中間件傳遞的 start_response方法和前面稍有不同,如下面代碼所示:



這樣,是不是很古怪!改變響應本來應該是很直接的,但我們並不直接更改頭部,而是通過一個 函數完成,如果該函數被調⽤,除了完成start_response的工作,還會添加一個額外的頭部。自然地, 管道下游的所有中間件都會認為這是start_response函數的功能,所以這個函數還會被最裏層應用調用。當然,也有一些其他中間件對start_response函數做了自己的包裝! 


這些是WSGI的缺點:調用鏈、控制流和分工相當混亂。但是擁有定義中間件管道這麼強大功能令 WSGI成為了一個相當引⼈人注目的接口。 


更多的信息,如WSGI教程,可以在網上找到。


通過 Paste 來配置中間件


Swift 使用 Python Paste 框架來進行配置和部署,特別是它的PasteDeploy模塊。在Paste術語 中,中間件包是應用進程上的“過濾器(filter)”。Paste使⽤用INI風格的配置文檔來定義應用和過濾器, 提供一種簡單的方式來為每個組件設定配置變量。我們將在這討論一些基本用法,關於更多的內容請 參考http://pythonpaste.org/deploy/ 


讓我們看一個簡單的Swift代理服務進程的配置文檔,proxy-server.conf:



這是一個非常簡單的配置文檔,缺失了很多必要的配置設置(如auth中間件),但是對於討論Paste 部署來説很有用。 


在這個管道中,核心的代理服務代碼被包圍在兩個中間件過濾器中間:最外層healthcheck和中間 層的proxy-logging。如果一個請求被healthcheck中間件攔截(不經過中間件管道中的其他部分而直接 返回一個響應),那麼proxy-logging將永遠看不到該請求,並且核心代理服務進程的代碼也看不到。 


配置文檔也定義了各種管道組件將會用到的配置變量。例如,disable_path配置變量 由healthcheck使用。顧名思義,如果指定的文檔存在,健康檢查將不可⽤用。 


上面例子中管道使用的中間件被打包成Python “eggs”。如果你想安裝的中間件並沒有被打包成 “egg” 呢?(這種情況在測試自定義中間件,甚至在生產環境安裝自定義中間件都很常見)。在這種情 況下,只需要用paste.filter_factory聲明替換配置文檔的use聲明:



Paste提供了一種機制讓中間件能夠取到自己所需要的配置變量。第一步就是每節中的use設置, 它給出了打包該中間件的Python egg的位置(若中間件沒被打包到egg,由paste.filter_factory設置起 到相同的作用)。這個值告訴Paste在哪能找到該filter或app的定義。在Swift默認安裝情況下,所有的 ⾮自定義中間件和Swift⼀起安裝在Python egg-info目錄下。這個目錄包含了entry-points.txt⽂文檔,內 容如下所示:



正如您猜測的,paste.app_factory和paste.filter_factory定義了應用進程和過濾器名稱以供配置文 件引用。(應用進程和過濾器的不同僅僅在於應用進程位於管道的最末端,因此它裏面沒有下⼀層中 間件接收請求,應用進程對它收到的所有請求都會產生響應)。⽐方説,當paste.filter_factory在管道 中檢測到healthcheck時,它可以調用swift.commom.middleware.healthcheck模塊中的filter_factory方 法來進行實例化。如果查詢該模塊的代碼,您將發現一個像下⾯的包裝函數:



這個包裝函數允許Paste定義如何讓管道的每個元素包裝(過濾)適當的內部層。這裏,filter_factory是以配置字典和關鍵字為參數的函數,該函數的返回值是另一個函數(本例中 是healthcheck_filter),後者在HealthCheckMiddleware裏包裝了⼀個WSGI app(比如代理服務器的 核心代碼,或者已經包裝了其他中間件的代理服務器代碼),而前⾯面傳入的參數也被傳給 HealthCheckMiddleware,以對它進行設置。


如何編寫Swift中間件 


讓我們來看一個較為完整的HealthCheckMiddleware中間件的例子。它的功能如下:



在這裏有一個新的特性:那就是使用swift.common.swob中的Request和Response對象。在這個模 塊中,Request是WSGI環境和頭部的包裝器。然⽽而,Response就複雜得多了。您可能已經想到,它包 裝了響應碼,響應頭部和響應體。但是,除了作為包裝器或容器外,它還可以作為一個WSGI應用!與 中間件返回self.app(env,start_response)(它調⽤用start_response來發送HTTP響應)類似,中間件還可 以創建一個變量resp = Response(request=req, status=200, body="OK"),並返回resp(env, start_response)而不將控制權傳遞給它所包裝的WSGI應用進程。這種將Response類作為一個WSGI應用 進程⽽而不是作為響應數據的包裝器或者容器的用法,對初學者來説可能有點意外。
 

記住這點,讓我們來看看 HealthCheckMiddleware 做了些什麼。當它被調用時,它實例化一個 Request 並且檢查請求路徑是否為 /healthcheck 。如果不是,它直接調用self.app(env,start_response) 把請求沿管道向下傳遞,並且原封不動地返回它的結果。如果路徑為 /healthcheck,中間件不會向下 傳遞請求,而是指派一個handler去處理(默認情況下是Get handler,如果設置了disable_path並且文 件存在,那麼由DISABLE handler處理)——然後返回handler的值,在這兩種情況下,handler都是一 個swob.Response對象,該對象對Content-Type進行設置並返回狀態碼和既定格式的響應體。


結束語


以上展⽰示了一個簡單的健康檢查中間件的實現。通過編寫中間件,還可以賦予Swift更強大的功能,甚至可以用不到100行代碼就能夠實現:


- 對某種類型的請求進行日誌收集和狀態統計。

- 減少拒絕服務攻擊(Dos)。

- 轉碼上傳數據。


- 攔截某些請求,如防止DELETE。

- 傳輸過程中壓縮/解壓。

- 在上傳和下載過程中對圖片、視頻加⽔水印

- 把上傳的數據發送到外部作業隊列進行後處理(post-processing)。


具體如何編寫Swift中間件,更多內容請參考由電子工業出版社博文視點出版的,奧思數據OStorage技術團隊李明宇等翻譯的《對象存儲:OpenStack Swift應用、管理與開發》。


點擊下方 “閲讀原文” 查看《對象存儲:OpenStack Swift應用、管理與開發》序、前言及目錄


購買鏈接:


電子工業出版社 http://www.phei.com.cn/module/goods/wssd_content.jsp?bookid=49416

京東 https://item.jd.com/12179368.html

噹噹 http://spu.dangdang.com/25066341.html


:本文只代表作者個人觀點,與任何組織機構無關,如有錯誤和不足之處歡迎在留言中批評指正。進一步交流技術可以加我的QQ/微信:490834312。如果您想在這個公眾號上分享自己的技術乾貨,也歡迎聯繫我:)


尊重知識,轉載時請保留全文,幷包括本行及如下二維碼。感謝您的閲讀和支持!《企業存儲技術》微信公眾號:HL_Storage


長按二維碼可直接識別關注

歷史文章彙總(傳送門):http://chuansong.me/account/huangliang_storage

關鍵詞:中間 一個 響應 swift 請求 wsgi 應用 可以 進程 response

相關推薦:

13個Python web框架比較

Werkzeug 與 WSGI 介紹

django框架--底層架構

採用 SwiftNIO 實現一個類似 Express 的 Web 框架

網絡協議HTTP TCP/UDP 瀏覽器緩存 Restful(十)

Django 源碼閲讀(六):深入理解WSGI協議

整理的最全 python常見面試題(基本必考)

WSGI協議

Python的面試題都長什麼樣?[面試題整理]教你入職自己心儀的公司

Flask 通關攻略大全