Cloud Foundry中gorouter源碼分析

        在Cloud Foundry v1版本中,router作爲路由節點,轉發所有進入Cloud Foundry的請求。由於開發語言爲ruby,故router接受並處理併發請求的能力受到語言層的限制。雖然在v1版本中,router曾經有過一定的優化,採用lua腳本代替原先的ruby腳本,由lua來分析請求,使得一部分請求不再經過ruby代碼,而直接去DEA訪問應用,但是,一旦router暴露在大量的訪問請求下,性能依舊是不盡如人意。


        爲了提高Cloud Foundry router的可用性,Cloud Foundry開源社區不久前推出了gorouter。gorouter採用現階段比較新穎的go作爲編程語言,並重新設計了原有的組件架構。由於go語言本身的特性,gorouter處理併發請求的能力大大超過了router,甚至在同種實驗環境下,性能是原先router的20倍左右。


        由於gorouter的高性能,筆者也抱着期待的心態去接觸go,當然還有gorouter。本文不會從go語言語法的角度入手gorouter,所以有一些go語言的基礎再來看本文,是有必要的。本文主要是對gorouter的源碼的簡單解讀,另外還包含一些筆者對gorouter的看法。


gorouter的程序組織形式


        首先,先從gorouter的程序組織形式入手,可見下圖:


        以下簡單介紹其中一些重要文件的功能:

        common:common意指通用,所以該文件夾中也是一些比較通識的概念定義,比如varz,healthz,component等,以及關於項目過程的一些基本操作定義。

        config:顧名思義,該文件夾中的文件爲gorouter組件的配置文件。

        log:定義gorouter的log形式定義。

        proxy:作爲一個代理處理外界進入Cloud Foundry的所有請求。

        registry:處理組件或者DEA中應用到gorouter來註冊uri的事件,另外還負責請求訪問應用時查找應用真實IP,port。

        route:主要定義在rigistry中需要使用到的三個數據結構:endpoint,pool和uris。

        router:程序的主入口,main函數所在處。

        stats:主要負責一些應用記錄的狀態,還有一些其他零碎的東西,比如定義一個堆。

        util:其中一般是工具源碼,在這裏只負責給gorouter進程寫pid這件事。

        varz:主要涉及varz信息的處理,其實就是gorouter組件狀態的查閱。

        router.go: 主要定義了router的數據結構,及其實例初始化的過程,還有最終運行的流程。


gorouter的功能


        gorouter的功能主要可以分爲三個部分:負責接收Cloud Foundry內部組件及應用uri註冊以及註銷的請求,負責轉發所有外部對Cloud Foundry的訪問請求,負責提供gorouter作爲一個組件的狀態監控。


接受uri註冊及註銷請求


        當Cloud Foundry內一個組件需要提供HTTP服務的時候,那麼這個組件則必須將自己的uri和IP一起註冊到gorouter處,典型的有,Cloud Foundry中Service Gateway與Cloud Controller通過HTTP建立連接的,另外Cloud Controller也需要對外提供HTTP服務,所以這些組件必須在gorouter中進行註冊,以便可以順利通信或訪問。


        除了平臺級的組件uri註冊,最常見的是應用級的應用uri註冊,也就是在Cloud Foundry中新部署應用時,應用所在的DEA會向gorouter發送一個uri,IP和port的註冊請求。gorouter收到這個請求後,會添加該記錄,並保證可以解析外部的URL訪問形式。當然,反過來,當一個應用被刪除的時候,爲了不浪費Cloud Foundry內部的uri資源,Cloud Foundry會將該uri從gorouter中註銷,隨即gorouter在節點處刪除這條記錄。


轉發對Cloud Foundry的訪問請求


        gorouter接受到的訪問請求大致可以分爲三種:外部請求有:用戶對應用的訪問請求,用戶對Cloud Foundry內部資源的管理請求;內部的請求有:內部組件之間通過HTTP的各類通信。


       雖然說請求的類型可以分爲三種,但是gorouter對於這些請求的操作都是一致的,找到相應的uri,提取出相應的IP和port,然後進行轉發。需要注意的是,在原先版本的router中,router只能接收HTTP請求,然而現在gorouter中,已經考慮了TCP連接,以及websocket。


提供組件監控


        Cloud Foundry都有自己的狀態監控,可以通過HTTP訪問。這主要是每個組件在啓動的時候,都作爲一個component向Cloud Foundry進行註冊,註冊的時候帶有很多關於自身組件的信息,同時也啓動了一個HTTP server。


gorouter的初始化及啓動流程


Router對象實例的創建與初始化


        gorouter的啓動過程主要在router.go文件中,在該文件中,首先定義創建一個Router實例的操作並進行初始化,另外還定義了Router實例的開始運行所做的操作。

        在router.go文件中,首先需要是Router結構體的定義:

type Router struct {
	config     *config.Config
	……
}
        隨後又定義了Router實例的初始化:

func NewRouter(c *config.Config) *Router {
	router := &Router{
		config: c,
	}
        ……
	return router
}
        還有就是定義了Router實例開始運行時所做的操作:

func (router *Router) Run() {
        ……
}
        查看源碼可以發現Router結構體有以下幾個屬性:

  • config:負責傳入gorouter所需要的配置信息
  • proxy:一個代理對象,負責完成請求的轉發
  • mbusClient:作爲gorouter中的nats_client,負責與Cloud Foundry的消息中間件NATS通信
  • registry:作爲gorouter中的註冊模塊,完成Cloud Foundry中註冊或註銷請求的處理
  • varz:處理gorouter自身作爲一個組件的狀態監控
  • component:gorouter作爲一個組件的信息,將自身的信息存入該component對象

       在初始化Router對象實例的時候,都是通過傳入的config文件中的配置文件來完成初始化。首先通過創建一個Router實例,並初始化該實例的配置信息:

router := &Router{
		config: c,
	}
        然後通過讀取該配置屬性的信息逐步完成Router實例其他屬性的初始化。

        創建完Router實例對象router之後,router首先做的是創建一個用來與Cloud Foundry中與nats_server建立聯機的nats_client: mbusClient:

router.establishMBus()
         然後分別初始化了router對象的registry,varz,proxy:

    router.registry = registry.NewRegistry(router.config, router.mbusClient)
	router.registry.StartPruningCycle()

	router.varz = varz.NewVarz(router.registry)
	router.proxy = proxy.NewProxy(router.config, router.registry, router.varz)
        接着較爲重要的是:router.component的創建和執行啓動操作:

router.component = &vcap.VcapComponent{
		Type:        "Router",
		Index:       router.config.Index,
		Host:        host,
		Credentials: []string{router.config.Status.User, router.config.Status.Pass},
		Config:      router.config,
		Varz:        varz,
		Healthz:     healthz,
		InfoRoutes: map[string]json.Marshaler{
			"/routes": router.registry,
		},
	}
	vcap.StartComponent(router.component)
        最後返回了router實例對象之後,創建與初始化工作即完成了。


Router實例對象的運行操作

        在Router對象的函數run()中,幾乎執行了所有的Router實例對象的運行操作。

        1.router對象使用mbusClient週期性地去連接Cloud Foundry的nats_server:

	for {
		err = router.mbusClient.Connect()
		if err == nil {
			break
		}
		log.Errorf("Could not connect to NATS: %s", err)
		time.Sleep(500 * time.Millisecond)
	}
        2.router通過mbusClient將自己作爲一個component註冊到Cloud Foundry: router.RegisterComponent()

        3.router訂閱其他組件和應用要註冊或註銷的消息:

	router.SubscribeRegister()
	router.HandleGreetings()
	router.SubscribeUnregister()
        4.router通過SendStartMessage()發佈router.start的消息:

	router.SendStartMessage()

	// Send start again on reconnect
	router.mbusClient.OnConnect(func() {
		router.SendStartMessage()
	})
         5.週期性的刷新活着的應用的app_id。

         6.等待一個start信息的發送時間,以保證gorouter內registry映射表中已經有路由信息,以便而後在代理外部請求的時候,可以找到路由表的映射關係。所以,很顯然大家可以發現,gorouter會將組件或者應用的uri註冊信息存放在該自身的內存中,而gorouter關閉的時候,映射表中所有的信息丟失,每當重啓的時候,需要通過發送一個start消息,然後靠訂閱該消息的組件重新註冊uri,從而獲取所有的路由關係。

         7.以TCP的方式監聽本機的一個端口:

	listen, err := net.Listen("tcp", fmt.Sprintf(":%d", router.config.Port))
	if err != nil {
		log.Fatalf("net.Listen: %s", err)
	}
        8.寫pid文件:util.WritePidFile(router.config.Pidfile)

        9.創建proxy中Server結構體的實例server,並最終一個協程來執行這個server服務於剛纔創建的Listen對象:

	server := proxy.Server{Handler: router.proxy}
	go func() {	
		err := server.Serve(listen)
		if err != nil {
			log.Fatalf("proxy.Serve: %s", err)
		}
	}()

        以上便是gorouter的router實例在運行時所需要作的操作,當然其中很多模塊在實現功能的時候,還定義了其他的函數輔助實現,幾乎都在router.go文件的函數定義部分,代碼本身不難理解,可以對源碼進行仔細閱讀。


registry模塊源碼分析


        registry模塊接管的是Cloud Foundry中組件及應用的uri註冊或者註銷請求。從計算和存儲的角度來分析該模塊,即可發現該模塊完成了請求的處理和自身內存路由表的設計與維護。


        首先來分析一下registry的Registry對象:

type Registry struct {
	sync.RWMutex

	*steno.Logger

	*stats.ActiveApps
	*stats.TopApps

	byUri map[route.Uri]*route.Pool

	table map[tableKey]*tableEntry

	pruneStaleDropletsInterval time.Duration
	dropletStaleThreshold      time.Duration

	messageBus mbus.MessageBus

	timeOfLastUpdate time.Time
}

        在該對象中,有兩個非常重要的屬性byUri和table。

        可以看到byUri屬性是一個map類型,map的key類型爲route.Uri,value類型爲*route.pool。那麼現在去route/uris.go和route/pool.go中去看一下這些數據結構。uris.go中由定義 type Uri string,那說明它本身就是一個字符串類型,而後可以發現,這是主要域名的形式。而pool類型要稍顯複雜,可以理解爲是一個路由池,因爲在實際情況中,如果DEA上的一個應用由多個實例的話,那麼一個uri會對應於多個IP+port的組合。pool擁有一個屬性爲endpoints,該屬性又是一個map類型,key爲string,value爲Endpoint,具體形式如下:

type Pool struct {
	endpoints map[string]*Endpoint
}
        而Endpoint的定義在route/endpoint.go文件中,如下:

type Endpoint struct {
	sync.Mutex

	ApplicationId     string
	Host              string
	Port              uint16
	Tags              map[string]string
	PrivateInstanceId string
}

        同樣的table屬性也是map類型,key爲tableKey,value爲*tableEntry,隨後在相同文件中,有者兩個屬性的定義:

type tableKey struct {
	addr string
	uri  route.Uri
}

type tableEntry struct {
	endpoint  *route.Endpoint
	updatedAt time.Time
}

        關於uri的註冊,可以參看Resgister函數:

func (registry *Registry) Register(uri route.Uri, endpoint *route.Endpoint) {
	registry.Lock()
	defer registry.Unlock()

	uri = uri.ToLower()

	key := tableKey{
		addr: endpoint.CanonicalAddr(),
		uri:  uri,
	}

	var endpointToRegister *route.Endpoint

	entry, found := registry.table[key]
	if found {
		endpointToRegister = entry.endpoint
	} else {
		endpointToRegister = endpoint
		entry = &tableEntry{endpoint: endpoint}

		registry.table[key] = entry
	}

	pool, found := registry.byUri[uri]
	if !found {
		pool = route.NewPool()
		registry.byUri[uri] = pool
	}

	pool.Add(endpointToRegister)

	entry.updatedAt = time.Now()

	registry.timeOfLastUpdate = time.Now()
}
        其中,lock()函數負責將registry對象上鎖,隨後的defer語句,表示當整個Register函數執行完畢後,有go語言來完成registry對象的解鎖操作。


        key爲一個tableKey的實例,其中addr爲"IP:port"形式的string值,同時創建一個Endpoint類型的endpointToRegister,對於需要註冊的(uri,endpoint)組合,首先查看table屬性中能都找到鍵爲key的記錄,如果找到,那說明該key(實爲IP+port,uri的組合)已經存在於table中,所以將table中的記錄賦值於endpointToRegister;如果沒有找到,那說明該key還未存在於table中,屬於一個全新的key,需要在table中相應的記錄,則首先用請求中的endpoint賦值給endpointToRegister,然後在通過endpoint創建一個endtry對象,並使用語句:registry.table[key] = entry來實現最終在table中的存儲。當gorouter需要解析域名的時候使用的是byUri數據結構,所以在註冊的時候也要對byUri進行操作。首先通過請求中的uri來在byUri中查找是否存在該uri的路由表,如果沒有找到的話,則需要新建一個路由池pool,在將整個路由池pool,映射到相應的域名上,也就是請求中的uri。隨後還需要給該路由池pool添加endpointToRegister整個對象,由於pool的一條記錄本身是map類型的,所以在執行添加時,以endpointToPoint的(IP+port)作爲該記錄的key,整個endpointToRegister作爲value。最後再更新一些其他屬性。

        以上便是Register函數所做的一些工作,Unregister函數做的工作則是一些註銷工作。

 

        隨後則是一些關於byUri的查找,主要由以下幾種類型:

  • Lookup(uri route.Uri)  通過uri查找,返回pool.Sample(),其實也就是將pool隨機返回一條記錄,具體可以查看pool的Sample()函數。
  • LookupByPrivateInstanceId(uri route.Uri, p string)  通過 PrivateInstanceID查找,返回pool匹配該PrivateInstanceID的endpoint。
  • lookupByUri(uri route.Uri)  通過uri查找,返回整個路由池pool。

        以上是關於uri的註冊或者註銷,另外gorouter還會對路由表進行一定的管理,主要是清理一些很陳舊的路由記錄。首先在Router實例對象的初始化中就有陳舊路由信息的剪枝:router.registry.StartPruningCycle(),然後通過建立一個協程進行go registry.checkAndPrune(),通過中間一系列的操作之後,執行pruneStaleDroplets(),遍歷table對象中所有的記錄,並按條件進行剪枝。

    

proxy模塊源碼分析


server部分

        可以說作爲一個路由節點,proxy是其最爲重要的功能,registry這樣的模塊,其實也是爲了能夠服務於proxy。對於Cloud Foundry來說,所有通過uri訪問Cloud Foundry內部資源的請求,都需要路由節點gorouter作代理。gorouter的proxy模塊,首先監聽底層的網絡端口,然後再將端口發來的請求進行uri解析,最終將請求轉發至指定的Cloud Foundry內部節點處。


       在proxy模塊,實現過程幾乎可以從源碼中百分百的呈現。從代理流程來講,proxy模塊可以認爲是一個方向代理server端,接收所有從nginx發來的請求,並把請求轉發至Cloud Foundry內的某些組件處。從實現方式來看,proxy模塊建立一條nginx發來請求的連接,根據請求的內部具體信息,做相應的HTTP處理,最終構建該請求的response信息,並通過剛纔的連接,將response信息返回給Nginx,當然Nginx最後也會把請求返回給發起請求的用戶。其中,剛纔提到的相應的HTTP處理,也就是如何將請求發給Cloud Foundry內的某些組件,並接收返回的信息。


       粗略劃分的話,proxy模塊可以分爲server端的實現與proxy代理流程的實現。首先從源碼入手,第一個需要了解的自然是一些proxy模塊中server的重要數據結構,位於server.go文件中。


  1. conn:代表一條連到proxy模塊中server上的連接,或者說是從Nginx到gorouter的連接。其中,有需要訪問的遠程目標的地址,該連接連上的server對象等。
    type conn struct {
    	remoteAddr string            // network address of remote side
    	server     *Server           // the Server on which the connection arrived
    	rwc        net.Conn          // i/o connection
    	lr         *io.LimitedReader // io.LimitReader(rwc)
    	buf        *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->rwc
    	hijacked   bool              // connection has been hijacked by handler
    }

  2. request:代表請求對象,其中包括http類型中的請求對象,也包含一個response返回信息的對象。
    type request struct {
    	*http.Request
    	w *response
    }

  3. response:代表從server端返回去的一條response的HTTP信息,其中包括這條返回信息返回時的承載的連接,還有很多關於該HTTP響應的屬性值。
    type response struct {
    	conn *conn
    
    	reqWantsHttp10KeepAlive bool
    	reqMethod               string
    	reqProtoAtLeast10       bool
    	reqProtoAtLeast11       bool
    	reqExpectsContinue      bool
    	reqContentLength        int64
    
    	chunking      bool        // using chunked transfer encoding for reply body
    	wroteHeader   bool        // reply header has been written
    	wroteContinue bool        // 100 Continue response was written
    	header        http.Header // reply header parameters
    	written       int64       // number of bytes written in body
    	contentLength int64       // explicitly-declared Content-Length; or -1
    	status        int         // status code passed to WriteHeader
    	
    	closeAfterReply bool
    
    	requestBodyLimitHit bool
    }

  4. server:代表接收請求,轉發請求的server端,其中包含一個遠程地址,另外還有一個非常重要的handler對象,用來處理HTTP請求,如果對Nginx源碼熟悉的話,對Handler這個模塊應該不會陌生。另外,這裏的handler其實就是proxy.go文件中定義的proxy結構體。
    type Server struct {
    	Addr           string        // TCP address to listen on, ":http" if empty
    	Handler        http.Handler  // handler to invoke, http.DefaultServeMux if nil
    	ReadTimeout    time.Duration // maximum duration before timing out read of the request
    	WriteTimeout   time.Duration // maximum duration before timing out write of the response
    	MaxHeaderBytes int           // maximum size of request headers, DefaultMaxHeaderBytes if 0
    }

       源碼本身是從結構體入手,並進行方法定義,爲了便於理解,以下采用請求流程的方式對源碼進行解讀。


       要想對server結構有初步的瞭解,那必須從server的函數Server()入手。簡單代碼形式如下(已省略部分異常處理等代碼):

func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	var tempDelay time.Duration // how long to sleep on accept failure
	for {
		rw, e := l.Accept()
		……
		c, err := srv.newConn(rw)
		……
		go c.serve()
	}
	panic("not reached")
}

       該Serve()函數的發起者爲Server實例對象,傳入的參數爲對端口的監聽對象。在執行該函數的時候,defer方法顯性的定義了關於函數執行完畢後所需要處理的後續工作。關於server的具體執行操作,不難理解的服務器端需要不斷輪詢端口,並對端口處發來的請求進行相應的處理。在這裏的代碼實現即爲一個for循環,在該循環中,首先server實例對象accept一個連接。這裏的原理和socket的實現很類似,首先作爲一個server端,先去監聽listen,某一個端口,然後去accept這個端口發來的連接請求,也就是說,一旦有連接請求發來的話,server便會去accept該請求;然後作爲一個client端,所需要做的操作就是去給server端的某一端口發送連接請求,如果有server監聽了這個端口,那麼它可以accept該連接請求;最後雙方可以通信。

       

       首先,server通過代碼 rw, e := l.Accept() 實現對監聽端口請求的接受;然後server再對這個net.Conn類型的rw,進行處理,最後生成一個新的連接,也就是上面涉及到的conn結構對象;接着,server創建一個協程來完成這條連接上的請求。可以發現的是,由於在gorouter中server對象只有一個,所以所有的請求都是經過這個server的,那在for循環中,server會接受很多的請求,創建很多的連接,然後對於對於一個連接上的請求,又會創建一個協程來完成,如果不借助協程的高併發處理能力,幾乎不能應對大負載。


       以上是對Server實例對象執行時的代碼入口的解讀,當真正處理請求的時候,是在go c.serve()處,其中go代表這開闢一個協程,c.serve()則是處理的具體實現。


       以下是對serve()函數的分析,首先來看函數中的主要代碼:

func (c *conn) serve() {
	defer func() {
		……
		}
	}()

	for {
		req, w, err := c.readRequest()
		……
		// Expect 100 Continue support
		if req.expectsContinue() {
			……
		} else if req.Header.Get("Expect") != "" {
			……
		}

		handler := c.server.Handler
		……
		handler.ServeHTTP(w, req.Request)
		……
		req.finishRequest()
		if w.closeAfterReply {
			break
		}
	}
	c.close()
}
       defer關鍵字依舊是表示隨後定義的函數是做來爲serve()方法作善後處理。接着是一個for循環,在該for循環中,首先從連接中讀取一個請求,然後對該請求的某些屬性進行查閱並處理,接着創建一個handler,最後由該handler來處理HTTP請求,並結束一個請求,如果該請求是一個一次連接,那麼關於該連接,如果該請求處於長連接上,則繼續for循環的下一次迭代,繼續從連接中讀取請求並處理。


       現在我們涉及函數中具體實現。 在for循環中,首先讀取連接中的請求,代碼形式爲:req, w, err := c.readRequest(), 該實現,返回兩個對象,一個爲讀取的請求,另一個爲需要生成的response。在readRequest()函數中,代碼的形式爲(只顯示主要部分):

func (c *conn) readRequest() (r *request, w *response, err error) {
	……
	var req *http.Request
	if req, err = http.ReadRequest(c.buf.Reader); err != nil {
		……
	}
	c.lr.N = noLimit

	req.RemoteAddr = c.remoteAddr

	w = new(response)
	w.conn = c

	r = new(request)
	r.Request = req
	r.w = w

	……
	return r, w, nil
}

       可見,執行該函數的時候,首先通過http的函數ReadRequest()來實現從連接c中讀取請求,然後通過該請求,分別創建一個request對象和response對象。需要注意的是代碼w.conn=c,也就是在說創建玩reponse對象後,對對象屬性初始化時,將response的連接熟悉感conn,依舊賦值爲c,那麼當server將該請求轉發給Cloud Foundry內部組件處理後收到回覆,並對回覆再進行處理,來完成這裏的response重寫後,依舊通過之前的連接發回去。這樣的實現顯得更加高效,之前我一直在考慮,想nginx之類的反向代理服務器,接收請求轉發請求,接收回復轉發回復的流程,如果都需要重新創建連接來完成的話,連接的開銷會巨大,對於一些有長連接需求的http請求,重建連接的機制會顯得非常笨重。在這裏的go語言實現中,由於自定義了response的結構體,又輕鬆地實現了連接捆綁,所以不需要考慮連接的重新創建,但是這樣的方式肯定也會付出一定的代價,比如說內存的消耗等,因爲每個response實例對象中,都會存放一個連接信息。


       在serve()函數可以看到,在讀取連接請求readRequest()後,對請求進行一些處理之後,會創建一個handler對象來實現HTTP請求的處理,由於該部分的實現在proxy.go文件中,所以本文稍後即會涉及,簡單來講就是給後臺做代理,將請求發給後臺,並接收後臺的回覆。


       當獲得後臺的響應請求後,server隨即執行finishRequest()函數,其主要的功能就是將返回的後臺回覆,寫入需要返回給用戶的response對象中。然後判斷response對象中的屬性closeAfterReply,如果爲真,則表示之前的請求是一個一次請求,該請求表明,自身發出之後接收到回覆之後,不會再發起請求,就算有,也情願是在創建一個連接來實現,所以程序跳出for循環,關閉連接;如果爲假的話,那說明請求需要在一條長連接上進行操作,換言之,在請求的回覆發給用戶後,用戶還會有請求通過這條連接發給server,這樣的話,無需關閉連接,只需有server繼續對這條連接執行readRequest()函數即可。


       

proxy部分

       相比較而言,proxy部分做的工作要比server部分少一些,它主要的工作就是解析請求的uri轉發請求


       關於解析請求的uri的工作,在函數Lookup()中實現:

func (proxy *Proxy) Lookup(request *http.Request) (*route.Endpoint, bool) {
	uri := route.Uri(hostWithoutPort(request))

	if _, err := request.Cookie(StickyCookieKey); err == nil {
		if sticky, err := request.Cookie(VcapCookieId); err == nil {
			routeEndpoint, ok := proxy.Registry.LookupByPrivateInstanceId(uri, sticky.Value)
			if ok {
				return routeEndpoint, ok
			}
		}
	}
	return proxy.Registry.Lookup(uri)
}

        首先,找到請求中的host,然後對於該請求,檢查是否有StickyCookieKey,如果有的話,直接從中獲取sticky,再通過uri和sticky.value的組合找到相應的routeEndpoint,也就是請求的backend。這裏可以稍微解釋一下StickyCookieKey的作用。一旦一個請求中含有該cookie,而且能被解析到相應的uri和sticky值,也就是說這個請求,希望被處理的時候,能繼續被上次處理過這個用戶發出的請求的app instance上,這樣的話,可以避免一些不必要的數據衝突等,或者減少DEA中 app instance的負載。如果沒有找到cookie的話,那麼proxy就老老實實通過host來找到相應的ip:port,如果一個host有多個instance實例的話,proxy會通過某種策略來決策由哪個Instance來服務。


       在轉發請求的時候,實現在函數ServeHTTP()方法中,在實現過程中,我們需要清楚其中的幾個重要的方面即可:構建一個responseWriter並初始化某些屬性,判斷請求的類型並分別處理(TCP、websocket和HTTP),若爲HTTP類型則通過transport.RoundTrip方法發送請求並接收響應,最後填寫reponseWriter和設置cookie。這一部分的代碼隨着gorouter版本的更新會有一些形式上的不同,但是主要的功能和思想都是一致的。

       以上就是對gorouter一些模塊的源碼分析。


關於作者:

孫宏亮,DAOCLOUD軟件工程師。兩年來在雲計算方面主要研究PaaS領域的相關知識與技術。堅信輕量級虛擬化容器的技術,會給PaaS領域帶來深度影響,甚至決定未來PaaS技術的走向。


轉載請註明出處。

這篇文檔更多出於我本人的理解,肯定在一些地方存在不足和錯誤。希望本文能夠對接觸Cloud Foundry中router或者gorouter的人有些幫助,如果你對這方面感興趣,並有更好的想法和建議,也請聯繫我。

我的郵箱:[email protected]

新浪微博:@蓮子弗如清 

發佈了47 篇原創文章 · 獲贊 10 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章