您的位置:首页 > 其它

Cloud Foundry 源码解析一览(router)

2013-07-30 10:21 253 查看
前面的文章已经介绍了整个cloud foundry的源码的启动过程,这篇文章介绍一下router方面的细节,毕竟外界访问cloud foundry的入口就是router。。

首先来看router的启动:

[cpp]
view plaincopy

/home/fjs/cloudfoundry/vcap/router/bin/router -c /home/fjs/cloudfoundry/.deployments/devbox/config/router.yml

接下来进入源码来看看。。。

[cpp]
view plaincopy

config_path = ENV["CLOUD_FOUNDRY_CONFIG_PATH"] || File.join(File.dirname(__FILE__), '../config')
config_file = File.join(config_path, 'router.yml')
port, inet = nil, nil

options = OptionParser.new do |opts|
opts.banner = 'Usage: router [OPTIONS]'
opts.on("-p", "--port [ARG]", "Network port") do |opt|
port = opt.to_i
end
opts.on("-i", "--interface [ARG]", "Network Interface") do |opt|
inet = opt
end
opts.on("-c", "--config [ARG]", "Configuration File") do |opt|
config_file = opt #/home/fjs/cloudfoudnry/.deployments/devbox/config/router.yml
end
opts.on("-h", "--help", "Help") do
puts opts
exit
end
end
options.parse!(ARGV.dup)

begin
config = File.open(config_file) do |f| #读取配置文件
YAML.load(f)
end
rescue => e
puts "Could not read configuration file: #{e}"
exit
end

# Placeholder for Component reporting
config['config_file'] = File.expand_path(config_file) #/home/fjs/cloudfoudnry/.deployments/devbox/config/router.yml

port = config['port'] unless port
inet = config['inet'] unless inet

首先是进行一些基本的配置,例如读取配置文件等等。。
然后会启动EVENTMACHINE,进行真正的启动。。。

在代码之前,先介绍一下router的大体设计。。。

router中会集成一个简单的服务器,通过Sinatra开发的,然后还会集成一个nginx服务器,外界的访问首先是到达nginx服务器,然后nginx会调用lua脚本,生成http请求发送到router自己的服务器,然后router会通过外界访问的host的值来找到相应的app的ip+port地址(指向对应的dea),然后再返回,然后nginx再代理到返回的地址就好了。。。

这样也就是先了router的功能。。。。类似于如下:



好了,大体的设计已经介绍完了,接下来进入代码吧:

[cpp]
view plaincopy

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的服务器,用来与nginx进行交互

Router.server.start if Router.server #启动服务器
Router.local_server.start if Router.local_server

这里,一般情况下是监听一个本地的sock文件,这样就很容易实现nginx与router自己的服务器之间的通信:/tmp/router.sock
接下来是订阅一些消息,例如有新的app上线了,那么router需要登记它的名字和ip+port地址,

[cpp]
view plaincopy

Router.setup_listeners #主要是订阅一些nats的消息

它的代码具体如下:

[cpp]
view plaincopy

def setup_listeners
#订阅app的注册消息
NATS.subscribe('router.register') { |msg|
msg_hash = Yajl::Parser.parse(msg, :symbolize_keys => true)
return unless uris = msg_hash[:uris]
uris.each { |uri| register_droplet(uri, msg_hash[:host], msg_hash[:port],
msg_hash[:tags], msg_hash[:app]) }
}
#订阅一些app解注册的消息
NATS.subscribe('router.unregister') { |msg|
msg_hash = Yajl::Parser.parse(msg, :symbolize_keys => true)
return unless uris = msg_hash[:uris]
uris.each { |uri| unregister_droplet(uri, msg_hash[:host], msg_hash[:port]) }
}
end

router.register用于登记新的app,unregistered则是当有app下线的时候需要将其删除。。
接下来的代码是:

[html]
view plaincopy

@hello_message = { :id => @router_id, :version => Router::VERSION }.to_json.freeze

# This will check on the state of the registered urls, do maintenance, etc..
Router.setup_sweepers

# Setup a start sweeper to make sure we have a consistent view of the world.
EM.next_tick do
# Announce our existence
NATS.publish('router.start', @hello_message)

# Don't let the messages pile up if we are in a reconnecting state
EM.add_periodic_timer(START_SWEEPER) do
unless NATS.client.reconnecting?
NATS.publish('router.start', @hello_message)
end
end
end

setup_sweepers主要是用于设置周期函数,用于更新一些实时的数据,例如http请求数量等等。。
然后又会设置周期函数,用于广播当前router的一些基本信息。。。。

然后我们来看router自己的服务器。。。。

[cpp]
view plaincopy

get "/" do
uls_response = {}
VCAP::Component.varz[:requests] += 1

# Get request body
request.body.rewind # in case someone already read the body
body = request.body.read #{"host":"fjs.vcap.me","stats":[{"response_latency":0,"request_tags":"BAh7BjoOY29tcG9uZW50SSIUQ2xvdWRDb250cm9sbGVyBjoGRVQ=","response_codes":{"responses_3xx":1},"response_samples":1}]}
Router.log.debug "Request body: #{body}"

# Parse request body
uls_req = JSON.parse(body, :symbolize_keys => true)
raise ParserError if uls_req.nil? || !uls_req.is_a?(Hash)
stats, url = uls_req[ULS_STATS_UPDATE], uls_req[ULS_HOST_QUERY] #url为当前app的http的请求的header的host字段的值
sticky = uls_req[ULS_STICKY_SESSION]

if stats then
update_uls_stats(stats)
end

if url then
# Lookup a droplet
unless droplets = Router.lookup_droplet(url)
Router.log.debug "No droplet registered for #{url}"
raise Sinatra::NotFound
end

# Pick a droplet based on original backend addr or pick a droplet randomly
#这里是为了区分instance的session
if sticky
_, host, port = Router.decrypt_session_cookie(sticky)
droplet = check_original_droplet(droplets, host, port)
end
droplet ||= droplets[rand*droplets.size]
Router.log.debug "Routing #{droplet[:url]} to #{droplet[:host]}:#{droplet[:port]}"

# Update droplet stats
update_droplet_stats(droplet)

# Update active apps
Router.add_active_app(droplet[:app]) if droplet[:app]

# Get session cookie for droplet
new_sticky = Router.get_session_cookie(droplet)

uls_req_tags = Base64.encode64(Marshal.dump(droplet[:tags])).strip
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,
}
end

uls_response.to_json
end

代码其实还是很简单的,主要是接受经过lua脚本处理过然后nginx传过来的数据,然后router根据host的数据查找相应的app的信息,主要是找到访问这个app的ip+port地址,然后将它返回回去,这样nginx就可以直接通过这个地址来直接到dea来访问对应的app了。。。
另外还剩下的就是lua脚本和nginx的配置方面的东西了,其实很简单,稍微看看就能明白。。。

这样router的主要的东西就讲完了。。。通过这几天看cloud foundry的源码,发现其实cloud foundry的整个实现还是相对来说比较简单的了。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: