欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解go-zero是如何做路由管理的

 更新時間:2023年08月07日 09:02:36   作者:AlwaysBeta  
go-zero 是一個微服務(wù)框架,包含了 web 和 rpc 兩大部分,而對于 web 框架來說,路由管理是必不可少的一部分,那么本文就來探討一下 go-zero 的路由管理是怎么做的吧

go-zero 是一個微服務(wù)框架,包含了 web 和 rpc 兩大部分。

而對于 web 框架來說,路由管理是必不可少的一部分,那么本文就來探討一下 go-zero 的路由管理是怎么做的,具體采用了哪種技術(shù)方案。

路由管理方案

路由管理方案有很多種,具體應(yīng)該如何選擇,應(yīng)該根據(jù)使用場景,以及實現(xiàn)的難易程度做綜合分析,下面介紹常見的三種方案。

注意這里只是做一個簡單的概括性對比,更加詳細的內(nèi)容可以看這篇文章:HTTP Router 算法演進。

標(biāo)準(zhǔn)庫方案

最簡單的方案就是直接使用 map[string]func() 作為路由的數(shù)據(jù)結(jié)構(gòu),鍵為具體的路由,值為具體的處理方法。

//?路由管理數(shù)據(jù)結(jié)構(gòu)
type?ServeMux?struct?{
????mu????sync.RWMutex??????????//?對象操作讀寫鎖
????m?????map[string]muxEntry???//?存儲路由映射關(guān)系
}

這種方案優(yōu)點就是實現(xiàn)簡單,性能較高;缺點也很明顯,占用內(nèi)存更高,更重要的是不夠靈活。

Trie Tree

Trie Tree 也稱為字典樹或前綴樹,是一種用于高效存儲和檢索、用于從某個集合中查到某個特定 key 的數(shù)據(jù)結(jié)構(gòu)。

Trie Tree 時間復(fù)雜度低,和一般的樹形數(shù)據(jù)結(jié)構(gòu)相比,Trie Tree 擁有更快的前綴搜索和查詢性能。

和查詢時間復(fù)雜度為 O(1) 常數(shù)的哈希算法相比,Trie Tree 支持前綴搜索,并且可以節(jié)省哈希函數(shù)的計算開銷和避免哈希值碰撞的情況。

最后,Trie Tree 還支持對關(guān)鍵字進行字典排序。

Radix Tree

Radix Tree(基數(shù)樹)是一種特殊的數(shù)據(jù)結(jié)構(gòu),用于高效地存儲和搜索字符串鍵值對,它是一種基于前綴的樹狀結(jié)構(gòu),通過將相同前綴的鍵值對合并在一起來減少存儲空間的使用。

Radix Tree 通過合并公共前綴來降低存儲空間的開銷,避免了 Trie Tree 字符串過長和字符集過大時導(dǎo)致的存儲空間過多問題,同時公共前綴優(yōu)化了路徑層數(shù),提升了插入、查詢、刪除等操作效率。

比如 Gin 框架使用的開源組件 HttpRouter 就是采用這個方案。

go-zero 路由規(guī)則

在使用 go-zero 開發(fā)項目時,定義路由需要遵守如下規(guī)則:

  • 路由必須以 / 開頭
  • 路由節(jié)點必須以 / 分隔
  • 路由節(jié)點中可以包含 :,但是 : 必須是路由節(jié)點的第一個字符,: 后面的節(jié)點值必須要在結(jié)請求體中有 path tag 聲明,用于接收路由參數(shù)
  • 路由節(jié)點可以包含字母、數(shù)字、下劃線、中劃線

接下來就讓我們深入到源碼層面,相信看過源碼之后,你就會更懂這些規(guī)則的意義了。

go-zero 源碼實現(xiàn)

首先需要說明的是,底層數(shù)據(jù)結(jié)構(gòu)使用的是二叉搜索樹,還不是很了解的同學(xué)可以看這篇文章:使用 Go 語言實現(xiàn)二叉搜索樹

節(jié)點定義

先看一下節(jié)點定義:

//?core/search/tree.go
const?(
????colon?=?':'
????slash?=?'/'
)
type?(
????//?節(jié)點
????node?struct?{
????????item?????interface{}
????????children?[2]map[string]*node
????}
????//?A?Tree?is?a?search?tree.
????Tree?struct?{
????????root?*node
????}
)

重點說一下 children,它是一個包含兩個元素的數(shù)組,元素 0 存正常路由鍵,元素 1 存以 : 開頭的路由鍵,這些是 url 中的變量,到時候需要替換成實際值。

舉一個例子,有這樣一個路由 /api/:user,那么 api 會存在 children[0],user 會存在 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ù)判斷,看看是不是有多個?/
????if?route[0]?==?slash?{
????????return?errDupSlash
????}
????for?i?:=?range?route?{
????????//?判斷是不是?/,目的就是去處兩個?/?之間的內(nèi)容
????????if?route[i]?!=?slash?{
????????????continue
????????}
????????token?:=?route[:i]
????????//?看看有沒有子節(jié)點,如果有子節(jié)點,就在子節(jié)點下面繼續(xù)添加
????????children?:=?nd.getChildren(token)
????????if?child,?ok?:=?children[token];?ok?{
????????????if?child?!=?nil?{
????????????????return?add(child,?route[i+1:],?item)
????????????}
????????????return?errInvalidState
????????}
????????//?沒有子節(jié)點,那么新建一個
????????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)加了注釋,其實這個過程就是樹的構(gòu)建,如果讀過之前那篇文章,那這里還是比較好理解的。

路由查找

先來看一段 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,
????}
}

這里有兩個參數(shù):

  • pat:路由樹中存儲的路由
  • token:實際請求的路由,可能包含參數(shù)值

還是剛才的例子 /api/:user,如果是 api,沒有以 : 開頭,那就不會走 if 邏輯。

接下來匹配 :user 部分,如果實際請求的 url 是 /api/zhangsan,那么會將 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ù),會把鍵值對保存到結(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
????})
}

以上就是路由管理的大部分代碼,整個文件也就 200 多行,邏輯也并不復(fù)雜,通讀之后還是很有收獲的。

到此這篇關(guān)于詳解go-zero是如何做路由管理的的文章就介紹到這了,更多相關(guān)go-zero路由管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go grpc高級用法

    go grpc高級用法

    RPC是遠程過程調(diào)用,可以像調(diào)用本地服務(wù)一樣取調(diào)用遠程服務(wù),本文主要介紹了go grpc高級用法,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • golang讀取yaml配置文件的示例代碼

    golang讀取yaml配置文件的示例代碼

    在項目開發(fā)中,經(jīng)常需要把一些配置文件常量提取到統(tǒng)一配置文件進行維護,go項目在開發(fā)中常常把需要維護的常量或者配置提取到y(tǒng)aml文件,所以本文主要來為大家介紹一下golang如何讀取yaml配置文件吧
    2023-11-11
  • Go語言學(xué)習(xí)之golang-jwt/jwt的教程分享

    Go語言學(xué)習(xí)之golang-jwt/jwt的教程分享

    jwt是?json?web?token的簡稱。go使用jwt目前,主流使用的jwt庫是golang-jwt/jwt。本文就來和大家講講golang-jwt/jwt的具體使用,需要的可以參考一下
    2023-01-01
  • golang使用viper解析配置文件的示例代碼

    golang使用viper解析配置文件的示例代碼

    Viper是一個輕量級的、易于使用的配置工具庫,它允許你在Go應(yīng)用中方便地管理配置,Viper支持從多種來源讀取配置,如環(huán)境變量、命令行參數(shù)、文件、甚至是加密的數(shù)據(jù)存儲,本文給大家介紹了golang使用viper解析配置文件,需要的朋友可以參考下
    2024-08-08
  • 使用go求冪的幾種方法小結(jié)

    使用go求冪的幾種方法小結(jié)

    這篇文章主要介紹了使用go求冪的幾種方法小結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go并發(fā)編程中sync/errGroup的使用

    Go并發(fā)編程中sync/errGroup的使用

    本文主要介紹了Go并發(fā)編程中sync/errGroup的使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • golang中for循環(huán)遍歷channel時需要注意的問題詳解

    golang中for循環(huán)遍歷channel時需要注意的問題詳解

    這篇文章主要給大家介紹了關(guān)于golang中for循環(huán)遍歷channel時需要注意的問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • Go歸并排序算法的實現(xiàn)方法

    Go歸并排序算法的實現(xiàn)方法

    歸并排序采用的也是分治的策略,把原本的問題先分解成一些小問題進行求解,再把這些小問題各自的答案修整到一起得到原本問題的答案,從而達到分而治之的目的,對Go歸并排序算法相關(guān)知識感興趣的朋友一起看看吧
    2022-04-04
  • 最新評論