Cloud Foundry 源碼解析一覽(router)

前面的文章已經介紹了整個cloud foundry的源碼的啓動過程,這篇文章介紹一下router方面的細節,畢竟外界訪問cloud foundry的入口就是router。。

首先來看router的啓動:

  1. /home/fjs/cloudfoundry/vcap/router/bin/router -c /home/fjs/cloudfoundry/.deployments/devbox/config/router.yml  
接下來進入源碼來看看。。。
  1. config_path = ENV["CLOUD_FOUNDRY_CONFIG_PATH"] || File.join(File.dirname(__FILE__), '../config')  
  2. config_file = File.join(config_path, 'router.yml')  
  3. port, inet = nil, nil  
  4.   
  5. options = OptionParser.new do |opts|  
  6.   opts.banner = 'Usage: router [OPTIONS]'  
  7.   opts.on("-p""--port [ARG]""Network port"do |opt|  
  8.     port = opt.to_i  
  9.   end  
  10.   opts.on("-i""--interface [ARG]""Network Interface"do |opt|  
  11.     inet = opt  
  12.   end  
  13.   opts.on("-c""--config [ARG]""Configuration File"do |opt|  
  14.     config_file = opt   #/home/fjs/cloudfoudnry/.deployments/devbox/config/router.yml  
  15.   end  
  16.   opts.on("-h""--help""Help"do  
  17.     puts opts  
  18.     exit  
  19.   end  
  20. end  
  21. options.parse!(ARGV.dup)  
  22.   
  23. begin  
  24.   config = File.open(config_file) do |f|   #讀取配置文件  
  25.     YAML.load(f)  
  26.   end  
  27. rescue => e  
  28.   puts "Could not read configuration file:  #{e}"  
  29.   exit  
  30. end  
  31.   
  32. # Placeholder for Component reporting  
  33. config['config_file'] = File.expand_path(config_file)  #/home/fjs/cloudfoudnry/.deployments/devbox/config/router.yml  
  34.   
  35. port = config['port'] unless port  
  36. inet = config['inet'] unless inet  
首先是進行一些基本的配置,例如讀取配置文件等等。。

然後會啓動EVENTMACHINE,進行真正的啓動。。。

在代碼之前,先介紹一下router的大體設計。。。

router中會集成一個簡單的服務器,通過Sinatra開發的,然後還會集成一個nginx服務器,外界的訪問首先是到達nginx服務器,然後nginx會調用lua腳本,生成http請求發送到router自己的服務器,然後router會通過外界訪問的host的值來找到相應的app的ip+port地址(指向對應的dea),然後再返回,然後nginx再代理到返回的地址就好了。。。

這樣也就是先了router的功能。。。。類似於如下:



好了,大體的設計已經介紹完了,接下來進入代碼吧:

  1. Router.server = Thin::Server.new(inet, port, RouterULSServer, :signals => falseif inet && port   #這個一般情況下不會使用  
  2.    Router.local_server = Thin::Server.new(fn, RouterULSServer, :signals => falseif fn #創建router的服務器,用來與nginx進行交互  
  3.   
  4.    Router.server.start if Router.server    #啓動服務器  
  5.    Router.local_server.start if Router.local_server  
這裏,一般情況下是監聽一個本地的sock文件,這樣就很容易實現nginx與router自己的服務器之間的通信:/tmp/router.sock

接下來是訂閱一些消息,例如有新的app上線了,那麼router需要登記它的名字和ip+port地址,

  1. Router.setup_listeners  #主要是訂閱一些nats的消息  
它的代碼具體如下:
  1. def setup_listeners  
  2.       #訂閱app的註冊消息  
  3.       NATS.subscribe('router.register') { |msg|  
  4.         msg_hash = Yajl::Parser.parse(msg, :symbolize_keys => true)  
  5.         return unless uris = msg_hash[:uris]  
  6.         uris.each { |uri| register_droplet(uri, msg_hash[:host], msg_hash[:port],  
  7.                                            msg_hash[:tags], msg_hash[:app]) }  
  8.       }  
  9.       #訂閱一些app解註冊的消息  
  10.       NATS.subscribe('router.unregister') { |msg|  
  11.         msg_hash = Yajl::Parser.parse(msg, :symbolize_keys => true)  
  12.         return unless uris = msg_hash[:uris]  
  13.         uris.each { |uri| unregister_droplet(uri, msg_hash[:host], msg_hash[:port]) }  
  14.       }  
  15.     end  
router.register用於登記新的app,unregistered則是當有app下線的時候需要將其刪除。。

接下來的代碼是:

  1. @hello_message = { :id => @router_id, :version => Router::VERSION }.to_json.freeze  
  2.   
  3. # This will check on the state of the registered urls, do maintenance, etc..  
  4. Router.setup_sweepers  
  5.   
  6. # Setup a start sweeper to make sure we have a consistent view of the world.  
  7. EM.next_tick do  
  8.   # Announce our existence  
  9.   NATS.publish('router.start', @hello_message)  
  10.   
  11.   # Don't let the messages pile up if we are in a reconnecting state  
  12.   EM.add_periodic_timer(START_SWEEPER) do  
  13.     unless NATS.client.reconnecting?  
  14.       NATS.publish('router.start', @hello_message)  
  15.     end  
  16.   end  
  17. end  
setup_sweepers主要是用於設置周期函數,用於更新一些實時的數據,例如http請求數量等等。。

然後又會設置周期函數,用於廣播當前router的一些基本信息。。。。

然後我們來看router自己的服務器。。。。

  1. get "/" do  
  2.   uls_response = {}  
  3.   VCAP::Component.varz[:requests] += 1  
  4.   
  5.   # Get request body  
  6.   request.body.rewind # in case someone already read the body  
  7.   body = request.body.read  #{"host":"fjs.vcap.me","stats":[{"response_latency":0,"request_tags":"BAh7BjoOY29tcG9uZW50SSIUQ2xvdWRDb250cm9sbGVyBjoGRVQ=","response_codes":{"responses_3xx":1},"response_samples":1}]}  
  8.   Router.log.debug "Request body: #{body}"  
  9.     
  10.   # Parse request body  
  11.   uls_req = JSON.parse(body, :symbolize_keys => true)  
  12.   raise ParserError if uls_req.nil? || !uls_req.is_a?(Hash)  
  13.   stats, url = uls_req[ULS_STATS_UPDATE], uls_req[ULS_HOST_QUERY]   #url爲當前app的http的請求的header的host字段的值  
  14.   sticky = uls_req[ULS_STICKY_SESSION]  
  15.   
  16.   if stats then  
  17.     update_uls_stats(stats)  
  18.   end  
  19.   
  20.   if url then  
  21.     # Lookup a droplet  
  22.     unless droplets = Router.lookup_droplet(url)  
  23.       Router.log.debug "No droplet registered for #{url}"  
  24.       raise Sinatra::NotFound  
  25.     end  
  26.   
  27.     # Pick a droplet based on original backend addr or pick a droplet randomly  
  28.     #這裏是爲了區分instance的session  
  29.     if sticky  
  30.       _, host, port = Router.decrypt_session_cookie(sticky)  
  31.       droplet = check_original_droplet(droplets, host, port)  
  32.     end  
  33.     droplet ||= droplets[rand*droplets.size]  
  34.     Router.log.debug "Routing #{droplet[:url]} to #{droplet[:host]}:#{droplet[:port]}"  
  35.   
  36.     # Update droplet stats  
  37.     update_droplet_stats(droplet)  
  38.   
  39.     # Update active apps  
  40.     Router.add_active_app(droplet[:app]) if droplet[:app]  
  41.   
  42.     # Get session cookie for droplet  
  43.     new_sticky = Router.get_session_cookie(droplet)  
  44.   
  45.     uls_req_tags = Base64.encode64(Marshal.dump(droplet[:tags])).strip  
  46.     uls_response = {  
  47.       ULS_STICKY_SESSION => new_sticky,  
  48.       ULS_BACKEND_ADDR   => "#{droplet[:host]}:#{droplet[:port]}",  
  49.       ULS_REQUEST_TAGS   => uls_req_tags,  
  50.       ULS_ROUTER_IP      => Router.inet,  
  51.       ULS_APP_ID         => droplet[:app] || 0,  
  52.     }  
  53.   end  
  54.   
  55.   uls_response.to_json  
  56. end  
代碼其實還是很簡單的,主要是接受經過lua腳本處理過然後nginx傳過來的數據,然後router根據host的數據查找相應的app的信息,主要是找到訪問這個app的ip+port地址,然後將它返回回去,這樣nginx就可以直接通過這個地址來直接到dea來訪問對應的app了。。。

另外還剩下的就是lua腳本和nginx的配置方面的東西了,其實很簡單,稍微看看就能明白。。。

這樣router的主要的東西就講完了。。。通過這幾天看cloud foundry的源碼,發現其實cloud foundry的整個實現還是相對來說比較簡單的了。。。。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章