網(wǎng)站中的qq客服怎么做班級優(yōu)化大師是干什么用的
序言
Gin框架作為go語言使用最多的web框架,以其快速的響應(yīng)速度和對復(fù)雜http路由配置的支持受到程序員和媛們的喜愛,幾乎統(tǒng)治了web市場。但作為一名合格的程序員,要知其然更要知其所以然,不然八股文背的也沒有啥意思。本著這個原則鄙人打算站在前人的大腿根上從頭到尾梳理下Gin的執(zhí)行流程,主要涉及兩大部分:1. 服務(wù)器的建立(重點(diǎn)是:Gin是怎么處理和存儲各種不同的路由路徑和請求函數(shù)體的);2. 客戶端的連接(主要涉及根據(jù)路由尋找對應(yīng)函數(shù)體來執(zhí)行具體業(yè)務(wù)邏輯)
1. gn框架的誕生
1.1 go 原生web框架
go 原生的 web框架 在 net/http 包里,因不是本文重點(diǎn),所以只簡要介紹。
net/http 主要采用 map的原理來 存儲 路徑和handler 其中 key 是 路徑 value 是 handler ,如下圖的代碼
# ServeMux是一個HTTP請求多路復(fù)用器。其中 m 保存了 其請求路徑和handler的映射關(guān)系。
type ServeMux struct {mu sync.RWMutexm map[string]muxEntryes []muxEntry // slice of entries sorted from longest to shortest.hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {h Handlerpattern string
}
func TestHttp(t *testing.T) {// 創(chuàng)建路由器mux := http.NewServeMux()// 設(shè)置路由規(guī)則mux.HandleFunc("/hello", hello)mux.HandleFunc("/hello/hell", hello2)mux.HandleFunc("/hel/ww", hello2)mux.HandleFunc("/helw/*ww", hello2)// 創(chuàng)建服務(wù)器server := &http.Server{Addr: Addr,WriteTimeout: time.Second * 3, //超時時間Handler: mux, //路由規(guī)則 }// 監(jiān)聽端口并提供服務(wù)log.Println("Starting httpserver at " + Addr)err := server.ListenAndServe()if err != nil {panic(err)return}log.Fatal()
}func hello(w http.ResponseWriter, r *http.Request) {time.Sleep(1 * time.Second)w.Write([]byte("bye bye ,this is httpServer"))
}func hello2(w http.ResponseWriter, r *http.Request) {time.Sleep(1 * time.Second)w.Write([]byte("bye bye ,this is httpServer"))
}
其 建立的 map如下:
可以看出其確實(shí)建立了一個 map來存儲 路徑和handler的映射關(guān)系。采用map形式 查找的速度也比較的塊。但是為啥還要采用Gin框架呢。
我們來簡要梳理下我們程序員在工作過程中需要啥樣的web框架吧
- 需要一個可以處理通配符的框架,比如這種: aa/dd* 雖然我(net/http)不支持但是我 速度快啊
- 需要可以處理中間件的框架 比如 對日志的處理等 雖然我(net/http)不支持但是我 速度快啊
- 需要 支持分組的框架 比如 v1 v2這種不同的版本 雖然我(net/http)不支持但是我 速度快啊
- …
目前看來 net/http不適合這種復(fù)雜場景的業(yè)務(wù)邏輯 當(dāng)然 Google go開發(fā)組 目的只是提供一個簡小的web框架,設(shè)計目標(biāo)是簡單和通用。go開發(fā)組 當(dāng)然也想到了 要利用開源的優(yōu)勢為各路大神提供大顯神通的機(jī)會。問題是怎么接入呢,現(xiàn)實(shí)世界和虛擬世界的連接入口是 二維碼。那gin框架如何接入 net/http 呢,也就是如何在重新利用它的其他功能的情況下,再進(jìn)行擴(kuò)展呢 你當(dāng)然能想到了 這就是 interface 接口。
net/http框架中 確實(shí)是 通過實(shí)現(xiàn)接口來 進(jìn)行 路由查找 并找到要執(zhí)行的 hanlder(例如 上述代碼中的hello),這樣路由建立模塊和路由尋址查找handler模塊就可以通過不同實(shí)現(xiàn)來形成不同的第三方框架。建立路由模塊是第三方包獨(dú)自完成,而路由尋址查找模塊主要是實(shí)現(xiàn)了 如下接口:
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
這樣第三方框架 就可以復(fù)用net/http 的大部分功能(包括最重要的 epoll多路復(fù)用),并通過實(shí)現(xiàn) ServeHTTP 接口 來實(shí)現(xiàn)自己的 路由查找模塊(ps: 路由建立模塊需自己建立 這塊功能用不到接口) ,說完了 理論 那我們再來梳理下 net/http 從 建立連接 到 ServeHTTP的調(diào)用鏈,來驗(yàn)證下上述是否是這樣的。
1.2 原生 net/http的 調(diào)用鏈
以 TestHttp 這個函數(shù)為例,調(diào)用入口是 server.ListenAndServe() ,其調(diào)用鏈為
可以看到 其最終調(diào)用到了 ServeHTTP() 這個函數(shù),而所有第三方框架都是實(shí)現(xiàn)了 這個函數(shù)。這個函數(shù)包括 根據(jù)req函數(shù)中的 url獲取 handler(第三方框架提供) 、處理handler和對客戶端返回結(jié)果三大塊的功能。
這樣第三方框架就可以 實(shí)現(xiàn)ServeHTTP這個函數(shù)來 實(shí)現(xiàn)對 handler的獲取(怎么建立路由結(jié)構(gòu)是第三方框架自己定義)。
1.3 gin等第三方web框架和net/http關(guān)系
讓我們脫離源碼 來梳理下
可以看到 第三方 框架 主要是頭尾兩個地方跟 net/http不同 中間還是需要復(fù)用net/http代碼。后續(xù) 講解會圍繞這張流程圖展開,其中比較重要的是 圓圈 1、2和3,1 包括 gin 的引擎 engine 其包括建立壓縮前綴樹的功能并實(shí)現(xiàn)了 serverHttp接口 形成了 3,2主要涉及了多路復(fù)用技術(shù)。
以下的 步驟 2、3、4主要涉及圓圈1 的內(nèi)容,步驟5 涉及圓圈2 的內(nèi)容 ,步驟6 涉及圓圈3的內(nèi)容
2. gin框架簡介
2.1 gin框架發(fā)展歷程
gin 框架早期版本是基于julienschmidt/httprouter 發(fā)展而來,julienschmidt/httprouter是一個高性能的http請求器。但是隨著gin框架的發(fā)展 它逐漸發(fā)展出了自己的 路由實(shí)現(xiàn)器,實(shí)現(xiàn)源碼也部分參考 julienschmidt/httprouter 這也就是為什么好多資料都說 gin基于julienschmidt/httprouter 但是你去看它最新的源碼卻沒發(fā)現(xiàn)針對 julienschmidt/httprouter的引用。
gin框架之所以運(yùn)行效率高是因?yàn)椴捎昧艘环N叫 Radix Tree (壓縮前綴樹)的結(jié)構(gòu)體來存儲路由路徑,其是一種例如有如下路由:
/aa/bb
/aa/bd
/aa/cc
/ac/dd
/ee/ff
建立壓縮前綴樹 ,請思考下其建立的樹是左邊還是右邊呢
gin 框架的路由樹的建立 就是一步一步建立如上圖所示的 壓縮前綴樹 ps:真是的樹的節(jié)點(diǎn)比較復(fù)雜 但是大體步驟就是如此
那么壓縮前綴樹 有啥優(yōu)點(diǎn)呢 gin 為什么使用這種結(jié)構(gòu)來存儲器節(jié)點(diǎn)呢 直覺上看 我們可以想到兩點(diǎn) 1: 這種樹形結(jié)構(gòu) 查找的時間復(fù)雜度是時間復(fù)雜度為 o(k) ,k是字符串的長度 2: 壓縮證明其使用的空間比較少 可以看到 路徑中 有 6個a 但是 我們樹節(jié)點(diǎn)中只有 2個。這只是我們直觀看出來的,對不對呢,是否還有其他優(yōu)點(diǎn)呢?
答案: 對,當(dāng)然有其他優(yōu)點(diǎn),這種樹 也可以用來 進(jìn)行通配符的匹配 例如這種 /aa/bb/* ;還可以快速建立路由分組等。這兩種優(yōu)勢不是本文的重點(diǎn),感興趣的同學(xué)可以自行查閱資料。
既然你說gin 路由使用的是 壓縮前綴樹,口說無憑 我們來驗(yàn)證下吧 順便看下建立的是左邊還是右邊的壓縮前綴樹
2.2 gin框架的使用
下面示例代碼為(本文后續(xù)圍繞下面例子展開代碼講解):
func TestGin(t *testing.T) {// 創(chuàng)建一個默認(rèn)的路由引擎r := gin.Default()// 當(dāng)客戶端以GET方法請求路徑時,會執(zhí)行后面的匿名函數(shù)r.GET("/aa/bb", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/aa/bb",}) })r.GET("/aa/bd", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/aa/bd",}) })r.GET("/aa/cc", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/aa/cc",}) })r.GET("/ac/dd", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/ac/dd",}) })r.GET("/ee/ff", func(c *gin.Context) { c.JSON(200, gin.H{"route path ": "/ee/ff",}) })// 以上操作 **主要 涉及 圓圈 1**// 啟動HTTP服務(wù),默認(rèn)在0.0.0.0:8080啟動服務(wù) **涉及 net/http 框架處理主邏輯 內(nèi)部 主要 調(diào)用 net/http 包**r.Run()}
對上述代碼 debugger 可以得到 r 這個參數(shù)的 實(shí)例 實(shí)力分析如下 其中 壓縮前綴樹的節(jié)點(diǎn)node結(jié)構(gòu)體的結(jié)構(gòu)如下:
type node struct {path stringindices stringwildChild boolnType nodeTypepriority uint32children []*node // child nodes, at most 1 :param style node at the end of the arrayhandlers HandlersChainfullPath string
}
現(xiàn)在只需關(guān)注node節(jié)點(diǎn)的 path 和 children 這兩個參數(shù) node結(jié)構(gòu)體詳情會在后續(xù)步驟介紹
2.2.1 父節(jié)點(diǎn)
可以看到 父節(jié)點(diǎn) path == “/” 且有兩個孩子節(jié)點(diǎn)
2.2.2 第二層節(jié)點(diǎn)
可以看到 第二層 節(jié)點(diǎn) 左邊節(jié)點(diǎn) path==“a” 孩子個數(shù)為2 ;右邊節(jié)點(diǎn) pah 是 “ee/ff” 這里就是將節(jié)點(diǎn)進(jìn)行了壓縮 ee 和 ff 不用再拆分了。因?yàn)?ff是ee的唯一的一個孩子節(jié)點(diǎn) 因?yàn)閷ぶ仿窂轿ㄒ?所以可以向上合并,以便節(jié)省空間。
2.2.3 第三層節(jié)點(diǎn)
可以看到 左邊節(jié)點(diǎn) path==“a/” 孩子節(jié)點(diǎn)個數(shù)為2 ;右邊節(jié)點(diǎn) path==“c/dd”(壓縮了) 孩子節(jié)點(diǎn)為空
2.2.4 第四層節(jié)點(diǎn)
可以看到 左邊 節(jié)點(diǎn) path=“b” ,其有兩個孩子節(jié)點(diǎn);右邊節(jié)點(diǎn) path=“cc” 其沒有孩子節(jié)點(diǎn)
2.2.5 第五層 節(jié)點(diǎn)
可以看到 左邊節(jié)點(diǎn) path==“b” 無孩子節(jié)點(diǎn) ;右邊節(jié)點(diǎn) path==“d” 無孩子節(jié)點(diǎn)
到這里我們可以看出來其確實(shí)是建立了一顆 壓縮前綴樹??偨Y(jié)下來就是: 1. 孩子節(jié)點(diǎn)必須大于1(否則應(yīng)向上合并)2: 壓縮有兩層含義 第一層將 路由里面 重復(fù)的路徑 進(jìn)行壓縮 例如 字母a 壓縮后就剩2個;第二層 一個節(jié)點(diǎn)有一個子節(jié)點(diǎn)時 向上兼并 壓縮空間
所以建立的前綴樹是 右邊的。
r.run()執(zhí)行后 在瀏覽器輸入 路徑 就可以看到 對應(yīng)的函數(shù)被執(zhí)行(注意:默認(rèn)端口是8080)結(jié)果 如圖 這邊主要涉及 圓圈 2–>3
2.3 gin框架的執(zhí)行過程
梳理完畢 壓縮前綴樹的建立 那現(xiàn)在開始我們梳理下 整個 gin框架的流程圖 其實(shí)主要是圍繞 構(gòu)建的壓縮前綴樹展開的 ,我個人比較愿意先學(xué)習(xí)框架使用,然后再進(jìn)入細(xì)節(jié),這樣有一個提綱挈領(lǐng)的抓手,我們就知道這些細(xì)節(jié)在整體脈絡(luò)中的位置,不至于陷進(jìn)去失去了方向感。
通過1.3的圖可以看到 gin 框架 大概 分為 三大部分
- 創(chuàng)建 壓縮前綴樹 并且 將 路由 對應(yīng)的節(jié)點(diǎn) 按照規(guī)則 插入樹節(jié)點(diǎn) ---- 步驟一(圓圈1)
- 運(yùn)行 引擎 建立 對tcp套接字的監(jiān)聽 這里采用多路復(fù)用技術(shù) 進(jìn)行阻塞 等待鏈接到來 ---- 步驟二
- 瀏覽器 輸入 url 進(jìn)行客戶端請求 這時 喚醒阻塞的程序 從圓圈2 按照箭頭執(zhí)行順序 一直執(zhí)行到 圓圈3,然后在圓圈3 中遍歷 壓縮前綴樹 找到對應(yīng)的 handler (對于路徑 :/aa/bd 其 handler 為:func(c *gin.Context) { c.JSON(200, gin.H{"route path ": “/aa/bd”}) }) 執(zhí)行后 返回結(jié)果 ---- 步驟三
3. gn框架源碼–四種重要的結(jié)構(gòu)體
框架一般都會采用面向?qū)ο蟮姆绞絹順?gòu)建,而面向?qū)ο笾凶钪匾暮诵木褪墙Y(jié)構(gòu)體。gin框架四種重要的結(jié)構(gòu)體 分別是 Engine/RouterGroup/Node/context ,其中Engine 包含了 RouteGroup和Node 是gin框架的引擎結(jié)構(gòu)體 ;Node是壓縮前綴樹的樹節(jié)點(diǎn),用來保存 壓縮路徑和handler,在2.2.1----2.2.5中已經(jīng)做過簡要介紹 ;Context 結(jié)構(gòu)體官方介紹是 gin最重要的結(jié)構(gòu)體 ,它允許我們在中間件之間傳遞變量,管理流,處理 request 請求體和 respose 響應(yīng)體??梢哉f gin 框架基本上是圍繞著這四個結(jié)構(gòu)體來操作的。
3.1 Engine 結(jié)構(gòu)體
type Engine struct {RouterGroup // 路由組 ...... // 這里為了使得文章簡短 一些本文講解沒用到的 屬性 沒有列舉 感興趣的可以自己研究下 // UseH2C enable h2c support.UseH2C bool// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.ContextWithFallback booldelims render.DelimssecureJSONPrefix stringHTMLRender render.HTMLRenderFuncMap template.FuncMapallNoRoute HandlersChainallNoMethod HandlersChainnoRoute HandlersChainnoMethod HandlersChainpool sync.Pool // 這個池化技術(shù) 用來存儲 Context結(jié)構(gòu)體。 它是 gin框架很重要的結(jié)構(gòu)體 主要用來 處理 request和 respose 請求trees methodTrees // 方法樹 針對 Get/Post/Delete 等不同請求 都生成一個樹 9種請求 9種樹 但實(shí)現(xiàn)原理都是相同的 本文只介紹 Get方法,其 包含了 Node 結(jié)構(gòu)體maxParams uint16maxSections uint16trustedProxies []stringtrustedCIDRs []*net.IPNet
}
Engine 結(jié)構(gòu)體 是 gin 框架的入口,其包含了許多屬性 但對于 我們學(xué)習(xí)jin框架核心執(zhí)行邏輯來說,只需要知道 RouterGroup/pool/trees 這三個就行了
3.2 RouteGroup結(jié)構(gòu)體
type RouterGroup struct {Handlers HandlersChain // 需要處理的 handler 鏈,一般是 默認(rèn)hanlder(例如 處理 logger和panic的handlewr)+ group組的中間件handler(例如 鑒權(quán)等)+ 用戶 注冊的 handlerbasePath string // 組的基本路徑engine *Engine // gin 框架 引擎root bool // 跟節(jié)點(diǎn)
}
RouterGroup 主要是用來 對路由進(jìn)行操作的,包括對post/get等方法的處理。
3.3 Node 結(jié)構(gòu)體
type node struct {path string // 節(jié)點(diǎn)路勁indices string // 其子節(jié)點(diǎn)的 path的第一個單詞 組成的 字符串 用來快速定位路徑尋址時 是否走此孩子節(jié)點(diǎn)wildChild boolnType nodeType // 節(jié)點(diǎn)類型priority uint32 // 優(yōu)先級 從左往右 越左側(cè) 優(yōu)先級越大 優(yōu)先從左邊開始 說明 左邊的 重復(fù)的路徑前綴比較多 同層其優(yōu)先級越高 子節(jié)點(diǎn)越多 ,一般情況下 priority等于其直屬孩子節(jié)點(diǎn)個數(shù),且如果其直屬孩子節(jié)點(diǎn)為一個 或者 為空,其 優(yōu)先級 為 1children []*node // child nodes, at most 1 :param style node at the end of the arrayhandlers HandlersChain // 處理器 存儲 節(jié)點(diǎn) handler 鏈 fullPath string // 全路徑 fullPath
}
node 是壓縮前綴樹子節(jié)點(diǎn),是gin框架之所以速度快的核心原因,也是我們本篇文章重點(diǎn)需要介紹和理解的結(jié)構(gòu)體。
對于節(jié)點(diǎn)屬性的理解 可以對照著 標(biāo)題 2.21----2.2.5來理解
3.4 context 結(jié)構(gòu)體
type Context struct {writermem responseWriterRequest *http.RequestWriter ResponseWriterParams Paramshandlers HandlersChainindex int8fullPath stringengine *Engineparams *ParamsskippedNodes *[]skippedNode// This mutex protects Keys map.mu sync.RWMutex// Keys is a key/value pair exclusively for the context of each request.Keys map[string]any// Errors is a list of errors attached to all the handlers/middlewares who used this context.Errors errorMsgs// Accepted defines a list of manually accepted formats for content negotiation.Accepted []string// queryCache caches the query result from c.Request.URL.Query().queryCache url.Values// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,// or PUT body parameters.formCache url.Values// SameSite allows a server to define a cookie attribute making it impossible for// the browser to send this cookie along with cross-site requests.sameSite http.SameSite
}
context 是gin框架最重要的結(jié)構(gòu)體 其包含了對 請求 和 響應(yīng)的 處理邏輯,可以在中間件之間傳遞數(shù)據(jù)流。因不是本文的理解gin框架的需要用到的結(jié)構(gòu)體,暫不做過多介紹,請自行用谷歌百度一下。