对åç§Go httpè·¯ç±æ¡æ¶çæ¯è¾ï¼ Irisææ¾èåºï¼å®çæ§è½è¿è¿è¶
è¿å
¶å®Golang httpè·¯ç±æ¡æ¶ã
ä½æ¯ï¼å¨çå®çç¯å¢ä¸ï¼Irisççå°±æ¯æå¿«çGolang httpè·¯ç±æ¡æ¶åï¼
Benchmarkæµè¯åæ
å¨é£ç¯æç« ä¸æ使ç¨çæ¯Julien Schmidtç æµè¯ä»£ç ,ä»æ¨¡æäºéæè·¯ç±ãGithub APIãGoolge+ APIãParse APIçåç§æ
åµï¼å 为è¿äºAPIæ¯ç¥åç½ç«çå¼æ¾çAPIï¼çèµ·æ¥æµè¯æºçå®å¯é çã
ä½æ¯ï¼è¿ä¸ªæµè¯åå¨çä¸ä¸ªä¸¥éçé®é¢ï¼å°±æ¯Handlerçä¸å¡é»è¾é常çç®åï¼å个æ¡æ¶çhandler类似ï¼æ¯å¦IrisçHandlerçå®ç°ï¼
funcirisHandler(_ *iris.Context) {}funcirisHandlerWrite(c *iris.Context) { io.WriteString(c.ResponseWriter, c.Param("name"))}funcirisHandlerTest(c *iris.Context) { io.WriteString(c.ResponseWriter, c.Request.RequestURI)}
å ä¹æ²¡æä»»ä½çä¸å¡é»è¾ï¼æå¤æ¯å¾Responseä¸åå
¥ä¸ä¸ªå符串ã
è¿åç产ç¯å¢ä¸çæ
åµä¸¥éä¸ç¬¦!
å®é
ç产åè¯å®ä¼æä¸äºä¸å¡çå¤çï¼æ¯å¦åæ°çæ ¡éªï¼æ°æ®ç计ç®ï¼æ¬å°æ件ç读åãè¿ç¨æå¡çè°ç¨ãç¼åç读åãæ°æ®åºç读åååå
¥çï¼æäºæä½å¯è½è±è´¹çæ¶é´å¾å¤ï¼ä¸ä¸¤ä¸ªæ¯«ç§å°±å¯ä»¥æå®ï¼æçå´å¾èæ¶ï¼å¯è½éè¦å å毫ç§ï¼æ¯å¦ï¼
ä»ä¸ä¸ªç½ç»è¿æ¥ä¸è¯»åæ°æ® åæ°æ®å°ç¡¬çä¸ è°ç¨å
¶å®æå¡ï¼çå¾
æå¡ç»æçè¿å â¦â¦
è¿ææ¯æ们常ç¨çcase,èä¸æ¯ä¸ä¸ªç®åçåå符串ã
å æ¤é£ä¸ªæµè¯æ¡æ¶çHandlerè¿åºè¯¥å å
¥æ¶é´è±è´¹çæ
åµã
模æçå®çHandlerçæ
åµ
æ们模æä¸ä¸çå®çæ
åµï¼ççIrisæ¡æ¶åGolangå
ç½®çHttpè·¯ç±æ¡æ¶çæ§è½å¦ä½ã
é¦å
使ç¨Iriså®ç°ä¸ä¸ªHttp Server:
packagemainimport("os""strconv""time""github.com/kataras/iris")funcmain() { api := iris.New() api.Get("/rest/hello",func(c *iris.Context) { sleepTime, _ := strconv.Atoi(os.Args[1])ifsleepTime >0{ time.Sleep(time.Duration(sleepTime) * time.Millisecond) } c.Text("Hello world") }) api.Listen(":8080")}
æ们å¯ä»¥ä¼ éç»å®ä¸ä¸ªæ¶é´è±è´¹çåæ°sleepTimeï¼æ¨¡æè¿ä¸ªHandlerå¨å¤çä¸å¡æ¶è¦è±è´¹çæ¶é´ï¼å®ä¼è®©å¤çè¿ä¸ªHandlerçæåsleepTime毫ç§ï¼å¦æ为0,åä¸éè¦æåï¼è¿ç§æ
åµç±»ä¼¼ä¸é¢çæµè¯ã
ç¶åæ们使ç¨Goå
ç½®çè·¯ç±åè½å®ç°ä¸ä¸ªHttp Server:
packagemainimport("log""net/http""os""strconv""time")// There are some golang RESTful libraries and mux libraries but i use the simplest to test.funcmain() { http.HandleFunc("/rest/hello",func(w http.ResponseWriter, r *http.Request) { sleepTime, _ := strconv.Atoi(os.Args[1])ifsleepTime >0{ time.Sleep(time.Duration(sleepTime) * time.Millisecond) } w.Write([]byte("Hello world")) }) err := http.ListenAndServe(":8080",nil)iferr !=nil{ log.Fatal("ListenAndServe: ", err) }}
ç¼è¯ä¸¤ä¸ªç¨åºè¿è¡æµè¯ã
1ãé¦å
è¿è¡ä¸å¡é»è¾æ¶é´è±è´¹ä¸º0çæµè¯
è¿è¡ç¨åº iris 0,ç¶åæ§è¡ wrk -t16 -c100 -d30s
http://127.0.0.1:8080/rest/helloè¿è¡å¹¶å100,æç»30ç§çæµè¯ã
irisçååç为46155 requests/secondã
è¿è¡ç¨åº gomux 0,ç¶åæ§è¡ wrk -t16 -c100 -d30s
http://127.0.0.1:8080/rest/helloè¿è¡å¹¶å100,æç»30ç§çæµè¯ã
Goå
ç½®çè·¯ç±ç¨åºçååç为55944 requests/secondã
两è
çååéå·®å«ä¸å¤§ï¼irisç¥å·®ä¸ç¹
2ãç¶åè¿è¡ä¸å¡é»è¾æ¶é´è±è´¹ä¸º10çæµè¯
è¿è¡ç¨åº iris 10,ç¶åæ§è¡ wrk -t16 -c100 -d30s
http://127.0.0.1:8080/rest/helloè¿è¡å¹¶å100,æç»30ç§çæµè¯ã
irisçååç为97 requests/secondã
è¿è¡ç¨åº gomux 10,ç¶åæ§è¡ wrk -t16 -c100 -d30s
http://127.0.0.1:8080/rest/helloè¿è¡å¹¶å100,æç»30ç§çæµè¯ã
Goå
ç½®çè·¯ç±ç¨åºçååç为9294 requests/secondã
3ãæåè¿è¡ä¸å¡é»è¾æ¶é´è±è´¹ä¸º1000çæµè¯
è¿æ¬¡æ¨¡æä¸ä¸ªæ端çæ
åµï¼ä¸å¡å¤çå¾æ
¢ï¼å¤çä¸ä¸ªä¸å¡éè¦1ç§çæ¶é´ã
è¿è¡ç¨åº iris 1000,ç¶åæ§è¡ wrk -t16 -c100 -d30s
http://127.0.0.1:8080/rest/helloè¿è¡å¹¶å100,æç»30ç§çæµè¯ã
irisçååç为1 requests/secondã
è¿è¡ç¨åº gomux 1000,ç¶åæ§è¡ wrk -t16 -c100 -d30s
http://127.0.0.1:8080/rest/helloè¿è¡å¹¶å100,æç»30ç§çæµè¯ã
Goå
ç½®çè·¯ç±ç¨åºçååç为95 requests/secondã
å¯ä»¥çå°ï¼å¦æå ä¸ä¸å¡é»è¾çå¤çæ¶é´ï¼Goå
ç½®çè·¯ç±åè½è¦è¿è¿å¥½äºIris, çè³å¯ä»¥è¯´Irisçè·¯ç±æ ¹æ¬æ æ³åºç¨çæä¸å¡é»è¾ç产åä¸ï¼éçä¸å¡é»è¾çæ¶é´èè´¹å 大ï¼irisçååéæ¥å§ä¸éã
è对äºGoçå
置路ç±æ¥è¯´ï¼ä¸å¡é»è¾çæ¶é´èè´¹å 大ï¼å个clientä¼çå¾
æ´é¿çæ¶é´ï¼ä½æ¯å¹¶åé大çç½ç«æ¥è¯´ï¼ååçä¸ä¼ä¸é太å¤ã
æ¯å¦æ们ç¨1000ç并åéæµè¯ gomux 10å gomux 1000ã
gomux 10: ååç为47664 gomux 1000: ååç为979
è¿ææ¯Httpç½ç«çå®çæ
åµï¼å 为æ们è¦åºä»çç½ç«ç并åéï¼ç½ç«åºè¯¥æ¯æåæ¶æå°½å¯è½å¤çç¨æ·è®¿é®ï¼å³ä½¿å个ç¨æ·å¾å°è¿å页é¢éè¦ä¸ç¾æ¯«ç§ä¹å¯ä»¥æ¥åã
èIriså¨ä¸å¡é»è¾çå¤çæ¶é´å¢å¤§çæ
åµä¸ï¼æ æ³æ¯æ大çååçï¼å³ä½¿å¨å¹¶åéå¾å¤§çæ
åµä¸(æ¯å¦1000),ååçä¹å¾ä½ã
æ·±å
¥äºè§£Go http serverçå®ç°
Go http serverå®ç°çæ¯æ¯ä¸ªrequest对åºä¸ä¸ªgoroutine (goroutine per request), èèå°Http Keep-Aliveçæ
åµï¼æ´åç¡®ç说æ¯æ¯ä¸ªè¿æ¥å¯¹åºä¸ä¸ªgoroutine(goroutine per connection)ã
å 为goroutineæ¯é常轻é级çï¼ä¸ä¼åJavaé£æ · Thread per requestä¼å¯¼è´æå¡å¨èµæºä¸è¶³ï¼æ æ³å建å¾å¤çThreadï¼ Golangå¯ä»¥å建足å¤å¤çgoroutineï¼æ以goroutine per requestçæ¹å¼å¨Golangä¸æ²¡æé®é¢ãèä¸è¿è¿æä¸ä¸ªå¥½å¤ï¼å 为requestæ¯å¨ä¸ä¸ªgoroutineä¸å¤ççï¼ä¸å¿
èè对åä¸ä¸ªRequest/Response并å读åçé®é¢ã
å¦ä½æ¥çHandleræ¯å¨åªä¸ä¸ªgoroutineä¸æ§è¡çå¢?æ们éè¦å®ç°ä¸ä¸ªå½æ°æ¥è·ågoroutineçId:
funcgoID()int{varbuf[64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]),"goroutine "))[0] id, err := strconv.Atoi(idField)iferr !=nil{panic(fmt.Sprintf("cannot get goroutine id: %v", err)) }returnid}
ç¶åå¨handlerä¸æå°åºå½åçgoroutine id:
func(c *iris.Context) { fmt.Println(goID()) â¦â¦}
å
func(w http.ResponseWriter, r *http.Request) { fmt.Println(goID()) â¦â¦}
å¯å¨ gomux 0,ç¶åè¿è¡ ab -c 5 -n 5
http://localhost:8080/rest/helloæµè¯ä¸ä¸ï¼apacheçabå½ä»¤ä½¿ç¨5个并å并ä¸æ¯ä¸ªå¹¶å两个请æ±è®¿é®æå¡å¨ã
å¯ä»¥çå°æå¡å¨çè¾åºï¼
å 为没ææå® -kåæ°ï¼æ¯ä¸ªclientåé两个请æ±ä¼å建两个è¿æ¥ã
ä½ å¯ä»¥å ä¸ -kåæ°ï¼å¯ä»¥çåºä¼æéå¤çgoroutine idåºç°ï¼è¡¨æåä¸ä¸ªæä¹
è¿æ¥ä¼ä½¿ç¨åä¸ä¸ªgoroutineå¤çã
以ä¸æ¯éè¿å®éªéªè¯æ们çç论ï¼ä¸é¢æ¯ä»£ç åæã
net/http/server.goç 第2146è¡ go c.serve()表æï¼å¯¹äºä¸ä¸ªhttpè¿æ¥ï¼ä¼å¯å¨ä¸ä¸ªgoroutine:
func(srv *Server) Serve(l net.Listener) error {deferl.Close()iffn := testHookServerServe; fn !=nil{ fn(srv, l) }vartempDelay time.Duration// how long to sleep on accept failureiferr := srv.setupHTTP2(); err !=nil{returnerr }for{ rw, e := l.Accept() â¦â¦ tempDelay =0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can returngoc.serve() }}
èè¿ä¸ª c.serveæ¹æ³ä¼ä»è¿æ¥ä¸ 读årequest交ç±handlerå¤ç:
func(c *conn) serve() { â¦â¦for{ w, err := c.readRequest() â¦â¦ req := w.req serverHandler{c.server}.ServeHTTP(w, w.req)ifc.hijacked() {return } w.finishRequest()if!w.shouldReuseConnection() {ifw.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() }return } c.setState(c.rwc, StateIdle) }}
è ServeHTTPçå®ç°å¦ä¸ï¼å¦æ没æé
ç½®handleræè
è·¯ç±å¨ï¼å使ç¨ç¼ºçç DefaultServeMuxã
func(sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handlerifhandler ==nil{ handler = DefaultServeMux }ifreq.RequestURI =="*"&& req.Method =="OPTIONS"{ handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}
å¯ä»¥çåºè¿é并没ææ°å¼goroutine,èæ¯å¨åä¸ä¸ªconnection对åºçgoroutineä¸æ§è¡çãå¦æè¯ç¨Keep-Alive,è¿æ¯å¨è¿ä¸ªconnection对åºçgoroutineä¸æ§è¡ã
æ£å¦æ³¨éä¸æ说çé£æ ·ï¼
// HTTP cannot have multiple simultaneous active requests.[*] // Until the server replies to this request, it can't read another, // so we might as well run the handler in this goroutine. // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. serverHandler{c.server}.ServeHTTP(w, w.req)
å æ¤ä¸å¡é»è¾çæ¶é´è±è´¹ä¼å½±åå个goroutineçæ§è¡æ¶é´ï¼å¹¶ä¸åæ å°å®¢æ·çæµè§å¨æ¯æ¯å»¶è¿æ¶é´latencyå¢å¤§äºï¼å¦æ并åé足å¤å¤ï¼å½±åçæ¯ç³»ç»ä¸çgoroutineçæ°é以åå®ä»¬çè°åº¦ï¼ååçä¸ä¼å§çå½±åã
Irisçåæ
å¦æä½ ä½¿ç¨Irisæ¥çæ¯ä¸ªHandleræ¯ä½¿ç¨åªä¸ä¸ªgoroutineæ§è¡çï¼ä¼åç°æ¯ä¸ªè¿æ¥ä¹ä¼ç¨ä¸åçgoroutineæ§è¡ï¼å¯æ¯æ§è½å·®å¨åªå¿å¢ï¼
æè
说,æ¯ä»ä¹åå 导è´Irisçæ§è½æ¥å§ä¸éå¢ï¼
Irisæå¡å¨ççå¬å为è¿æ¥å¯å¨ä¸ä¸ªgoroutine没æä»ä¹ææ¾ä¸åï¼éè¦çä¸åå¨ä¸Routerå¤çRequestçé»è¾ã
åå å¨äºIris为äºæä¾æ§è½ï¼ç¼åäºcontext,对äºç¸åç请æ±urlåmethod,å®ä¼ä»ç¼åä¸ä½¿ç¨ç¸åçcontextã
func(r *MemoryRouter) ServeHTTP(res http.ResponseWriter, req *http.Request) {ifctx := r.cache.GetItem(req.Method, req.URL.Path); ctx !=nil{ ctx.Redo(res, req)return } ctx := r.getStation().pool.Get().(*Context) ctx.Reset(res, req)ifr.processRequest(ctx) {//if something found and served then add it's clone to the cache r.cache.AddItem(req.Method, req.URL.Path, ctx.Clone()) } r.getStation().pool.Put(ctx)}
ç±äºå¹¶åéè¾å¤§çæ¶åï¼å¤ä¸ªclientç请æ±é½ä¼è¿å
¥å°ä¸é¢ç ServeHTTPæ¹æ³ä¸ï¼å¯¼è´ç¸åç请æ±ä¼è¿å
¥ä¸é¢çé»è¾ï¼
ifctx := r.cache.GetItem(req.Method, req.URL.Path); ctx !=nil{ ctx.Redo(res, req)return}
ctx.Redo(res, req)导è´ä¸æ循ç¯ï¼ç´å°æ¯ä¸ªè¯·æ±å¤çå®æ¯ï¼å°contextæ¾åå°æ± åä¸ã
æ以对äºIrisæ¥è¯´ï¼å¹¶åé大çæ
åµä¸,对äºç¸åç请æ±(req.URL.PathåMethodç¸å)ä¼è¿å
¥æéçç¶æï¼å¯¼è´æ§è½ä½ä¸ã