cloud foundry之router源码解读

前言

router是cloud foundry云平台的门户,所有的外部访问请求都要通过router来转发,这部分代码没有像nats那样有很精彩的算法和数据 结构设计,主要就是对droplet的访问分析和控制。

router总体思路

刚开始看这部分代码的时候充满着疑惑,其目录文件中还有lua文件,再就是在主要的sinatra框架中并没有找到任何它将请求发送给组件 和droplet的代码。最后才明白router中只是保存了一些平台的访问信息,真正去接受和转发请求的并不是我们看到的那些ruby代码,而是 nginx去做这些工作,外部的请求先到达nginx,然后转发到router上面,router通过分析,将访问请求的ip和端口返回给nginx,nginx再将 相应的请求发送给droplet。我揣度设计者的顾虑应该是ruby处理大量请求的效果并不理想,而是借用nginx来实现请求的转发,而ruby只是 去处理一些请求逻辑。

代码分析

router的核心处理函数是router_uls_server.rb这个sinatra程序,但是为了更快的理出头绪,我们还是从最外层的router.rb入手,它是 router的入口。略过前面的参数解析,就可以直接进入router的EM主循环(EM.run)了:

if fn = config['sock']
  File.unlink(fn) if File.exists?(fn)
  Router.log.info "Listening on unix domain socket: '#{fn}'"
end

通过写日志的信息就可以知道,fn是unix socket的文件描述符,router正式通过它与nginx通信的。

EM.set_descriptor_table_size(32768)这句代码我们经常会在ruby代码中看到,它其实是设置EM 进程中可以打开的最大文件描述符数,在进程设置代码中一般都会用到。

begin
  # TCP/IP Socket
  Router.server = Thin::Server.new(inet, port, RouterULSServer, :signals => false) if inet && port
  Router.local_server = Thin::Server.new(fn, RouterULSServer, :signals => false) if fn

  Router.server.start if Router.server
  Router.local_server.start if Router.local_server
rescue => e
  Router.log.fatal "Problem starting server, #{e}"
  exit
end

这里就是EM的主要功能处,它启动了两个server,从命名和参数中我们就可以看到local_server是跟unix socket打交道的,它用thin来启动sinatra程序,这个web_server正是我们上文中提到的router与nginx的 接口。但是第一个server,暂时还不知道它的功能。

NATS.start(:uri => config['mbus']) 启动nats客户端与nats_server的连接 Router.setup_listeners 在router类中我们能知道它就是在nats上订阅了两个主题的消息,分别是 droplet的注册和解注册,在这里router并没有区分组件和droplet的url

Router.setup_sweepers 它链接到了Router类中的sweepers,我们不得不去看看里面的情况,因为 它涉及到router为droplet保活的特性。

走进sweeper中去,里面启动了三个定时器,分别是更新:

  1. 每两秒钟各个droplet的请求数
  2. 每30秒钟检查各个注册url的活性,当droplet的时间戳超过两分钟会从router中删除
  3. 每30秒钟更新活跃的应用

它的功能是及时保持平台中保持可访问活性的url和app,及统计平台中应用处理请求的频率。主循环中间 主要是针对varz的设置,以便从外部能够知道router的工作情况。

主循环的最后是一个定时器,它每30秒钟就会通过nats发布消息,向平台报活。

Router这个类中主要是一些函数,它保存了一个重要的hash表,就是平台中可达的droplet保存在@droplets中 droplet中保存的信息如下:

  • :app => 平台中的app_id
  • :session => 访问到droplet的session信息
  • :host => droplet对应的ip
  • :port => droplet访问端口
  • :clients => 访问droplet的客户端,里面其实就是保存了各个client连接一段时间的请求数
  • :url => droplet的访问url
  • :timestamp => droplet时间戳
  • :requests => 整个droplet的请求数,同样是一段时间内的统计
  • :tags => droplet的标签,它会在传输中加密

里面还有计算一段时间内访问频率的函数,它会通过varz以供读取。 flush_active_apps 这个函数向平台报告可访问到达的app,在线程池中将存活apps信息压缩,再 返回主程序通过nats发出去。

接下来就是处理访问的主要程序类router_uls_server.rb,它请求json格式的request body,这说明router与ngnix 之间是通过json来传输数据的。它支持新的url访问请求,也支持session(通过cookie来识别),要了解具体的cookie 代码得去看lua代码。

uls_response = {
    ULS_STICKY_SESSION => new_sticky,
    ULS_BACKEND_ADDR   => "#{droplet[:host]}:#{droplet[:port]}",
    ULS_REQUEST_TAGS   => uls_req_tags,
    ULS_ROUTER_IP      => Router.inet,
    ULS_APP_ID         => droplet[:app] || 0,
  }

这个response就是router返回给nginx的droplet信息,里面最主要的就是droplet的host与port,然后ngigx根据 这个信息将请求发送到instance内部,与我们前面所说的router的工作方式接上了。

后记

总体来说router的代码即清晰又晦涩,这里也只能帮助你理解它的工作原理,至于深究lua,nginx与sinatra的 细节就需要再去读源码包了。这里解读的其实是cloud foundry v1版本的router源码,v2版本中vmware用go语言 将router重写了,其工作方式也发生了变化,关于V2版本,我会后续更新。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories cloudfoundry  tagged with cloudfoundry