Project

Profile

Help

HostedRedmine.com has moved to the Planio platform. All logins and passwords remained the same. All users will be able to login and use Redmine just as before. *Read more...*

A little more about Router

Router V2 についての補足→A little more about Router V2

Routerの位置付け

  • 実際の構成

参考: Setup SSL on cloudfoundry landscape

Routerの仕事

  1. urlへのアクセスをアプリケーション・インスタンス(ホスト:ポート)に振り分ける
  2. アプリケーション・インスタンスからのレスポンスをクライアントに返す

URLへのアクセスをアプリケーション・インスタンス(droplet)に振り分ける

(この辺りの呼び出し関係についてはプロファイラーの出力結果 ruby-prof-call-tree-Router-extracted.maff も参考にした)

◎基本的な仕組み
  1. EventMachine.start_serverでクライアントからの接続を待ち受ける
  2. クライアントからの接続があると, EventMachine からClientConnection#receive_dataが呼び出される
  3. ClientConnection#receive_data
    1. URLに対応するdropletsを探す
      • dropletが複数ある場合は,その中から一つ選ぶ
    2. 選んだdroplet(のホスト:ポート)に AppConnection で接続する
◎そのためにしていること
  • URLとdropletsのmapの管理

EventMachine.start_serverでクライアントからの接続を待ち受ける

クライアントからの接続があると,EventMachineからClientConnection#receive_dataが呼び出される

ClientConnection#receive_data

  1. HTTP::Parserインスタンスの作成
          @parser = Http::Parser.new(self)
    (参考: http_parser.rb )
  2. ヘッダーの処理
          @parser << @headers
    
    呼び出されるメソッドは Http::Parser#<<
    その中で
    1. ClientConnection#on_headers_complete
      1. dropletsの検索
            # Lookup a Droplet
            unless droplets = Router.lookup_droplet(host)
              Router.log.debug "No droplet registered for #{host}" 
              VCAP::Component.varz[:bad_requests] += 1
              send_data(Router.notfound_redirect || ERROR_404_RESPONSE)
              close_connection_after_writing
              return
            end
        
      2. 接続先のdropletを一つ選ぶ
        1. cookieがある場合はcookieを発行したdropletを選択
              # Check for session state
              if VCAP_COOKIE =~ headers[COOKIE_HEADER]
                url, host, port = Router.decrypt_session_cookie($1)
                Router.log.debug "Client has __VCAP_ID__ for #{url}@#{host}:#{port}" 
                # Check host?
                droplets.each { |droplet|
                  # If we already now about them just update the timestamp..
                  if(droplet[:host] == host && droplet[:port] == port)
                    @droplet = droplet
                    break;
                  end
                }
                Router.log.debug "Client's __VCAP_ID__ is stale" unless @droplet
              end
          
        2. dropletをランダムに選択
              # pick a random backend unless selected from above already
              @droplet = droplets[rand*droplets.size] unless @droplet
          
      3. 選択したdropletに接続
        1. 既存コネクションがある場合は再利用
              @bound_app_conn = @droplet[:connections].pop
              if (@bound_app_conn && !@bound_app_conn.error?)
                Router.log.debug "Reusing pooled AppConnection.." 
                @bound_app_conn.rebind(self, @headers)
          
        2. なければ新たに接続
              else
                host, port = @droplet[:host], @droplet[:port]
                @bound_app_conn = EM.connect(host, port, AppConnection, self, @headers, @droplet)
              end
          
          • EventMachine#connect
            上の呼び出しの self, @headers, @droplet の3つが *args になる
                bind_connect nil, nil, server, port, handler, *args, &blk
            
            • EventMachine#bind_connect
                  klass = klass_from_handler(Connection, handler, *args)
              
                  s = if port
                        if bind_addr
                          bind_connect_server bind_addr, bind_port.to_i, server, port
                        else
                          connect_server server, port
                        end
                      else
                        connect_unix_server server
                      end
              
                  c = klass.new s, *args
              ..
              
  3. ボディの処理
          # Process left over body fragment if any
          process_request_body_chunk(@buf) if @parser
    

クライアントからのリクエストをアプリケーション(droplet)に振り分けるためにしていること

  • urlとdropletsのマッピングの管理
    • データ構造: Router@droplets
    • 登録: Router#register_droplet
    • 削除: Router#unregister_droplet
    • 登録/削除が行われる契機
      1. NATSでrouter.register,router.unregisterメッセージを受け取った時
        Router#setup_listeners
              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]) }
              }
              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]) }
              }
        
      2. (定期的な)チェック: Router#check_registered_ulrs
        • 定期チェックの登録: Router#setup_sweeper
                EM.add_periodic_timer(CHECK_SWEEPER) {
                  check_registered_urls
                  log_connection_stats
                }
          

アプリケーション・インスタンスからのレスポンスをクライアントに返す

(略)

Interfaces

HTTP

NATS messages

http://apidocs.cloudfoundry.com/router