Golang?HTTP服務(wù)超時(shí)控制實(shí)現(xiàn)原理分析
前情提要
因?yàn)?a href="http://www.fzitv.net/article/283878.htm" target="_blank">上一篇提過(guò),每次來(lái)一個(gè)請(qǐng)求,然后就會(huì)起一個(gè)goroutinue那么導(dǎo)致的可能就是一個(gè)樹形結(jié)構(gòu)的請(qǐng)求圖,底下節(jié)點(diǎn)在執(zhí)行中如果發(fā)生了超時(shí),那么就有協(xié)程會(huì)堆積,所以超時(shí)控制是有必要的,一般的實(shí)現(xiàn)都由一個(gè)頂層設(shè)計(jì)一個(gè)Context進(jìn)行自頂向下傳遞,這樣可以從一個(gè)地方去避免多處執(zhí)行異常,對(duì)于Context的過(guò)多細(xì)節(jié)我不在這里一一闡述,有需要的我將單獨(dú)出一篇關(guān)于Context的介紹,下面我們就來(lái)看一下源碼是如何設(shè)計(jì)的:
Context
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
// 當(dāng)Context被取消或者到了deadline,返回一個(gè)被關(guān)閉的channel
Done() <-chan struct{}
}
// 函數(shù)句柄
type CancelFunc func()設(shè)計(jì)初衷最關(guān)注的兩個(gè)點(diǎn)就是一個(gè)是如何主動(dòng)結(jié)束下游,另一個(gè)是如何通知上游結(jié)束下游時(shí)
前者利用CancelFunc 后者利用Done,后者需要不斷監(jiān)聽所以利用channel的返回值做監(jiān)聽
//創(chuàng)建退出Context
func WithCancel(parent Context)(ctx Context,cancel CancelFunc){}
//創(chuàng)建有超時(shí)時(shí)間的Context
func WithTimeout(parent Context,timeout time.Duration)(Context,CancelFunc){}
//創(chuàng)建有截止時(shí)間的Context
func WithDeadline(parent Context,d time.Time)(Context,CancelFunc){}WithCancel/WithTimeout/WithDeadline都是通過(guò)定時(shí)器來(lái)自動(dòng)觸發(fā)終結(jié)通知的,也就是說(shuō)為父節(jié)點(diǎn)生成一個(gè)Done的子節(jié)點(diǎn),并且返回子節(jié)點(diǎn)的CancelFunc函數(shù)句柄.
封裝自定義的Context
context.go
可以定義一個(gè)自己的Context,里面先擁有最基本的request和response兩個(gè)參數(shù),最后是因?yàn)樗伎嫉讲l(fā)寫resposne的writer所以需要加入鎖成員變量以及防止重復(fù)寫的超時(shí)標(biāo)志位
package framework
import (
"context"
"encoding/json"
"net/http"
"sync"
)
type Context struct {
Request *http.Request
ResponseWriter http.ResponseWriter
hasTimeOut bool // 是否超時(shí)標(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
}
// 自行封裝一個(gè)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
}
// 對(duì)外暴露鎖
func (ctx *Context) WriterMux() *sync.Mutex {
return ctx.writerMux
}
// 統(tǒng)一處理器Controller方法
type ControllerHandler func(c *Context) errormain.go
業(yè)務(wù)方法使用一下自己封裝的Context,里面考慮到了超時(shí)控制以及并發(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() // 防止多個(gè)協(xié)程之前writer的消息亂序
defer ctx.WriterMux().Unlock()
ctx.Json(500, "panic")
case <-finish: // 正常退出
ctx.Json(200, "ok")
fmt.Println("finish")
case <-durationCtx.Done(): // 超時(shí)事件
ctx.WriterMux().Lock()
defer ctx.WriterMux().Unlock()
ctx.Json(500, "timed out")
ctx.SetHasTimeOut() // 防止多次協(xié)程重復(fù)寫入超時(shí)日志
}
return nil
}Core.go
serverHandler的類,進(jìn)行處理請(qǐng)求的邏輯,可以先注冊(cè)對(duì)應(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),
}
}
// 注冊(cè)Get方法
func (c *Core) Get(pattern string, handler ControllerHandler) {
c.RouterMap["get"+"-"+pattern]=handler
}
// 注冊(cè)Post方法
func (c *Core) Post(pattern string, handler ControllerHandler) {
c.RouterMap["post"+"-"+pattern]=handler
}router.go
router統(tǒng)一管理注冊(cè)進(jìn)我們對(duì)應(yīng)的http方法到我們的請(qǐng)求邏輯類里去
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的注冊(cè)!傳入我們自定義的Context
package main
import (
"log"
"net/http"
"testdemo1/coredemo/framework"
)
func main() {
server:=&http.Server{Addr: ":8080",Handler: framework.NewCore()}
// 注冊(cè)router
registerRouter(framework.NewCore())
err := server.ListenAndServe()
if err!=nil{
log.Fatal(err)
}
}本文到此結(jié)束!可以自行實(shí)現(xiàn)一遍,體驗(yàn)一下,實(shí)際和gin的源碼封裝就是類似的~
我們下一篇再見
到此這篇關(guān)于Golang HTTP服務(wù)超時(shí)控制實(shí)現(xiàn)原理分析的文章就介紹到這了,更多相關(guān)Golang HTTP服務(wù)超時(shí)控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用GO語(yǔ)言實(shí)現(xiàn)Mysql數(shù)據(jù)庫(kù)CURD的簡(jiǎn)單示例
本文主要介紹了使用GO語(yǔ)言實(shí)現(xiàn)Mysql數(shù)據(jù)庫(kù)CURD的簡(jiǎn)單示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
windows安裝部署go超詳細(xì)實(shí)戰(zhàn)記錄(實(shí)測(cè)有用!)
Golang語(yǔ)言在近年來(lái)因?yàn)槠涓咝阅?、編譯速度快、開發(fā)成本低等特點(diǎn)逐漸得到大家的青睞,這篇文章主要給大家介紹了關(guān)于windows安裝部署go超詳細(xì)實(shí)戰(zhàn)的相關(guān)資料,需要的朋友可以參考下2023-02-02
GoFrame框架Scan類型轉(zhuǎn)換實(shí)例
這篇文章主要為大家介紹了GoFrame框架Scan類型轉(zhuǎn)換的實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Go語(yǔ)言實(shí)戰(zhàn)之詳細(xì)掌握正則表達(dá)式的應(yīng)用與技巧
正則表達(dá)式是一種從左到右與主題字符串匹配的模式,正則表達(dá)式用于替換字符串中的文本,驗(yàn)證表單,基于模式匹配從字符串中提取子字符串等等,這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言實(shí)戰(zhàn)之詳細(xì)掌握正則表達(dá)式的應(yīng)用與技巧,需要的朋友可以參考下2023-12-12
Go關(guān)鍵字defer的使用和底層實(shí)現(xiàn)
defer是Go語(yǔ)言的關(guān)鍵字,一般用于資源的釋放和異常的捕捉,defer語(yǔ)句后將其后面跟隨的語(yǔ)句進(jìn)行延遲處理,就是說(shuō)在函數(shù)執(zhí)行完畢后再執(zhí)行調(diào)用,也就是return的ret指令之前,本文給大家介紹了Go關(guān)鍵字defer的使用和底層實(shí)現(xiàn),需要的朋友可以參考下2023-11-11

