前言
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中去,里面启动了三个定时器,分别是更新:
- 每两秒钟各个droplet的请求数
- 每30秒钟检查各个注册url的活性,当droplet的时间戳超过两分钟会从router中删除
- 每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版本,我会后续更新。