詳解go-zero是如何做路由管理的
go-zero 是一個(gè)微服務(wù)框架,包含了 web 和 rpc 兩大部分。
而對(duì)于 web 框架來(lái)說(shuō),路由管理是必不可少的一部分,那么本文就來(lái)探討一下 go-zero 的路由管理是怎么做的,具體采用了哪種技術(shù)方案。
路由管理方案
路由管理方案有很多種,具體應(yīng)該如何選擇,應(yīng)該根據(jù)使用場(chǎng)景,以及實(shí)現(xiàn)的難易程度做綜合分析,下面介紹常見的三種方案。
注意這里只是做一個(gè)簡(jiǎn)單的概括性對(duì)比,更加詳細(xì)的內(nèi)容可以看這篇文章:HTTP Router 算法演進(jìn)。
標(biāo)準(zhǔn)庫(kù)方案
最簡(jiǎn)單的方案就是直接使用 map[string]func() 作為路由的數(shù)據(jù)結(jié)構(gòu),鍵為具體的路由,值為具體的處理方法。
//?路由管理數(shù)據(jù)結(jié)構(gòu)
type?ServeMux?struct?{
????mu????sync.RWMutex??????????//?對(duì)象操作讀寫鎖
????m?????map[string]muxEntry???//?存儲(chǔ)路由映射關(guān)系
}這種方案優(yōu)點(diǎn)就是實(shí)現(xiàn)簡(jiǎn)單,性能較高;缺點(diǎn)也很明顯,占用內(nèi)存更高,更重要的是不夠靈活。
Trie Tree
Trie Tree 也稱為字典樹或前綴樹,是一種用于高效存儲(chǔ)和檢索、用于從某個(gè)集合中查到某個(gè)特定 key 的數(shù)據(jù)結(jié)構(gòu)。

Trie Tree 時(shí)間復(fù)雜度低,和一般的樹形數(shù)據(jù)結(jié)構(gòu)相比,Trie Tree 擁有更快的前綴搜索和查詢性能。
和查詢時(shí)間復(fù)雜度為 O(1) 常數(shù)的哈希算法相比,Trie Tree 支持前綴搜索,并且可以節(jié)省哈希函數(shù)的計(jì)算開銷和避免哈希值碰撞的情況。
最后,Trie Tree 還支持對(duì)關(guān)鍵字進(jìn)行字典排序。
Radix Tree
Radix Tree(基數(shù)樹)是一種特殊的數(shù)據(jù)結(jié)構(gòu),用于高效地存儲(chǔ)和搜索字符串鍵值對(duì),它是一種基于前綴的樹狀結(jié)構(gòu),通過將相同前綴的鍵值對(duì)合并在一起來(lái)減少存儲(chǔ)空間的使用。

Radix Tree 通過合并公共前綴來(lái)降低存儲(chǔ)空間的開銷,避免了 Trie Tree 字符串過長(zhǎng)和字符集過大時(shí)導(dǎo)致的存儲(chǔ)空間過多問題,同時(shí)公共前綴優(yōu)化了路徑層數(shù),提升了插入、查詢、刪除等操作效率。
比如 Gin 框架使用的開源組件 HttpRouter 就是采用這個(gè)方案。
go-zero 路由規(guī)則
在使用 go-zero 開發(fā)項(xiàng)目時(shí),定義路由需要遵守如下規(guī)則:
- 路由必須以
/開頭 - 路由節(jié)點(diǎn)必須以
/分隔 - 路由節(jié)點(diǎn)中可以包含
:,但是:必須是路由節(jié)點(diǎn)的第一個(gè)字符,:后面的節(jié)點(diǎn)值必須要在結(jié)請(qǐng)求體中有path tag聲明,用于接收路由參數(shù) - 路由節(jié)點(diǎn)可以包含字母、數(shù)字、下劃線、中劃線
接下來(lái)就讓我們深入到源碼層面,相信看過源碼之后,你就會(huì)更懂這些規(guī)則的意義了。
go-zero 源碼實(shí)現(xiàn)
首先需要說(shuō)明的是,底層數(shù)據(jù)結(jié)構(gòu)使用的是二叉搜索樹,還不是很了解的同學(xué)可以看這篇文章:使用 Go 語(yǔ)言實(shí)現(xiàn)二叉搜索樹
節(jié)點(diǎn)定義
先看一下節(jié)點(diǎn)定義:
//?core/search/tree.go
const?(
????colon?=?':'
????slash?=?'/'
)
type?(
????//?節(jié)點(diǎn)
????node?struct?{
????????item?????interface{}
????????children?[2]map[string]*node
????}
????//?A?Tree?is?a?search?tree.
????Tree?struct?{
????????root?*node
????}
)重點(diǎn)說(shuō)一下 children,它是一個(gè)包含兩個(gè)元素的數(shù)組,元素 0 存正常路由鍵,元素 1 存以 : 開頭的路由鍵,這些是 url 中的變量,到時(shí)候需要替換成實(shí)際值。
舉一個(gè)例子,有這樣一個(gè)路由 /api/:user,那么 api 會(huì)存在 children[0],user 會(huì)存在 children[1]。
具體可以看看這段代碼:
func?(nd?*node)?getChildren(route?string)?map[string]*node?{
????//?判斷路由是不是以?:?開頭
????if?len(route)?>?0?&&?route[0]?==?colon?{
????????return?nd.children[1]
????}
????return?nd.children[0]
}路由添加
//?Add?adds?item?to?associate?with?route.
func?(t?*Tree)?Add(route?string,?item?interface{})?error?{
????//?需要路由以?/?開頭
????if?len(route)?==?0?||?route[0]?!=?slash?{
????????return?errNotFromRoot
????}
????if?item?==?nil?{
????????return?errEmptyItem
????}
????//?把去掉?/?的路由作為參數(shù)傳入
????err?:=?add(t.root,?route[1:],?item)
????switch?err?{
????case?errDupItem:
????????return?duplicatedItem(route)
????case?errDupSlash:
????????return?duplicatedSlash(route)
????default:
????????return?err
????}
}
func?add(nd?*node,?route?string,?item?interface{})?error?{
????if?len(route)?==?0?{
????????if?nd.item?!=?nil?{
????????????return?errDupItem
????????}
????????nd.item?=?item
????????return?nil
????}
????//?繼續(xù)判斷,看看是不是有多個(gè)?/
????if?route[0]?==?slash?{
????????return?errDupSlash
????}
????for?i?:=?range?route?{
????????//?判斷是不是?/,目的就是去處兩個(gè)?/?之間的內(nèi)容
????????if?route[i]?!=?slash?{
????????????continue
????????}
????????token?:=?route[:i]
????????//?看看有沒有子節(jié)點(diǎn),如果有子節(jié)點(diǎn),就在子節(jié)點(diǎn)下面繼續(xù)添加
????????children?:=?nd.getChildren(token)
????????if?child,?ok?:=?children[token];?ok?{
????????????if?child?!=?nil?{
????????????????return?add(child,?route[i+1:],?item)
????????????}
????????????return?errInvalidState
????????}
????????//?沒有子節(jié)點(diǎn),那么新建一個(gè)
????????child?:=?newNode(nil)
????????children[token]?=?child
????????return?add(child,?route[i+1:],?item)
????}
????children?:=?nd.getChildren(route)
????if?child,?ok?:=?children[route];?ok?{
????????if?child.item?!=?nil?{
????????????return?errDupItem
????????}
????????child.item?=?item
????}?else?{
????????children[route]?=?newNode(item)
????}
????return?nil
}主要部分代碼都已經(jīng)加了注釋,其實(shí)這個(gè)過程就是樹的構(gòu)建,如果讀過之前那篇文章,那這里還是比較好理解的。
路由查找
先來(lái)看一段 match 代碼:
func?match(pat,?token?string)?innerResult?{
????if?pat[0]?==?colon?{
????????return?innerResult{
????????????key:???pat[1:],
????????????value:?token,
????????????named:?true,
????????????found:?true,
????????}
????}
????return?innerResult{
????????found:?pat?==?token,
????}
}這里有兩個(gè)參數(shù):
pat:路由樹中存儲(chǔ)的路由token:實(shí)際請(qǐng)求的路由,可能包含參數(shù)值
還是剛才的例子 /api/:user,如果是 api,沒有以 : 開頭,那就不會(huì)走 if 邏輯。
接下來(lái)匹配 :user 部分,如果實(shí)際請(qǐng)求的 url 是 /api/zhangsan,那么會(huì)將 user 作為 key,zhangsan 作為 value 保存到結(jié)果中。
下面是搜索查找代碼:
//?Search?searches?item?that?associates?with?given?route.
func?(t?*Tree)?Search(route?string)?(Result,?bool)?{
????//?第一步先判斷是不是?/?開頭
????if?len(route)?==?0?||?route[0]?!=?slash?{
????????return?NotFound,?false
????}
????var?result?Result
????ok?:=?t.next(t.root,?route[1:],?&result)
????return?result,?ok
}
func?(t?*Tree)?next(n?*node,?route?string,?result?*Result)?bool?{
????if?len(route)?==?0?&&?n.item?!=?nil?{
????????result.Item?=?n.item
????????return?true
????}
????for?i?:=?range?route?{
????????//?和?add?里同樣的提取邏輯
????????if?route[i]?!=?slash?{
????????????continue
????????}
????????token?:=?route[:i]
????????return?n.forEach(func(k?string,?v?*node)?bool?{
????????????r?:=?match(k,?token)
????????????if?!r.found?||?!t.next(v,?route[i+1:],?result)?{
????????????????return?false
????????????}
????????????//?如果?url?中有參數(shù),會(huì)把鍵值對(duì)保存到結(jié)果中
????????????if?r.named?{
????????????????addParam(result,?r.key,?r.value)
????????????}
????????????return?true
????????})
????}
????return?n.forEach(func(k?string,?v?*node)?bool?{
????????if?r?:=?match(k,?route);?r.found?&&?v.item?!=?nil?{
????????????result.Item?=?v.item
????????????if?r.named?{
????????????????addParam(result,?r.key,?r.value)
????????????}
????????????return?true
????????}
????????return?false
????})
}以上就是路由管理的大部分代碼,整個(gè)文件也就 200 多行,邏輯也并不復(fù)雜,通讀之后還是很有收獲的。
到此這篇關(guān)于詳解go-zero是如何做路由管理的的文章就介紹到這了,更多相關(guān)go-zero路由管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoFrame代碼優(yōu)化gconv類型轉(zhuǎn)換避免重復(fù)定義map
這篇文章主要為大家介紹了GoFrame代碼優(yōu)化gconv類型轉(zhuǎn)換避免重復(fù)定義map示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Go高級(jí)特性探究之處理1分鐘百萬(wàn)請(qǐng)求詳解
對(duì)于大型的互聯(lián)網(wǎng)應(yīng)用程序,如電商平臺(tái)、社交網(wǎng)絡(luò)、金融交易平臺(tái)等,每秒鐘都會(huì)收到大量的請(qǐng)求,那么Go是如何處理這些百萬(wàn)請(qǐng)求的呢,下面就來(lái)和大家詳細(xì)講講2023-06-06
Golang?中實(shí)現(xiàn)?Set的思路詳解
本文介紹了Go中兩種set的實(shí)現(xiàn)原理,并在此基礎(chǔ)介紹了對(duì)應(yīng)于它們的兩個(gè)包簡(jiǎn)單使用,本文介紹的非常詳細(xì),需要的朋友參考下吧2024-01-01
基于Golang+Vue編寫一個(gè)手機(jī)遠(yuǎn)程控制電腦的懶人工具
這篇文章主要為大家詳細(xì)介紹了如何基于Golang+Vue編寫一個(gè)手機(jī)遠(yuǎn)程控制電腦的懶人工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-11-11
Golang項(xiàng)目搭配nginx部署反向代理負(fù)載均衡講解
這篇文章主要為大家介紹了Golang項(xiàng)目搭配nginx部署正反向代理負(fù)載均衡講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04

