自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go-Zero 是如何做路由管理的?

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

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

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

路由管理方案

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

注意這里只是做一個簡單的概括性對比。

標(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)鍵字進(jì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ī)則:

  1. 路由必須以 / 開頭
  2. 路由節(jié)點必須以 / 分隔
  3. 路由節(jié)點中可以包含 :,但是 : 必須是路由節(jié)點的第一個字符,: 后面的節(jié)點值必須要在結(jié)請求體中有 path tag 聲明,用于接收路由參數(shù)
  4. 路由節(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ù)雜,通讀之后還是很有收獲的。

大家如果感興趣的話,可以找到項目更詳細(xì)地閱讀。也可以關(guān)注我,接下來還會分析其他模塊的源碼。

責(zé)任編輯:姜華 來源: AlwaysBeta
相關(guān)推薦

2023-08-10 08:00:42

令牌限流器計數(shù)器

2023-08-28 08:00:45

2024-04-28 14:46:55

gozero微服務(wù)技巧

2012-03-12 16:42:54

測試

2024-04-22 08:26:37

協(xié)同編輯FigmaOT 算法

2024-01-15 07:42:37

Figma協(xié)同編輯算法

2011-08-01 09:08:49

程序員

2019-11-06 09:39:42

云成本企業(yè)云計算

2015-08-20 11:09:53

準(zhǔn)入控制盈高

2021-05-13 08:00:00

軟件測試程序IT

2021-07-06 10:03:05

軟件開發(fā) 技術(shù)

2022-12-07 11:21:30

Reactdiff

2017-11-16 21:21:18

DevOps測試軟件開發(fā)

2017-11-23 19:14:00

CRM數(shù)字化軟件開發(fā)

2019-09-15 14:07:49

2015-07-30 11:21:16

代碼審查

2022-08-03 09:11:31

React性能優(yōu)化

2022-08-29 08:08:58

SQLOracleCPU

2023-01-18 23:52:07

RTA用戶粒度運營

2015-08-11 09:13:16

2048WEB開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號