部署Swift对象存储中间件(部署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 核⼼心程序进行处理,并产生一个响应。响应顺着管道反向传递,管道中所有中间件都可以对响应进行修改。当然,任何一层中 间件也可以选择不对请求或响应做任何更改(大多数情况下都如此)。一旦最外层的中间件对响应进行处 理(或者不作任何修改)之后,最终的响应就会返回给⽤用户。

部署Swift对象存储中间件(部署Swift对象存储中间件)(1)

图 8-1. WSGI 中的多层中间件是如何⼯工作的

管道的每层中间件都可以短路请求并返回一个响应或错误。例如,如果未认证用户想要执行一个 需要认证的操作,认证中间件就会返回一个错误。当收到健康检查请求时,健康检查中间件可以选择 直接返回一个成功的响应,而不会继续向内层中间件传递该健康检查请求。在这些短路的例子中,都 不会将请求传递到下游的中间件或Swift核心程序(图8-2)。

部署Swift对象存储中间件(部署Swift对象存储中间件)(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中间件的模版或者说简单的例子。该代码仅仅向 您展示中间件和中间件管道,除此之外,并不具备任何功能:

部署Swift对象存储中间件(部署Swift对象存储中间件)(3)

让我们考虑一下它是如何工作的。在一台机器上运行着一个配置好的兼容WSGI的Web服务器,用 于给WSGI应用分发请求。(记住,“WSGI应用”不是一个独⽴立的进程而仅仅是一个实现上述接口的函数 或者其他调用)。Web服务器实现了类似下面伪代码所⽰

示的功能:

部署Swift对象存储中间件(部署Swift对象存储中间件)(4)

现在想象一下,前⾯面这个兼容WSGI的Web服务器是通过调用应用程序app来提供服务的,现在将 其配置为调用TrivialMiddleware(app)来提供服务。TrivialMiddleware的构造函数把app作为参数,因此 初始化能正常完成。对于每个请求而言,服务器不再调用app(environ,start_response),而是调用TrivailMiddleware实例的call方法。上例中这个方法只传回app(environ,start_response)的值。到目前 为止,我们还没有做任何事情,但是我们展示了如何构建一个前面所述的中间件管道。

数据流和数据的修改

虽然我们上面介绍的TriviaMiddleare类只是简单地把响应结果一成不变地返回给用户,但是要编 写一个修改响应的中间件也是很简单的。请您记住,应用程序返回值是一个可以迭代的值。这可以让 数据量大的响应以流的形式传递而不是一次性全部读入Web服务器的内存中。如果您知道您的响应数 据量不大,您可以选择在内存中实例化整个响应,但我们不建议这样做。这里有一个中间件实例,它 可以让所有的响应实体中的字符统一变成大写字母,它就是一次性读取整个响应并把它送入内存当 中:

部署Swift对象存储中间件(部署Swift对象存储中间件)(5)

这里还有另外一个实现,它返回一个生成器而不是一个列表,这样就可以避免读取整个响应到内 存中:

部署Swift对象存储中间件(部署Swift对象存储中间件)(6)

在这种情况下,让中间件以流的方式处理数据实际上没有增加任何代价,并且在处理大的响应方面更有优势。在一些复杂的情况下,为了简化代码,可以⼀次性把完整的响应读入内存中来。如果您 能确定您将处理的响应的大小,您可以这样做——但是必须万分谨慎,特别是在处理大规模存储的时 候!

如果最里层的应用程序调⽤用了start_response,那么在外层的中间件如何改变头部呢?当外部中间 件需要这样做的时候——⽐方说添加一个新的头部——这时要给管道中的下一个中间件传递的 start_response方法和前面稍有不同,如下面代码所示:

部署Swift对象存储中间件(部署Swift对象存储中间件)(7)

这样,是不是很古怪!改变响应本来应该是很直接的,但我们并不直接更改头部,而是通过一个 函数完成,如果该函数被调⽤,除了完成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:

部署Swift对象存储中间件(部署Swift对象存储中间件)(8)

这是一个非常简单的配置文件,缺失了很多必要的配置设置(如auth中间件),但是对于讨论Paste 部署来说很有用。

在这个管道中,核心的代理服务代码被包围在两个中间件过滤器中间:最外层healthcheck和中间 层的proxy-logging。如果一个请求被healthcheck中间件拦截(不经过中间件管道中的其他部分而直接 返回一个响应),那么proxy-logging将永远看不到该请求,并且核心代理服务进程的代码也看不到。

配置文件也定义了各种管道组件将会用到的配置变量。例如,disable_path配置变量 由healthcheck使用。顾名思义,如果指定的文件存在,健康检查将不可⽤用。

上面例子中管道使用的中间件被打包成Python “eggs”。如果你想安装的中间件并没有被打包成 “egg” 呢?(这种情况在测试自定义中间件,甚至在生产环境安装自定义中间件都很常见)。在这种情 况下,只需要用paste.filter_factory声明替换配置文件的use声明:

部署Swift对象存储中间件(部署Swift对象存储中间件)(9)

Paste提供了一种机制让中间件能够取到自己所需要的配置变量。第一步就是每节中的use设置, 它给出了打包该中间件的Python egg的位置(若中间件没被打包到egg,由paste.filter_factory设置起 到相同的作用)。这个值告诉Paste在哪能找到该filter或app的定义。在Swift默认安装情况下,所有的 ⾮自定义中间件和Swift⼀起安装在Python egg-info目录下。这个目录包含了entry-points.txt⽂文件,内 容如下所示:

部署Swift对象存储中间件(部署Swift对象存储中间件)(10)

正如您猜测的,paste.app_factory和paste.filter_factory定义了应用程序和过滤器名称以供配置文 件引用。(应用程序和过滤器的不同仅仅在于应用程序位于管道的最末端,因此它里面没有下⼀层中 间件接收请求,应用程序对它收到的所有请求都会产生响应)。⽐方说,当paste.filter_factory在管道 中检测到healthcheck时,它可以调用swift.commom.middleware.healthcheck模块中的filter_factory方 法来进行实例化。如果查询该模块的代码,您将发现一个像下⾯的包装函数:

部署Swift对象存储中间件(部署Swift对象存储中间件)(11)

这个包装函数允许Paste定义如何让管道的每个元素包装(过滤)适当的内部层。这里,filter_factory是以配置字典和关键字为参数的函数,该函数的返回值是另一个函数(本例中 是healthcheck_filter),后者在HealthCheckMiddleware里包装了⼀个WSGI app(比如代理服务器的 核心代码,或者已经包装了其他中间件的代理服务器代码),而前⾯面传入的参数也被传给 HealthCheckMiddleware,以对它进行设置。

如何编写Swift中间件

让我们来看一个较为完整的HealthCheckMiddleware中间件的例子。它的功能如下:

部署Swift对象存储中间件(部署Swift对象存储中间件)(12)

在这里有一个新的特性:那就是使用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应用、管理与开发》。

注:本文只代表作者个人观点,与任何组织机构无关,如有错误和不足之处欢迎在留言中批评指正。如果您想在这个公众号上分享自己的技术干货,也欢迎联系我:)

尊重知识,转载时请保留全文。感谢您的阅读和支持!

原文链接:https://mp.weixin.qq.com/s?__biz=MzAwODExNjI3NA==&mid=2649776495&idx=1&sn=717e501406d81c35dfbd10418afb1538&chksm=83770032b4008924a7ec7fd1b0dcd3d80828027042d8aea522f6838a91ace75de78457a612b9#rd

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页