Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1125625
  • 博文数量: 170
  • 博客积分: 1603
  • 博客等级: 上尉
  • 技术积分: 1897
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-09 15:54
文章分类

全部博文(170)

文章存档

2016年(27)

2015年(21)

2014年(27)

2013年(21)

2012年(7)

2011年(67)

我的朋友

分类: 虚拟化

2016-09-23 12:20:25

我们以keystone为例一步步看wsgi服务器的启动语url映射

截取一部分ini文件先做大致说明

点击(此处)折叠或打开

  1. [composite:main]
  2. use = egg:Paste#urlmap
  3. /v2.0 = public_api
  4. /v3 = api_v3
  5. / = public_version_api
  6. urlmap = paste.urlmap:urlmap_factory


  7. ][pipeline:api_v3]
  8. # The last item in this pipeline must be service_v3 or an equivalent
  9. # application. It cannot be a filter.
  10. pipeline = cors sizelimit url_normalize request_id admin_token_auth build_auth_context token_auth json_body ec2_extension_v3 s3_extension service_v3


  11. [filter:token_auth]
  12. use = egg:keystone#token_auth
  13. token_auth = keystone.middleware:TokenAuthMiddleware.factory


  14. [app:service_v3]
  15. use = egg:keystone#service_v3
  16. service_v3 = keystone.version.service:v3_app_factory

当一个请求过来的时候,先去composite:main找到对应url path
比如来了一个v3请求, 这时候被映射到pipeline:api_v3
那么这个请求会被pipeline:api_v3列表中的所有filter处理一遍,里面每个filter都要定义,我摘取了其中一个filter

pipeline的最后将是一个app 即可 service_v3
匹配到app:service_v3, 数据将在app:service_v3中被处理

问题1:名字相同怎么办——不会存在名字相同,否则会在启动时报错
问题2:这玩意怎么工作的——这就比较复杂了 


urlmap映射用的是paste.deploy
paste是一个包,包名叫Paste,rpm名字为python-paste,可以yum
deploy是paste的扩展包,包名字叫PasteDeploy,这玩意要自己打包,版本很久没更新了,最新1.5.2
附上spec文件

点击(此处)折叠或打开

  1. %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(0)")

  2. Name: python-PasteDeploy
  3. Version: 1.5.2
  4. Release: 0%{?dist}
  5. Summary: Load, configure, and compose WSGI applications and servers
  6. Group: Libraries/Python
  7. License: Python
  8. URL:
  9. Source0: PasteDeploy-%{version}.tar.gz
  10. BuildArch: noarch
  11. BuildRequires: python-devel
  12. BuildRequires: python-setuptools >= 0.6-0.a9.1
  13. Requires: python >= 2.6
  14. Requires: python-paste

  15. %description
  16. This tool provides code to load WSGI applications and servers from
  17. URIs; these URIs can refer to Python Eggs for INI-style configuration
  18. files. Paste Script provides commands to serve applications based on
  19. this configuration file.

  20. %prep
  21. %setup -q -n PasteDeploy-%{version}


  22. %build
  23. CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build


  24. %install
  25. %{__python} setup.py install -O1 --skip-build --root %{buildroot}
  26. install -D -m 644 README %{buildroot}%{_docdir}/%{name}-%{version}
  27. install -D -m 644 PKG-INFO %{buildroot}%{_docdir}/%{name}-%{version}
  28. install -D -m 644 MANIFEST.in %{buildroot}%{_docdir}/%{name}-%{version}
  29. #install -D -m 644 docs/news.txt %{buildroot}%{_docdir}/%{name}-%{version}
  30. #install -D -m 644 docs/license.txt.in %{buildroot}%{_docdir}/%{name}-%{version}
  31. #install -D -m 644 doc/index.txt %{buildroot}%{_docdir}/%{name}-%{version}


  32. %files
  33. %doc README PKG-INFO MANIFEST.in
  34. %{python_sitearch}/paste/deploy
  35. %{python_sitearch}/PasteDeploy-%{version}-py*.egg-info
  36. %{python_sitearch}/PasteDeploy-%{version}-py*-nspkg.pth

  37. %changelog
  38. * Sat Aug 20 2016 gcy - 1.5.2
  39. - gcy build it for Redhat EL 6

paste.deploy这玩意比较蛋痛,通过egg来动态载入模块(openstack里使用import util)
所以必须安装setuptool,并要求python的包里带有egg信息
因为这玩意load的时候,都是通过包内egg-info下的entry_points.txt来映射的
用起来有点像java的struct, 所以python的rpm包的时候必须带入egg info

deploy中有3种load函数
loadapp
配置中的composite:、pipeline:、app:都由他来处理(还有一个filter-app,keyston未使用)
因为这三个开头的都是用loadapp,所以他们的后面的name不可以相同(会报错)
比如有了composite:main,就不能再有app:main

loadfilter
配置中的filter:都由他处理

loadserver  
配置中的server:都由他处理(keyston未使用)
但是根据代码中的说明,打算废除绿色来启动wsig的socket监听
所以以后有可能直接在这里通过loadserver来启动socket监听


我们现在来看启动过程
init里直接调用的keyston-all来启动
实际就是启动两个server

点击(此处)折叠或打开

  1. 通过绿色线程启动两个进程的过程大致如下 public_worker_count必须>=2,不配置的话直接用cpu的数量,有可能报错
  2. def create_servers():
  3.     admin_worker_count = _get_workers('admin_workers')
  4.     public_worker_count = _get_workers('public_workers')

  5.     servers = []
  6.     servers.append(create_server(paste_config,
  7.                                  'admin',
  8.                                  CONF.eventlet_server.admin_bind_host,
  9.                                  CONF.eventlet_server.admin_port,
  10.                                  admin_worker_count))
  11.     servers.append(create_server(paste_config,
  12.                                  'main',
  13.                                  CONF.eventlet_server.public_bind_host,
  14.                                  CONF.eventlet_server.public_port,
  15.                                  public_worker_count))
  16.     return servers
后面

点击(此处)折叠或打开

  1. def create_server(conf, name, host, port, workers):
  2.     app = keystone_service.loadapp('config:%s' % conf, name)
实际调用

点击(此处)折叠或打开

  1. def loadapp(conf, name):
  2.     # NOTE(blk-u): Save the application being loaded in the controllers module.
  3.     # This is similar to how public_app_factory() and v3_app_factory()
  4.     # register the version with the controllers module.
  5.     controllers.latest_app = deploy.loadapp(conf, name=name)
  6.     return controllers.latest_app

两个server对应的name分别为admin和main,都使用loadapp启动
因为调用的是deploy.loadapp,所以会匹配到[composite:main]和[composite:admin]这两段配置
顺便可以看出到有使用全局变量,所以两个app至少两个进程,所以public_worker_count必须>=2
不配置的话直接用cpu的数量,有可能报错

上面划线的解释是错误的,详细看了下eventlet的封装里初始化了全局变量
不存在因为全局变量问题必须多进程
public 和admin的 worker count可以多进程也可以单进程
主进程在调用launch_service之前(最终fork之前)先调用了listen  
fork后通过self.launcher = self._child_process(wrap.service)下的launcher.launch_service(service)启动循环
socket数据接收直接在各个子进程 通过dup_socket = self.socket.dup() 复制出来的socket accept(看下面的说明来理解多进程接受数据)
socket如何接受数据 处理分包粘包都是在eventlet.wsgi的代码中

点击(此处)折叠或打开

  1. 对于监听一个socket来说,多个进程同时在accept处阻塞,当有一个连接进入,多个进程同时被唤醒,但之间只有一个进程能成功accept,而不会同时有多个进程能拿到该连接对象,操作系统保证了进程操作这个连接的安全性。

  2. 扩展:上述过程,多个进程同时被唤醒,去抢占accept到的资源,这个现象叫“惊群”,而根据网上资料,Linux 内核2.6以下,accept响应时只有一个进程accept成功,其他都失败,重新阻塞,也就是说所有监听进程同时被内核调度唤醒,这当然会耗费一定的系统资源。 而2.6以上,则已经不存在惊群现象了,但是由于开发者开发程序时使用了如epoll等异这时子进程保存了父进程的文件描述符的所有副本,可以进程跟父进程一样的操作了。
我之前一直以为复制的socket的fd和普通文件的fd一样会有共同读写问题
原来操作系统级别已经避免掉这问题了
因为eventlet中socket accept的时候没有用异步,所以这里的多进程写起来就很简单了
现在终于搞清楚了,顺便,每个进程中也使用了协程来支持多个请求

点击(此处)折叠或打开

  1. def loadapp(uri, name=None, **kw):
  2.     return loadobj(APP, uri, name=name, **kw)

  3. def loadobj(object_type, uri, name=None, relative_to=None,
  4.             global_conf=None):
  5.     context = loadcontext(
  6.         object_type, uri, name=name, relative_to=relative_to,
  7.         global_conf=global_conf)
  8.     return context.create()
第一次调用loadcontext传入的url,也就是paste_config
值通过解析conf文件转为   config:/etc/keystone-paste.ini
name就是上层传入的name,下面我们以name为main做流程解析
url.split(‘:’) 后通过冒号前面的config找到实际调用函数

点击(此处)折叠或打开

  1. def _loadconfig(object_type, uri, path, name, relative_to,
  2.                 global_conf):
里面生成ConfigLoader类(就是我们常用解析ini文件的类的封装),并调用类中的get_context方法
get_context会通过传入的name和APP类的config_prefixes获取到section([composite:main])的内容并放入local_conf变量中
当section是pipeline或者local_conf中有"use"这个key的时候,有对应递归(看这里大致明白pipeline是怎么一级一级的调用filter了),
由于local_conf的key里有"use"(use = egg:Paste#urlmap),调用_context_from_use
_context_from_use内部local_conf.pop弹出use对应values作为name(这次name为egg:Paste#urlmap)再调用get_context
这次又走到了loadcontext中实际函数为

点击(此处)折叠或打开

  1. def _loadegg(object_type, uri, spec, name, relative_to,
  2.              global_conf):

生成EggLoaderload类,
object_type为APP,name是urlmap,spec为Paste
调用这个类中get_context返回LoaderContext类
get_context中
entry_point, protocol, ep_name = self.find_egg_entry_point(object_type, name=name)
这里通过setuptool的对应工具找到Paste(spec)的egg配置中的paste.composite_factory即(通过APP(object_type)类的egg_protocols去匹配)
[paste.composite_factory]
urlmap = paste.urlmap.urlmap_factory
urlmap也就是paste.urlmap.urlmap_factory
方法最后并返回LoaderContext类(LoaderContext的属性loader本来应该是EggLoaderload,但是_context_from_use返回前把LoaderContext的loader覆盖为self,也就是ConfigLoader)
_context_from_use在返回LoaderContext之前还处理了下LoaderContext中的protocol
后面的处理应该是为了pipeline做的



回到前面的loadobj,这时候最后的create则是LoaderContext中的

点击(此处)折叠或打开

  1. def create(self):
  2.     return self.object_type.invoke(self)

object_type为APP
看APP的invoke

点击(此处)折叠或打开

  1. def invoke(self, context):
  2.     if context.protocol in ('paste.composit_factory',
  3.                             'paste.composite_factory'):
  4.         return fix_call(context.object,
  5.                         context.loader, context.global_conf,
  6.                         **context.local_conf)
  7.     elif context.protocol == 'paste.app_factory':
  8.         return fix_call(context.object, context.global_conf, **context.local_conf)
  9.     else:
  10.         assert 0, "Protocol %r unknown" % context.protocol

  11.         
  12. def fix_call(callable, *args, **kw):
  13.     """
  14.     Call ``callable(*args, **kw)`` fixing any type errors that come out.
  15.     """
  16.     try:
  17.         val = callable(*args, **kw)
  18.     except TypeError:
  19.         exc_info = fix_type_error(None, callable, args, kw)
  20.         reraise(*exc_info)
  21.     return val
 
上面context.object就是就是之前传入的的entry_point,也就是urlmap_factory

点击(此处)折叠或打开

  1. def urlmap_factory(loader, global_conf, **local_conf):
  2.     if 'not_found_app' in local_conf:
  3.         not_found_app = local_conf.pop('not_found_app')
  4.     else:
  5.         not_found_app = global_conf.get('not_found_app')
  6.     if not_found_app:
  7.         not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
  8.     urlmap = URLMap(not_found_app=not_found_app)
  9.     for path, app_name in local_conf.items():
  10.         path = parse_path_expression(path)
  11.         app = loader.get_app(app_name, global_conf=global_conf)
  12.         urlmap[path] = app
  13.     return urlmap
到这里local_conf还剩下
/v2.0 = public_api
/v3 = api_v3
/ = public_version_api
通过ConfigLoader的get_app(注意前面红字部分),完成url的与调用app的map映射(字典)
到这里,path匹配到对应的处理方法完成
urlmap key对应的value作为wsgi的app, 接收wsgi传入的数据
app的具体初始化过程参考
http://blog.chinaunix.net/uid-23504396-id-5754581.html


后面就是通过绿色线程(eventlet.wsgi.server)处理监听端口过来的数据
以key(url path)   values(对应的映射方法)形式分发传入的数据到不同的app进行处理  

顺便,在nova-api中,配置文件是这样的
[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory
匹配会走到
_context_from_explicit
这样就可以不绕egg找一圈直匹配到对应的类了

至于为什么有些用use = egg
比如keystone里
[app:service_v3]
use = egg:keystone#service_v3
而nova-api里直接引过去

我反正是不打算纠结了,知道怎么映射过去的就差不多了 

阅读(1916) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~