说起较大型系统的源码阅读,算上目前正在进行的Swift,也就只有两次经验(去年的上半年有阅读过学习过的源码)。虽说还是菜鸟级别啦,但两次也可以总结经验嘛:P,哈哈~
我的这个经验呢,就是对于这种服务器端的源码,最好首先对系统的“启动过程”和请求到来时的“data flow”进行一遍跟踪阅读,了解程序的运作流程以及各个关键类、方法之间的关系,然后再从这条主线进行各个分叉流程的细致学习。这种方式一来目的性比较强,代码比较容易看得下去,二来不会有越看越茫然的感觉,总体上是逐渐清晰了解细节的过程。
好啦,废话说完咯,进入正题。这两周开始了swift源码(swift 1.8.0)的学习,首先就按照以上所述的流程进行了一遍粗浅的阅读,现在总结一下。
Swift启动流程=========================================================
在ubuntu上部署完毕swift后,我们知道可以执行脚本 startmain 启动swift,那么这条命令执行后,swift究竟做了哪些启动相关的工作呢?
1. 概况
startmain 脚本实际上是执行了以下这条命令:
swift-init main start
即为swift执行初始化工作,而该命令实际上又分别执行了swift-proxy-server、swift-account-server、swift-container-server、swift-object-server 这四个swift bin中的python命令。接下来,让我们看一下在swift代码中都做了些什么。
2. swift-init
swift-init 是启动swift的脚本,改脚本可以跟几个参数:swift-init [主体] [动作]。在“swift-init main start”中main就对应主体,动作即为start。
1)swift-init main start 执行后,首先看swift-init代码 line61~line62,在这里对命令和参数进行了分割,从而决定接下来的执行任务:
command = args[- 1] #command此处为startservers = args[:-1] #servers此处为main
2)在swift-init代码line 70,创建了Manager的实例,Manager是直接管理各个servers的类:
manager = Manager(servers, run_dir=options.run_dir) # 这里执行了Manager的__init__
3)由于在上一步中,swift-init创建了Manager实例,因此调用了Manager的__init__初始化方法,在__init__方法中,我们可以看到对命令参数servers(这里是“main”)的处理(line 137)。
elif server == 'main': server_names.update(MAIN_SERVERS)
其中MAIN_SERVERS为manager.py中定义的全局数组,指明了swift中servers的启动脚本(line 37)
MAIN_SERVERS = ['proxy-server', 'account-server', 'container-server', 'object-server']
status = manager.run_command(command, **options.__dict__)
到这里,swift中的各个servers就被启动起来了,接下来让我们以proxy-server为例,看看在这些swift-xx-server中都做了些什么。
3. swift-xx-server的启动
在swift-xx-server中最终调用了相应的xx.server.py中相应类的__init__(以proxy为例):
run_wsgi(conf_file, 'proxy-server', default_port=8080 , **options)
我们可以看到swift的HTTP交互是基于WSGI实现的,并且它使用了paste deploy进行配置文件的管理,从而通过xx-server.conf配置文件load相应的wsgi app。在run_wsgi方法中,主要执行了以下四个步骤:
1)run_wsgi方法根据参数load配置文件
2)根据配置文件绑定的端口创建socket,将socket与server绑定
3)run_server方法调用proxy的server的__init__方法
4)__init__方法根据配置文件初始化server的内部状态,从而gets the server ready to handle requests
至此,Swift中的各个servers就已经stand by啦!只待request的到来!OK,那么接下来让我们来看一下当一个request到来时,swift中的data flow又是怎样的吧 : )
Swift Data Flow=========================================================
在上文中呢,我已经提到过swift的HTTP交互框架是基于wsgi实现的,并且使用paste deploy实现通过conf来load app,因此如果你希望可以更好的了解swift的交互流程和wsgi-style application标准,那么建议你先学习一下 以及 的配置文件含义。
首先,来看一张俺画滴图!这张图基本上就把整个请求过程的data flow说清楚啦!(原谅我无耻的打了水印,哈哈哈哈哈)
让我们按照图中的流程来解释吧。
1. Send a Request
首先,用户也就是Client向swift发出一个request,该请求通过proxy-server.conf中配置的bind_port进入swift;
2. WSGI middleware: Pipeline
这里先给出一份proxy-server.conf的配置内容,我们来结合这个paste deploy配置文件进行pipeline的说明:
[DEFAULT]bind_port = 8080user = rootlog_facility = LOG_LOCAL1eventlet_debug = true[pipeline:main]pipeline = healthcheck cache tempauth proxy-logging proxy-server[app:proxy-server]use = egg:swift#proxyallow_account_management = trueaccount_autocreate = true[filter:tempauth]use = egg:swift#tempauthuser_admin_admin = admin .admin .reseller_adminuser_test_tester = testing .adminuser_test2_tester2 = testing2 .adminuser_test_tester3 = testing3[filter:healthcheck]use = egg:swift#healthcheck[filter:cache]use = egg:swift#memcache[filter:proxy-logging]use = egg:swift#proxy_logging
请求需要经过wsgi pipeline中的层层过滤器才能最终到达请求的应用:proxy-server。请注意配置文件中加粗的部分:
[pipeline:main]表明这里是application的入口,pipeline代表一些列的filters,main则类似各种编程语言中的main方法指明入口。
pipeline = filter1 filter2 ... filterN application 的最后一个元素必须是一个符合wsgi的callable对象,具体到这个例子中,表明请求必须经过healthcheck、cache tempauth、proxy-logging这四个filter的过滤后,才能到达proxy-server。如果请求没有通过任何一个过滤器,则请求将会被返回相应的错误信息,不能到达proxy-server。
这些filters和最后的app都必须是符合WSGI标准的callable application,在程序执行时,依次调用它们的__call__方法,让我们以healthcheck为例,来看看它的__call__:
def __call__(self, env, start_response): req = Request(env) try: if req.path == '/healthcheck': handler = self.GET if self.disable_path and os.path.exists(self.disable_path): handler = self.DISABLED return handler(req)(env, start_response) except UnicodeError: # definitely, this is not /healthcheck pass return self.app(env, start_response)
我们可以看到,这个方法必须包含两个参数(这里是类中的方法,因此还有一个self)env和start_response,env是wsgi中的环境变量字典,start_response用于产生response的结果,这个方法返回的self.app(env, start_response)的这个app其实就是它的下一个filter:cache,cache也会以相同的方式执行,直到调用proxy-server,proxy-server负责产生相应的Response并返回。
3. Proxy-Server.__call__
假设Client发出的这个request非常的合法,他通过了所有的filters,现在终于到达了proxy-server,那接下来又会发生什么呢?
proxy-server这个application实际上对应swift源码proxy包下的server.py中的Application类,如同我们在上文中说到的,这里会调用Application.__call__。在Application.__call__中,对请求做了一些校验,合法后,就调用它的handle_request方法,顾名思义的来handle这个request。
我们知道在swift中请求可以针对account、container和object,显然它们所对应的控制器是不一样的,因此在handle_request方法中又继续调用了get_controller方法。
get_congroller方法根据request path中的http://xxxx/account[container/object]/xxx的子路径来判断这个request想要请求的部分,从而获取对应的controller(AccountController、ContainerController、ObjectController)。细心的你一定会检查一下swift源码中的account包、container包、obj包中的server.py吧,没错AccountController、ContainerController、ObjectController就分别位于这些server.py中!
4. Controllers
现在,我们已经确定了这个request对应的controller啦!那么它的请求动作又是什么呢?HEAD?GET?其实这一切已经在HTTP Verb中定义了,因此在handle_request方法中会继续调用controller实例中对应的XXX方法(如HEAD/PUT/GET/POST/DELETE),这些方法最终对这个request做出处理,产生响应,返回相应的Request!
5. 返回Request
最后这个Request就沿着原路一层层的返回到Client。Swift也继续该干嘛干嘛等待它来自四面八法的Requests。
至此,一个比较outline的请求处理过程就算介绍完毕了。我在这里省略了很多的细节和校验工作,只是为了让大家跟我一样,在diving into swift source code前先知道它的水大概有多深,路线大概是个什么样子的。至于每一步的细节,和精准的角度,还需要在后续一步步的来看,一步步的充实、学习。
==================================全文完结分割啦啦啦啦======================================
本文写于慌乱之际,说慌乱,是因为我对Python实在是一知半解,很多地方都是现学的(比如WSGI、pastedeploy),再加上这是对swift源码第一遍的粗浅了解,所以难免有些不正确的地方,麻烦各位有什么想法的话多多交流,一起diving啦 =D