Golang?HTTP服務(wù)超時控制實現(xiàn)原理分析
前情提要
因為上一篇提過,每次來一個請求,然后就會起一個goroutinue
那么導(dǎo)致的可能就是一個樹形結(jié)構(gòu)的請求圖,底下節(jié)點在執(zhí)行中如果發(fā)生了超時,那么就有協(xié)程會堆積,所以超時控制是有必要的,一般的實現(xiàn)都由一個頂層設(shè)計一個Context進行自頂向下傳遞,這樣可以從一個地方去避免多處執(zhí)行異常,對于Context的過多細節(jié)我不在這里一一闡述,有需要的我將單獨出一篇關(guān)于Context的介紹,下面我們就來看一下源碼是如何設(shè)計的:
Context
// Context's methods may be called by multiple goroutines simultaneously. type Context interface { // 當(dāng)Context被取消或者到了deadline,返回一個被關(guān)閉的channel Done() <-chan struct{} } // 函數(shù)句柄 type CancelFunc func()
設(shè)計初衷最關(guān)注的兩個點就是一個是如何主動結(jié)束下游,另一個是如何通知上游結(jié)束下游時
前者利用CancelFunc
后者利用Done
,后者需要不斷監(jiān)聽所以利用channel的返回值做監(jiān)聽
//創(chuàng)建退出Context func WithCancel(parent Context)(ctx Context,cancel CancelFunc){} //創(chuàng)建有超時時間的Context func WithTimeout(parent Context,timeout time.Duration)(Context,CancelFunc){} //創(chuàng)建有截止時間的Context func WithDeadline(parent Context,d time.Time)(Context,CancelFunc){}
WithCancel/WithTimeout/WithDeadline
都是通過定時器來自動觸發(fā)終結(jié)通知的,也就是說為父節(jié)點生成一個Done
的子節(jié)點,并且返回子節(jié)點的CancelFunc
函數(shù)句柄.
封裝自定義的Context
context.go
可以定義一個自己的Context,里面先擁有最基本的request和response兩個參數(shù),最后是因為思考到并發(fā)寫resposne的writer所以需要加入鎖成員變量以及防止重復(fù)寫的超時標(biāo)志位
package framework import ( "context" "encoding/json" "net/http" "sync" ) type Context struct { Request *http.Request ResponseWriter http.ResponseWriter hasTimeOut bool // 是否超時標(biāo)記位 writerMux *sync.Mutex } func NewContext()*Context{ return &Context{} } func (ctx *Context) BaseContext() context.Context { return ctx.Request.Context() } func (ctx *Context) Done() <-chan struct{} { return ctx.BaseContext().Done() } func (ctx *Context)SetHasTimeOut(){ ctx.hasTimeOut=true } func (ctx *Context)HasTimeOut()bool{ return ctx.hasTimeOut } // 自行封裝一個Json的方法 func (ctx *Context) Json(status int, obj interface{}) (err error) { if ctx.HasTimeOut(){ return nil } bytes, err := json.Marshal(obj) ctx.ResponseWriter.WriteHeader(status) _, err = ctx.ResponseWriter.Write(bytes) return } // 對外暴露鎖 func (ctx *Context) WriterMux() *sync.Mutex { return ctx.writerMux } // 統(tǒng)一處理器Controller方法 type ControllerHandler func(c *Context) error
main.go
業(yè)務(wù)方法使用一下自己封裝的Context,里面考慮到了超時控制以及并發(fā)讀寫,以及處理panic
package main import ( "context" "fmt" "testdemo1/coredemo/framework" "time" ) func FooController(ctx *framework.Context) error { durationCtx, cancel := context.WithTimeout(ctx.BaseContext(), time.Second) defer cancel() finish := make(chan struct{}, 1) panicChan := make(chan interface{}, 1) go func() { defer func() { if p := recover(); p != nil { panicChan <- p } }() time.Sleep(time.Second * 10) finish <- struct{}{} }() select { case p := <-panicChan: // panic fmt.Println("panic:",p) ctx.WriterMux().Lock() // 防止多個協(xié)程之前writer的消息亂序 defer ctx.WriterMux().Unlock() ctx.Json(500, "panic") case <-finish: // 正常退出 ctx.Json(200, "ok") fmt.Println("finish") case <-durationCtx.Done(): // 超時事件 ctx.WriterMux().Lock() defer ctx.WriterMux().Unlock() ctx.Json(500, "timed out") ctx.SetHasTimeOut() // 防止多次協(xié)程重復(fù)寫入超時日志 } return nil }
Core.go
serverHandler的類,進行處理請求的邏輯,可以先注冊對應(yīng)的映射器和方法
package framework import ( "net/http" ) type Core struct { RouterMap map[string]ControllerHandler } func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) { http.DefaultServeMux.ServeHTTP(writer, request) } func NewCore() *Core { return &Core{ RouterMap:make(map[string]ControllerHandler,0), } } // 注冊Get方法 func (c *Core) Get(pattern string, handler ControllerHandler) { c.RouterMap["get"+"-"+pattern]=handler } // 注冊Post方法 func (c *Core) Post(pattern string, handler ControllerHandler) { c.RouterMap["post"+"-"+pattern]=handler }
router.go
router統(tǒng)一管理注冊進我們對應(yīng)的http方法到我們的請求邏輯類里去
package main import "testdemo1/coredemo/framework" func registerRouter(core *framework.Core){ // 設(shè)置控制器 core.Get("foo",FooController) }
main.go
最后是主程序的執(zhí)行http服務(wù)監(jiān)聽和調(diào)用初始化router的注冊!傳入我們自定義的Context
package main import ( "log" "net/http" "testdemo1/coredemo/framework" ) func main() { server:=&http.Server{Addr: ":8080",Handler: framework.NewCore()} // 注冊router registerRouter(framework.NewCore()) err := server.ListenAndServe() if err!=nil{ log.Fatal(err) } }
本文到此結(jié)束!可以自行實現(xiàn)一遍,體驗一下,實際和gin的源碼封裝就是類似的~
我們下一篇再見
到此這篇關(guān)于Golang HTTP服務(wù)超時控制實現(xiàn)原理分析的文章就介紹到這了,更多相關(guān)Golang HTTP服務(wù)超時控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
分析Go語言中CSP并發(fā)模型與Goroutine的基本使用
我們都知道并發(fā)是提升資源利用率最基礎(chǔ)的手段,尤其是當(dāng)今大數(shù)據(jù)時代,流量對于一家互聯(lián)網(wǎng)企業(yè)的重要性不言而喻。串流顯然是不行的,尤其是對于web后端這種流量的直接載體。并發(fā)是一定的,問題在于怎么執(zhí)行并發(fā)。常見的并發(fā)方式有三種,分別是多進程、多線程和協(xié)程2021-06-06Go語言編譯程序從后臺運行,不出現(xiàn)dos窗口的操作
這篇文章主要介紹了Go語言編譯程序從后臺運行,不出現(xiàn)dos窗口的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04