分享

Glance源码架构分析(二)

w123aw 发表于 2014-11-23 12:50:39 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 22127
本帖最后由 w123aw 于 2014-11-23 13:09 编辑

问题导读



1.WSGI程序的作用是什么?
2.application函数要实现两个接口,environ和start_response(),他们的作用是什么?









上一章的最后,为大家介绍了Glance服务的对外启动接口/bin/glance-api,其中最用要的部分就是通过server = eventlet.wsgi.Server()生成了一个http server,并通过server.start()启动了一个WSGI程序。
首先为大家说说这个WSGI程序的作用。WSGI(web server gateway interface)web服务器网关接口。简单来说,WSGI的作用就是将client发给web server的请求转发给要实际处理这个请求的程序。
WSGI在Python中有官方的参考实现wsgiref,http://docs.python.org/2/library/wsgiref.html#module-wsgiref
  1. from wsgiref.simple_server import make_server  
  2.   
  3. # Every WSGI application must have an application object - a callable  
  4. # object that accepts two arguments. For that purpose, we're going to  
  5. # use a function (note that you're not limited to a function, you can  
  6. # use a class for example). The first argument passed to the function  
  7. # is a dictionary containing CGI-style envrironment variables and the  
  8. # second variable is the callable object (see PEP 333).  
  9. def hello_world_app(environ, start_response):  
  10.     status = '200 OK' # HTTP Status  
  11.     headers = [('Content-type', 'text/plain')] # HTTP Headers  
  12.     start_response(status, headers)  
  13.   
  14.     # The returned object is going to be printed  
  15.     return ["Hello World"]  
  16.   
  17. httpd = make_server('', 8000, hello_world_app)  
  18. print "Serving on port 8000..."  
  19.   
  20. # Serve until process is killed  
  21. httpd.serve_forever()  
复制代码


参考流程大概就是server中定义一个start_response()函数,返回值为一个write()函数,用来返回给client的响应。application函数要实现两个接口参数,environ和start_response(),前者就是服务器server传递过来的request请求,application控制后者将程序的返回值发回给web server。

这并不是本文所要讨论的重点,如有兴趣可自行做引申阅读。下面我们分析openstack中wsgi接口的实现,/glance/common/wsgi.py
  1. class Server(object):  
  2.     """Server class to manage multiple WSGI sockets and applications."""  
  3.   
  4.     def __init__(self, threads=1000):  
  5.         self.threads = threads  
  6.         self.children = []  
  7.         self.running = True  
  8.   
  9.     def start(self, application, default_port):  
  10.         """
  11.         Run a WSGI server with the given application.
  12.         :param application: The application to be run in the WSGI server
  13.         :param default_port: Port to bind to if none is specified in conf
  14.         """  
  15.         def kill_children(*args):  
  16.             """Kills the entire process group."""  
  17.             self.logger.info(_('SIGTERM or SIGINT received'))  
  18.             signal.signal(signal.SIGTERM, signal.SIG_IGN)  
  19.             signal.signal(signal.SIGINT, signal.SIG_IGN)  
  20.             self.running = False  
  21.             os.killpg(0, signal.SIGTERM)  
  22.   
  23.         def hup(*args):  
  24.             """
  25.             Shuts down the server, but allows running requests to complete
  26.             """  
  27.             self.logger.info(_('SIGHUP received'))  
  28.             signal.signal(signal.SIGHUP, signal.SIG_IGN)  
  29.             self.running = False  
  30.   
  31.         self.application = application  
  32.         self.sock = get_socket(default_port)  
  33.   
  34.         os.umask(027)  # ensure files are created with the correct privileges  
  35.         self.logger = os_logging.getLogger('eventlet.wsgi.server')  
  36.   
  37.         if CONF.workers == 0:  
  38.             # Useful for profiling, test, debug etc.  
  39.             self.pool = self.create_pool()  
  40.             self.pool.spawn_n(self._single_run, self.application, self.sock)  
  41.             return  
  42.         else:  
  43.             self.logger.info(_("Starting %d workers") % CONF.workers)  
  44.             signal.signal(signal.SIGTERM, kill_children)  
  45.             signal.signal(signal.SIGINT, kill_children)  
  46.             signal.signal(signal.SIGHUP, hup)  
  47.             while len(self.children) < CONF.workers:  
  48.                 self.run_child()  
  49.   
  50.     def create_pool(self):  
  51.         eventlet.patcher.monkey_patch(all=False, socket=True)  
  52.         return eventlet.GreenPool(size=self.threads)  
  53.   
  54.     def wait_on_children(self):  
  55.         while self.running:  
  56.             try:  
  57.                 pid, status = os.wait()  
  58.                 if os.WIFEXITED(status) or os.WIFSIGNALED(status):  
  59.                     self.logger.info(_('Removing dead child %s') % pid)  
  60.                     self.children.remove(pid)  
  61.                     if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0:  
  62.                         self.logger.error(_('Not respawning child %d, cannot '  
  63.                                             'recover from termination') % pid)  
  64.                         if not self.children:  
  65.                             self.logger.info(  
  66.                                 _('All workers have terminated. Exiting'))  
  67.                             self.running = False  
  68.                     else:  
  69.                         self.run_child()  
  70.             except OSError, err:  
  71.                 if err.errno not in (errno.EINTR, errno.ECHILD):  
  72.                     raise  
  73.             except KeyboardInterrupt:  
  74.                 self.logger.info(_('Caught keyboard interrupt. Exiting.'))  
  75.                 break  
  76.         eventlet.greenio.shutdown_safe(self.sock)  
  77.         self.sock.close()  
  78.         self.logger.debug(_('Exited'))  
  79.   
  80.     def wait(self):  
  81.         """Wait until all servers have completed running."""  
  82.         try:  
  83.             if self.children:  
  84.                 self.wait_on_children()  
  85.             else:  
  86.                 self.pool.waitall()  
  87.         except KeyboardInterrupt:  
  88.             pass  
  89.   
  90.     def run_child(self):  
  91.         pid = os.fork()  
  92.         if pid == 0:  
  93.             signal.signal(signal.SIGHUP, signal.SIG_DFL)  
  94.             signal.signal(signal.SIGTERM, signal.SIG_DFL)  
  95.             # ignore the interrupt signal to avoid a race whereby  
  96.             # a child worker receives the signal before the parent  
  97.             # and is respawned unneccessarily as a result  
  98.             signal.signal(signal.SIGINT, signal.SIG_IGN)  
  99.             self.run_server()  
  100.             self.logger.info(_('Child %d exiting normally') % os.getpid())  
  101.             # self.pool.waitall() has been called by run_server, so  
  102.             # its safe to exit here  
  103.             sys.exit(0)  
  104.         else:  
  105.             self.logger.info(_('Started child %s') % pid)  
  106.             self.children.append(pid)  
  107.   
  108.     def run_server(self):  
  109.         """Run a WSGI server."""  
  110.         if cfg.CONF.pydev_worker_debug_host:  
  111.             utils.setup_remote_pydev_debug(cfg.CONF.pydev_worker_debug_host,  
  112.                                            cfg.CONF.pydev_worker_debug_port)  
  113.   
  114.         eventlet.wsgi.HttpProtocol.default_request_version = "HTTP/1.0"  
  115.         try:  
  116.             eventlet.hubs.use_hub('poll')  
  117.         except Exception:  
  118.             msg = _("eventlet 'poll' hub is not available on this platform")  
  119.             raise exception.WorkerCreationFailure(reason=msg)  
  120.         self.pool = self.create_pool()  
  121.         try:  
  122.             eventlet.wsgi.server(self.sock,  
  123.                                  self.application,  
  124.                                  log=WritableLogger(self.logger),  
  125.                                  custom_pool=self.pool)  
  126.         except socket.error, err:  
  127.             if err[0] != errno.EINVAL:  
  128.                 raise  
  129.         self.pool.waitall()  
复制代码





大家可能看了代码之后又觉得头痛,其实没关系,前面有许多行无非是设置log写入程序,打开conf读取ip,port等信息,最重要的内容就是代码最后几行中的


  1. self.pool = self.create_pool()  
  2. eventlet.wsgi.server(self.sock,self.application,log=WritableLogger(self.logger),custom_pool=self.pool)  
复制代码



刚才提到了server和application之间的通讯接口WSGI,现在我们要讲讲server。OpenStack并没有使用Python标准库中的BaseHTTPServer,而是使用了在网络并发等领域处理效率非常优异的eventlet库http://eventlet.net/
eventlet提供了一套API以实现“协程”(coroutines)。所谓的“协程”可以简单的看做是“假线程”,他可以实现线程的非阻塞异步IO调用的功能,但是协程没有独立的堆栈,这和线程有自己独立的堆栈是有区别的。eventlet会维护一个协程“池”,用来存放所有创建的协程。但是不同于线程,协程同时只能有一个实例在运行,其他的协程要运行,必须等待当前协程显式的被挂起。不同于线程的执行顺序随机,协程的执行时按调用顺序的。
OpenStack的服务,没用使用市面上常见的web server的原因大概就是其处理并发无非就是使用多线程或IO复用等。然而,当多客户端并发访问时,OpenStack内部的一些共享资源,并不能十分安全的利用互斥锁等方法进行线程共享资源的互斥。为了防止并发出现资源死锁,简化架构设计流程,采用“协程”是个非常不错的选择。并且,线程间的切换需要大量的时间和空间的开销,而协程可以有效的避免这个问题。

  1. import eventlet  
  2.   
  3. def handle(client):  
  4.     while True:  
  5.         c = client.recv(1)  
  6.         if not c: break  
  7.         client.sendall(c)  
  8.   
  9. server = eventlet.listen(('0.0.0.0', 6000))  
  10. pool = eventlet.GreenPool(10000)  
  11. while True:  
  12.     new_sock, address = server.accept()  
  13.     pool.spawn_n(handle, new_sock)  
复制代码

上面是eventlet一个简单服务器端的示例,首先用eventlet.GreenPool(1000)生成一个最大容量为1000的“协程”缓冲池,server.accept()等待,服务器server端收到一个客户端的连接请求,就用pool.spawn_n()启动一个“协程”进行处理响应。
回到glance中,OpenStack将Python原版的CGIHTTPServer进行“绿化”,提供了eventlet.wsgi.server进行http的响应,其内部实现结构和作用和上面的代码相似,同样都是来一个http请求,就会启动一个协程server进行响应。参数custom_pool就是我们上面刚刚申请的GreenPool协程池。参数self.application为WSGI程序入口。这样我们就成功运行了一个WSGI服务程序。
本章结束,我们已经成功运行启动了Glance的WSGI服务,下一章将会开始具体介绍WSGI程序所使用的请求分发组件Routes和request与response的包装类webob的相关内容。

本章提到了些高并发访问处理方面的相关内容,实际上eventlet协程的设置上也使用了eventlet.hubs.use_hub('poll'),欢迎大家继续展开阅读与讨论Select I/O poll epoll等相关内容。


相关文章:

Glance源码架构分析(一)
http://www.aboutyun.com/thread-10237-1-1.html


Glance源码架构分析(三)
http://www.aboutyun.com/thread-10239-1-1.html

Glance源码架构分析(四)
http://www.aboutyun.com/thread-10240-1-1.html



作者:Ethan_熠森张



没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条