Cloud Foundry新版router源码分析

Cloud Foundry在几个月前改进了它的router,我们可以在新版cloudfoundry解密中看到它的介绍:

上个版本中。Router作为一个nginx脚本存在。所以的请求都必须经过Ruby代码,然后加以转发。这个设计干净利落,不过Ruby也因此转发了大量的数据,容易引起性能问题,所以下个版本中做了如下的改进。

Cloud Foundry新版router源码分析

在新版本的设计中,他们使用Lua脚本来代替原先的Ruby脚本。而Lua脚本会对请求加以分析,转发给Ruby程序,然后Ruby程序再将分析的结果返回。这样一来,proxied request已经不再经过Ruby代码。逻辑和数据完美分离。性能和稳定性都大幅提高了。

在前版设计中,当Router接收到请求后,会随机分配一个Droplet来处理这个请求,这种方式使得用户没有办法使用Session,因为连续的HTTP请求会被分发到不同的应用实例上处理。新版本设计中增加了对SESSION的支持,当Router发现用户的请求中带了cookie信息,它会在Cookie里暗藏一个Droplet的host,port地址。当有新的请求进来,Router通过解析Cookie得到上次的应用实例,然后尽量转发到同一台Droplet上。

那么本文就在此基础上,依据源码来做更加详细的分析。

Router工程
Router有一个独立的app目录,在vcap下面的router文件夹下。我们只需要关注两个部分:ext和lib。ext中有两个lua脚本,而lib中是ruby文件。

Nginx通过lua脚本发送lookup请求
总的来讲,Nginx通过lua脚本把lookup请求发送给由ruby代码建立的http server(这个server叫做uls server,也就是上图中的upstream locator svc),获取用户请求所指向的node的ip。那么本节主要研究Nginx如何通过lua发送这个lookup消息。

Nginx使用lua脚本是通过它的配置文件实现的。这个文件一般在:~/cloudfoundry/.deployments/devbox/deploy/nginx/nginx-0.8.54/conf/nginx.conf。我们可以看一下这个文件的内容,如果没有安装过,可以在vcap的源码的/dev_box/cookbooks/nginx/templates/defaults/Ubuntu-nginx.conf.erb文件看。这个erb文件就是在chef安装cf时,填入参数,然后放到上述的conf目录下的。

那么这个文件的主要内容是:

  1. access_by_lua '  
  2. -- add package.path andpackage.cpath  
  3.  package.path = package.path..";/root/cloudfoundry/.deployments/devbox/deploy/lua/lua-5.1.4/lib/lua/5.1/?.lua"  
  4.  package.cpath = package.cpath..";/root/cloudfoundry/.deployments/devbox/deploy/lua/lua-5.1.4/lib/lua/5.1/?.so"  
  5.  local uls = require ("uls")  
  6.    
  7. ngx.log(ngx.DEBUG,"monitor trigger stats syncup")  
  8.    
  9. local req = uls.generate_stats_request()  
  10.    
  11. -- generate one subrequest to uls to update stats  
  12.  ngx.location.capture(  
  13.  "/vcapuls", { body = req }  
  14.  )  
  15.  ';  

access_by_lua就是Nginx调用lua脚本来发送请求的配置,可以看http://wiki.nginx.org/HttpLuaModule#access_by_lua学习。那么在这个代码块中,首先把lua配置好了,包括lua文件,在package.path的路径中,就是上文提到的两个lua脚本。然后local req = uls.generate_stats_request()这行代码,其实是调用lua脚本中的一个函数,产生http request。这个函数可以在uls.lua文件中找到。

至此已经基本清楚了:Nginx配置好了lua,然后调用lua产生http request,然后Nginx将这个request发送给server。其他就不详细了,具体可以自己看代码

sticky session
新版本支持sticky session,这部分可以参考lua脚本:uls.lua,其中有函数专门解析request中的session,然后将这个信息加入到lookup request中,那么ruby在回应的时候会将其考虑在内。具体可以自己看代码。

请求的格式
这里码一下这个请求的格式,这段文字在uls.lua文件中:

  1. Message between nginx and uls (as http body)  
  2.  nginx -> uls  
  3.  {  
  4.  "host": api.vcap.me,  
  5.  "backend_addr": 10.117.9.178:9022,  
  6.  "stats": [  
  7.  {  
  8.  "request_tags": xxx,  
  9.  "response_latency": xxx,  
  10.  "response_samples": xxx,  
  11.  "response_codes": {  
  12.  {"responses_xxx":xxx},  
  13.  {"responses_2xx":xxx}  
  14.  }  
  15.  },  
  16.  {  
  17.  "request_tags": xxx,  
  18.  "response_latency": xxx,  
  19.  "response_samples": xxx,  
  20.  "response_codes": {  
  21.  {"responses_xxx":xxx},  
  22.  {"responses_2xx":xxx}  
  23.  {"responses_5xx":xxx},  
  24.  }  
  25.  }  
  26.  ]  
  27.  }  
  28.    
  29. nginx <- uls{  
  30.  "backend_addr": xxx,  
  31.  "request_tags": xxx,  
  32.  "router_ip": xxx  
  33.  }  

第一部分是Nginx发出的请求格式,包括了host,tag,host就是用户发来的需要访问的url,而tag则是加密之后的参数。一个实际的例子:

{"host":"api.vcap.me","stats":[{"response_latency":0,"request_tags":"BAh7BjoOY29tcG9uZW50SSIUQ2xvdWRDb250cm9sbGVyBjoGRVQ=","response_codes":{"responses_2xx":1},"response_samples":1}]}

第二部分则是返回的response,包括了转发的ip以及tag。

Ruby端http server
现在的ruby文件主要负责建立一个http server供lua脚本查询ip,那么在router工程的lib文件中主要是/router/router_uls_server.rb文件。这个文件基于sinatra建立了一个server,而这个server在router.rb文件中被启动。而其他ruby则负责从NATS拿到其他组件的register信息,然后更新自己的router表。这部分也不是太难懂,就不展开了。

相关推荐